summaryrefslogtreecommitdiff
path: root/Source/WebCore/Modules/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/Modules/plugins')
-rw-r--r--Source/WebCore/Modules/plugins/PluginReplacement.h38
-rw-r--r--Source/WebCore/Modules/plugins/QuickTimePluginReplacement.css33
-rw-r--r--Source/WebCore/Modules/plugins/QuickTimePluginReplacement.h70
-rw-r--r--Source/WebCore/Modules/plugins/QuickTimePluginReplacement.idl35
-rw-r--r--Source/WebCore/Modules/plugins/QuickTimePluginReplacement.js350
-rw-r--r--Source/WebCore/Modules/plugins/YouTubePluginReplacement.cpp354
-rw-r--r--Source/WebCore/Modules/plugins/YouTubePluginReplacement.h63
7 files changed, 927 insertions, 16 deletions
diff --git a/Source/WebCore/Modules/plugins/PluginReplacement.h b/Source/WebCore/Modules/plugins/PluginReplacement.h
index b08f4e476..b54c66b0f 100644
--- a/Source/WebCore/Modules/plugins/PluginReplacement.h
+++ b/Source/WebCore/Modules/plugins/PluginReplacement.h
@@ -10,10 +10,10 @@
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
- * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
@@ -23,11 +23,9 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef PluginReplacement_h
-#define PluginReplacement_h
+#pragma once
#include "RenderPtr.h"
-#include <wtf/RefCounted.h>
#include <wtf/text/WTFString.h>
namespace JSC {
@@ -39,32 +37,36 @@ namespace WebCore {
class HTMLPlugInElement;
class RenderElement;
class RenderStyle;
+class RenderTreePosition;
+class Settings;
class ShadowRoot;
+class URL;
class PluginReplacement : public RefCounted<PluginReplacement> {
public:
virtual ~PluginReplacement() { }
- virtual bool installReplacement(ShadowRoot*) = 0;
- virtual JSC::JSObject* scriptObject() { return 0; }
+ virtual bool installReplacement(ShadowRoot&) = 0;
+ virtual JSC::JSObject* scriptObject() { return nullptr; }
virtual bool willCreateRenderer() { return false; }
- virtual RenderPtr<RenderElement> createElementRenderer(HTMLPlugInElement&, PassRef<RenderStyle>) = 0;
-
-protected:
- PluginReplacement() { }
+ virtual RenderPtr<RenderElement> createElementRenderer(HTMLPlugInElement&, RenderStyle&&, const RenderTreePosition&) = 0;
};
-typedef PassRefPtr<PluginReplacement> (*CreatePluginReplacement)(HTMLPlugInElement&, const Vector<String>& paramNames, const Vector<String>& paramValues);
+typedef Ref<PluginReplacement> (*CreatePluginReplacement)(HTMLPlugInElement&, const Vector<String>& paramNames, const Vector<String>& paramValues);
typedef bool (*PluginReplacementSupportsType)(const String&);
typedef bool (*PluginReplacementSupportsFileExtension)(const String&);
+typedef bool (*PluginReplacementSupportsURL)(const URL&);
+typedef bool (*PluginReplacementEnabledForSettings)(const Settings&);
class ReplacementPlugin {
public:
- ReplacementPlugin(CreatePluginReplacement constructor, PluginReplacementSupportsType supportsType, PluginReplacementSupportsFileExtension supportsFileExtension)
+ ReplacementPlugin(CreatePluginReplacement constructor, PluginReplacementSupportsType supportsType, PluginReplacementSupportsFileExtension supportsFileExtension, PluginReplacementSupportsURL supportsURL, PluginReplacementEnabledForSettings isEnabledBySettings)
: m_constructor(constructor)
, m_supportsType(supportsType)
, m_supportsFileExtension(supportsFileExtension)
+ , m_supportsURL(supportsURL)
+ , m_isEnabledBySettings(isEnabledBySettings)
{
}
@@ -72,21 +74,25 @@ public:
: m_constructor(other.m_constructor)
, m_supportsType(other.m_supportsType)
, m_supportsFileExtension(other.m_supportsFileExtension)
+ , m_supportsURL(other.m_supportsURL)
+ , m_isEnabledBySettings(other.m_isEnabledBySettings)
{
}
- PassRefPtr<PluginReplacement> create(HTMLPlugInElement& element, const Vector<String>& paramNames, const Vector<String>& paramValues) const { return m_constructor(element, paramNames, paramValues); }
+ Ref<PluginReplacement> create(HTMLPlugInElement& element, const Vector<String>& paramNames, const Vector<String>& paramValues) const { return m_constructor(element, paramNames, paramValues); }
bool supportsType(const String& mimeType) const { return m_supportsType(mimeType); }
bool supportsFileExtension(const String& extension) const { return m_supportsFileExtension(extension); }
+ bool supportsURL(const URL& url) const { return m_supportsURL(url); }
+ bool isEnabledBySettings(const Settings& settings) const { return m_isEnabledBySettings(settings); };
private:
CreatePluginReplacement m_constructor;
PluginReplacementSupportsType m_supportsType;
PluginReplacementSupportsFileExtension m_supportsFileExtension;
+ PluginReplacementSupportsURL m_supportsURL;
+ PluginReplacementEnabledForSettings m_isEnabledBySettings;
};
typedef void (*PluginReplacementRegistrar)(const ReplacementPlugin&);
}
-
-#endif
diff --git a/Source/WebCore/Modules/plugins/QuickTimePluginReplacement.css b/Source/WebCore/Modules/plugins/QuickTimePluginReplacement.css
new file mode 100644
index 000000000..9219747e3
--- /dev/null
+++ b/Source/WebCore/Modules/plugins/QuickTimePluginReplacement.css
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2013 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+embed::-webkit-plugin-replacement,
+object::-webkit-plugin-replacement
+{
+ position: relative;
+ display: inline-block;
+ width: 100%;
+ height: 100%;
+}
+
diff --git a/Source/WebCore/Modules/plugins/QuickTimePluginReplacement.h b/Source/WebCore/Modules/plugins/QuickTimePluginReplacement.h
new file mode 100644
index 000000000..d574aa0d3
--- /dev/null
+++ b/Source/WebCore/Modules/plugins/QuickTimePluginReplacement.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2013 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "PluginReplacement.h"
+
+namespace WebCore {
+
+class DOMWrapperWorld;
+class HTMLVideoElement;
+
+class QuickTimePluginReplacement final : public PluginReplacement {
+public:
+ static void registerPluginReplacement(PluginReplacementRegistrar);
+
+ virtual ~QuickTimePluginReplacement();
+
+ unsigned long long movieSize() const;
+ void postEvent(const String&);
+
+ HTMLVideoElement* parentElement() { return m_mediaElement.get(); }
+
+private:
+ QuickTimePluginReplacement(HTMLPlugInElement&, const Vector<String>& paramNames, const Vector<String>& paramValues);
+ static Ref<PluginReplacement> create(HTMLPlugInElement&, const Vector<String>& paramNames, const Vector<String>& paramValues);
+ static bool supportsMimeType(const String&);
+ static bool supportsFileExtension(const String&);
+ static bool supportsURL(const URL&) { return true; }
+ static bool isEnabledBySettings(const Settings&);
+
+ bool installReplacement(ShadowRoot&) final;
+ JSC::JSObject* scriptObject() final { return m_scriptObject; }
+
+ bool willCreateRenderer() final { return m_mediaElement; }
+ RenderPtr<RenderElement> createElementRenderer(HTMLPlugInElement&, RenderStyle&&, const RenderTreePosition&) final;
+
+ bool ensureReplacementScriptInjected();
+ DOMWrapperWorld& isolatedWorld();
+
+ HTMLPlugInElement* m_parentElement;
+ RefPtr<HTMLVideoElement> m_mediaElement;
+ const Vector<String> m_names;
+ const Vector<String> m_values;
+ JSC::JSObject* m_scriptObject; // FIXME: Why is it safe to have this pointer here? What keeps it alive during GC?
+};
+
+}
diff --git a/Source/WebCore/Modules/plugins/QuickTimePluginReplacement.idl b/Source/WebCore/Modules/plugins/QuickTimePluginReplacement.idl
new file mode 100644
index 000000000..983d95f90
--- /dev/null
+++ b/Source/WebCore/Modules/plugins/QuickTimePluginReplacement.idl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+[
+ NoInterfaceObject,
+ JSGenerateToJSObject,
+] interface QuickTimePluginReplacement {
+ readonly attribute unsigned long long movieSize;
+ [CustomGetter] readonly attribute any timedMetaData;
+ [CustomGetter] readonly attribute any accessLog;
+ [CustomGetter] readonly attribute any errorLog;
+ void postEvent(DOMString eventName);
+};
diff --git a/Source/WebCore/Modules/plugins/QuickTimePluginReplacement.js b/Source/WebCore/Modules/plugins/QuickTimePluginReplacement.js
new file mode 100644
index 000000000..e59f835b0
--- /dev/null
+++ b/Source/WebCore/Modules/plugins/QuickTimePluginReplacement.js
@@ -0,0 +1,350 @@
+
+function createPluginReplacement(root, parent, host, attributeNames, attributeValues)
+{
+ return new Replacement(root, parent, host, attributeNames, attributeValues);
+};
+
+function Replacement(root, parent, host, attributeNames, attributeValues)
+{
+ this.root = root;
+ this.parent = parent;
+ this.host = host;
+ this.listeners = {};
+ this.scriptObject = {};
+
+ this.autoExitFullScreen = true;
+ this.postEvents = false;
+ this.height = 0;
+ this.width = 0;
+ this.src = "";
+ this.autohref = false;
+ this.href = "";
+ this.qtsrc = "";
+ this.baseUrl = "";
+ this.target = "";
+
+ this.createVideoElement(attributeNames, attributeValues);
+ this.createScriptInterface();
+};
+
+Replacement.prototype = {
+
+ HandledVideoEvents: {
+ loadstart: 'handleLoadStart',
+ error: 'handleError',
+ loadedmetadata: 'handleLoadedMetaData',
+ canplay: 'qt_canplay',
+ canplaythrough: 'qt_canplaythrough',
+ play: 'qt_play',
+ pause: 'qt_pause',
+ ended: 'handleEnded',
+ webkitfullscreenchange: 'handleFullscreenChange',
+ },
+
+ AttributeMap: {
+ autoexitfullscreen: 'autoExitFullScreen',
+ postdomevents: 'postEvents',
+ height: 'height',
+ width: 'width',
+ qtsrc: 'qtsrc',
+ src: 'src',
+ airplay: 'x-webkit-airplay=',
+ href: 'href',
+ target: 'target',
+ },
+
+ MethodMap: {
+ SetURL : 'setURL',
+ GetURL : 'url',
+ Play : 'play',
+ Stop : 'pause',
+ GetRate : 'rate',
+ SetRate : 'setRate',
+ IsFullScreen : 'isFullScreen',
+ ExitFullScreen : 'exitFullScreen',
+ GetPluginStatus : 'pluginStatus',
+ GetTime : 'currentTime',
+ SetTime : 'setCurrentTime',
+ SeekToDate : 'seekToDate',
+ GetDate : 'date',
+ GetDuration : 'duration',
+ GetTimeScale : 'timeScale',
+ GetMaxTimeLoaded : 'maxTimeLoaded',
+ GetMaxBytesLoaded : 'maxBytesLoaded',
+ GetMovieSize : 'movieSize',
+ GetTimedMetadataUpdates : 'timedMetadataUpdates',
+ GetAccessLog : 'accessLog',
+ GetErrorLog : 'errorLog',
+ },
+
+ TimeScale: 30000,
+
+ createVideoElement: function(attributeNames, attributeValues)
+ {
+ var video = this.video = document.createElement('video');
+
+ for (name in this.HandledVideoEvents)
+ video.addEventListener(name, this, false);
+
+ for (i = 0; i < attributeNames.length; i++) {
+ var property = this.AttributeMap[attributeNames[i]];
+ if (this[property] != undefined)
+ this[property] = attributeValues[i];
+ }
+
+ video.setAttribute('pseudo', '-webkit-plugin-replacement');
+ video.setAttribute('controls', 'controls');
+ this.setStatus('Waiting');
+
+ var src = this.resolveRelativeToUrl(this.src, "");
+ this.baseUrl = src;
+
+ // The 'qtsrc' attribute is used when a page author wanted to always use the QuickTime plug-in
+ // to load a media type even if another plug-in was registered for that type. It tells the
+ // plug-in to ignore the 'src' url, and to load the 'qtsrc' url instead.
+ if (this.qtsrc)
+ src = this.resolveRelativeToUrl(this.qtsrc, this.src);
+ if (this.href && this.target) {
+ src = this.resolveRelativeToUrl(this.href, this.src);
+ video.poster = this.src;
+ video.setAttribute('preload', 'none');
+ }
+
+ if (src.length) {
+ this.setStatus('Validating');
+ this.video.src = src;
+ }
+
+ this.root.appendChild(video);
+ },
+
+ resolveRelativeToUrl: function(url, baseUrl)
+ {
+ if (url.indexOf('://') != -1)
+ return url;
+ if (baseUrl.indexOf('://') == -1)
+ baseUrl = this.resolveRelativeToUrl(baseUrl, document.baseURI);
+
+ var base = document.createElement('base');
+ base.href = baseUrl;
+ document.head.appendChild(base);
+
+ var resolver = document.createElement('a');
+ resolver.href = url;
+ url = resolver.href;
+
+ document.head.removeChild(base);
+ base = null;
+
+ return url;
+ },
+
+ createScriptInterface: function()
+ {
+ for (name in this.MethodMap) {
+ var methodName = this.MethodMap[name];
+ this.scriptObject[name] = this[methodName].bind(this);
+ }
+ },
+
+ handleEvent: function(event)
+ {
+ if (event.target !== this.video)
+ return;
+
+ try {
+ var eventData = this.HandledVideoEvents[event.type];
+ if (!eventData)
+ return;
+
+ if (this[eventData] && typeof this[eventData] === "function")
+ this[eventData].call(this, event);
+ else
+ this.postEvent(eventData);
+ } catch(e) {
+ if (window.console)
+ console.error(e);
+ }
+ },
+
+ postEvent: function(eventName)
+ {
+ try {
+ if (this.postEvents)
+ this.host.postEvent(eventName);
+ } catch(e) { }
+ },
+
+ setStatus: function(status)
+ {
+ this.status = status;
+ },
+
+ handleLoadedMetaData: function(event)
+ {
+ this.setStatus('Playable');
+ this.postEvent('qt_validated');
+ this.postEvent('qt_loadedfirstframe');
+ this.postEvent('qt_loadedmetadata');
+ },
+
+ handleFullscreenChange: function(event)
+ {
+ this.postEvent(this.isFullScreen() ? 'qt_enterfullscreen' : 'qt_exitfullscreen');
+ },
+
+ handleError: function(event)
+ {
+ this.setStatus('Error');
+ this.postEvent('qt_error');
+ },
+
+ handleLoadStart:function(event)
+ {
+ if (this.video.poster)
+ this.setStatus('Waiting');
+ else
+ this.setStatus('Loading');
+ this.postEvent('qt_begin');
+ },
+
+ handleEnded: function(event)
+ {
+ this.postEvent('qt_ended');
+ if (this.isFullScreen() && this.autoExitFullScreen)
+ document.webkitExitFullscreen();
+ },
+
+ isFullScreen: function()
+ {
+ return document.webkitCurrentFullScreenElement === this.video;
+ },
+
+ setURL: function(url)
+ {
+ this.setStatus('Validating');
+ if (url.length)
+ url = this.resolveRelativeToUrl(url, this.baseUrl);
+ this.video.src = url;
+ },
+
+ url: function()
+ {
+ return this.video.currentSrc;
+ },
+
+ play: function()
+ {
+ this.video.play();
+ },
+
+ pause: function()
+ {
+ this.video.playbackRate = 0;
+ this.video.pause();
+ },
+
+ rate: function()
+ {
+ return this.video.paused ? 0 : 1;
+ },
+
+ setRate: function(rate)
+ {
+ if (rate)
+ this.video.play();
+ else
+ this.video.pause();
+ },
+
+ exitFullScreen: function()
+ {
+ document.webkitExitFullscreen();
+ },
+
+ pluginStatus: function()
+ {
+ return this.status;
+ },
+
+ currentTime: function()
+ {
+ return this.video.currentTime * this.TimeScale;
+ },
+
+ setCurrentTime: function(time)
+ {
+ this.video.currentTime = time / this.TimeScale;
+ },
+
+ seekToDate: function()
+ {
+ // FIXME: not implemented yet.
+ },
+
+ date: function()
+ {
+ return new Date();
+ },
+
+ duration: function()
+ {
+ return this.video.duration * this.TimeScale;
+ },
+
+ timeScale: function()
+ {
+ // Note: QuickTime movies and MPEG-4 files have a timescale, but it is not exposed by all media engines.
+ // 30000 works well with common frame rates, eg. 29.97 NTSC can be represented accurately as a time
+ // scale of 30000 and frame duration of 1001.
+ return 30000;
+ },
+
+ maxTimeLoaded: function()
+ {
+ return this.video.duration * this.TimeScale;
+ },
+
+ maxBytesLoaded: function()
+ {
+ var percentLoaded = this.video.buffered.end(0) / this.video.duration;
+ return percentLoaded * this.movieSize();
+ },
+
+ movieSize: function()
+ {
+ try {
+ return this.host.movieSize;
+ } catch(e) { }
+
+ return 0;
+ },
+
+ timedMetadataUpdates: function()
+ {
+ try {
+ return this.host.timedMetaData;
+ } catch(e) { }
+
+ return null;
+ },
+
+ accessLog: function()
+ {
+ try {
+ return this.host.accessLog;
+ } catch(e) { }
+
+ return null;
+ },
+
+ errorLog: function()
+ {
+ try {
+ return this.host.errorLog;
+ } catch(e) { }
+
+ return null;
+ },
+};
+
diff --git a/Source/WebCore/Modules/plugins/YouTubePluginReplacement.cpp b/Source/WebCore/Modules/plugins/YouTubePluginReplacement.cpp
new file mode 100644
index 000000000..b497adc77
--- /dev/null
+++ b/Source/WebCore/Modules/plugins/YouTubePluginReplacement.cpp
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2014 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "YouTubePluginReplacement.h"
+
+#include "HTMLIFrameElement.h"
+#include "HTMLNames.h"
+#include "HTMLParserIdioms.h"
+#include "HTMLPlugInElement.h"
+#include "RenderElement.h"
+#include "Settings.h"
+#include "ShadowRoot.h"
+#include "YouTubeEmbedShadowElement.h"
+#include <wtf/text/StringBuilder.h>
+
+namespace WebCore {
+
+void YouTubePluginReplacement::registerPluginReplacement(PluginReplacementRegistrar registrar)
+{
+ registrar(ReplacementPlugin(create, supportsMimeType, supportsFileExtension, supportsURL, isEnabledBySettings));
+}
+
+Ref<PluginReplacement> YouTubePluginReplacement::create(HTMLPlugInElement& plugin, const Vector<String>& paramNames, const Vector<String>& paramValues)
+{
+ return adoptRef(*new YouTubePluginReplacement(plugin, paramNames, paramValues));
+}
+
+bool YouTubePluginReplacement::supportsMimeType(const String& mimeType)
+{
+ return equalLettersIgnoringASCIICase(mimeType, "application/x-shockwave-flash")
+ || equalLettersIgnoringASCIICase(mimeType, "application/futuresplash");
+}
+
+bool YouTubePluginReplacement::supportsFileExtension(const String& extension)
+{
+ return equalLettersIgnoringASCIICase(extension, "spl") || equalLettersIgnoringASCIICase(extension, "swf");
+}
+
+YouTubePluginReplacement::YouTubePluginReplacement(HTMLPlugInElement& plugin, const Vector<String>& paramNames, const Vector<String>& paramValues)
+ : m_parentElement(&plugin)
+{
+ ASSERT(paramNames.size() == paramValues.size());
+ for (size_t i = 0; i < paramNames.size(); ++i)
+ m_attributes.add(paramNames[i], paramValues[i]);
+}
+
+RenderPtr<RenderElement> YouTubePluginReplacement::createElementRenderer(HTMLPlugInElement& plugin, RenderStyle&& style, const RenderTreePosition& insertionPosition)
+{
+ ASSERT_UNUSED(plugin, m_parentElement == &plugin);
+
+ if (!m_embedShadowElement)
+ return nullptr;
+
+ return m_embedShadowElement->createElementRenderer(WTFMove(style), insertionPosition);
+}
+
+bool YouTubePluginReplacement::installReplacement(ShadowRoot& root)
+{
+ m_embedShadowElement = YouTubeEmbedShadowElement::create(m_parentElement->document());
+
+ root.appendChild(*m_embedShadowElement);
+
+ auto iframeElement = HTMLIFrameElement::create(HTMLNames::iframeTag, m_parentElement->document());
+ if (m_attributes.contains("width"))
+ iframeElement->setAttributeWithoutSynchronization(HTMLNames::widthAttr, AtomicString("100%", AtomicString::ConstructFromLiteral));
+
+ const auto& heightValue = m_attributes.find("height");
+ if (heightValue != m_attributes.end()) {
+ iframeElement->setAttribute(HTMLNames::styleAttr, AtomicString("max-height: 100%", AtomicString::ConstructFromLiteral));
+ iframeElement->setAttributeWithoutSynchronization(HTMLNames::heightAttr, heightValue->value);
+ }
+
+ iframeElement->setAttributeWithoutSynchronization(HTMLNames::srcAttr, youTubeURL(m_attributes.get("src")));
+ iframeElement->setAttributeWithoutSynchronization(HTMLNames::frameborderAttr, AtomicString("0", AtomicString::ConstructFromLiteral));
+
+ // Disable frame flattening for this iframe.
+ iframeElement->setAttributeWithoutSynchronization(HTMLNames::scrollingAttr, AtomicString("no", AtomicString::ConstructFromLiteral));
+ m_embedShadowElement->appendChild(iframeElement);
+
+ return true;
+}
+
+static inline URL createYouTubeURL(const String& videoID, const String& timeID)
+{
+ ASSERT(!videoID.isEmpty());
+ ASSERT(videoID != "/");
+
+ URL result(URL(), "youtube:" + videoID);
+ if (!timeID.isEmpty())
+ result.setQuery("t=" + timeID);
+
+ return result;
+}
+
+static YouTubePluginReplacement::KeyValueMap queryKeysAndValues(const String& queryString)
+{
+ YouTubePluginReplacement::KeyValueMap queryDictionary;
+
+ size_t queryLength = queryString.length();
+ if (!queryLength)
+ return queryDictionary;
+
+ size_t equalSearchLocation = 0;
+ size_t equalSearchLength = queryLength;
+
+ while (equalSearchLocation < queryLength - 1 && equalSearchLength) {
+
+ // Search for "=".
+ size_t equalLocation = queryString.find('=', equalSearchLocation);
+ if (equalLocation == notFound)
+ break;
+
+ size_t indexAfterEqual = equalLocation + 1;
+ if (indexAfterEqual > queryLength - 1)
+ break;
+
+ // Get the key before the "=".
+ size_t keyLocation = equalSearchLocation;
+ size_t keyLength = equalLocation - equalSearchLocation;
+
+ // Seach for the ampersand.
+ size_t ampersandLocation = queryString.find('&', indexAfterEqual);
+
+ // Get the value after the "=", before the ampersand.
+ size_t valueLocation = indexAfterEqual;
+ size_t valueLength;
+ if (ampersandLocation != notFound)
+ valueLength = ampersandLocation - indexAfterEqual;
+ else
+ valueLength = queryLength - indexAfterEqual;
+
+ // Save the key and the value.
+ if (keyLength && valueLength) {
+ String key = queryString.substring(keyLocation, keyLength).convertToASCIILowercase();
+ String value = queryString.substring(valueLocation, valueLength);
+ value.replace('+', ' ');
+
+ if (!key.isEmpty() && !value.isEmpty())
+ queryDictionary.add(key, value);
+ }
+
+ if (ampersandLocation == notFound)
+ break;
+
+ // Continue searching after the ampersand.
+ size_t indexAfterAmpersand = ampersandLocation + 1;
+ equalSearchLocation = indexAfterAmpersand;
+ equalSearchLength = queryLength - indexAfterAmpersand;
+ }
+
+ return queryDictionary;
+}
+
+static bool hasCaseInsensitivePrefix(const String& input, const String& prefix)
+{
+ return input.startsWith(prefix, false);
+}
+
+static bool isYouTubeURL(const URL& url)
+{
+ String hostName = url.host();
+ return equalLettersIgnoringASCIICase(hostName, "m.youtube.com")
+ || equalLettersIgnoringASCIICase(hostName, "youtu.be")
+ || equalLettersIgnoringASCIICase(hostName, "www.youtube.com")
+ || equalLettersIgnoringASCIICase(hostName, "youtube.com")
+ || equalLettersIgnoringASCIICase(hostName, "www.youtube-nocookie.com")
+ || equalLettersIgnoringASCIICase(hostName, "youtube-nocookie.com");
+}
+
+static const String& valueForKey(const YouTubePluginReplacement::KeyValueMap& dictionary, const String& key)
+{
+ const auto& value = dictionary.find(key);
+ if (value == dictionary.end())
+ return emptyString();
+
+ return value->value;
+}
+
+static URL processAndCreateYouTubeURL(const URL& url, bool& isYouTubeShortenedURL, String& outPathAfterFirstAmpersand)
+{
+ if (!url.protocolIsInHTTPFamily())
+ return URL();
+
+ // Bail out early if we aren't even on www.youtube.com or youtube.com.
+ if (!isYouTubeURL(url))
+ return URL();
+
+ String hostName = url.host();
+ bool isYouTubeMobileWebAppURL = equalLettersIgnoringASCIICase(hostName, "m.youtube.com");
+ isYouTubeShortenedURL = equalLettersIgnoringASCIICase(hostName, "youtu.be");
+
+ // Short URL of the form: http://youtu.be/v1d301D
+ if (isYouTubeShortenedURL) {
+ String videoID = url.lastPathComponent();
+ if (videoID.isEmpty() || videoID == "/")
+ return URL();
+ return createYouTubeURL(videoID, emptyString());
+ }
+
+ String path = url.path();
+ String query = url.query();
+ String fragment = url.fragmentIdentifier();
+
+ // On the YouTube mobile web app, the path and query string are put into the
+ // fragment so that one web page is only ever loaded (see <rdar://problem/9550639>).
+ if (isYouTubeMobileWebAppURL) {
+ size_t location = fragment.find('?');
+ if (location == notFound) {
+ path = fragment;
+ query = emptyString();
+ } else {
+ path = fragment.substring(0, location);
+ query = fragment.substring(location + 1);
+ }
+ fragment = emptyString();
+ }
+
+ if (equalLettersIgnoringASCIICase(path, "/watch")) {
+ if (!query.isEmpty()) {
+ const auto& queryDictionary = queryKeysAndValues(query);
+ String videoID = valueForKey(queryDictionary, "v");
+
+ if (!videoID.isEmpty()) {
+ const auto& fragmentDictionary = queryKeysAndValues(url.fragmentIdentifier());
+ String timeID = valueForKey(fragmentDictionary, "t");
+ return createYouTubeURL(videoID, timeID);
+ }
+ }
+
+ // May be a new-style link (see <rdar://problem/7733692>).
+ if (fragment.startsWith('!')) {
+ query = fragment.substring(1);
+
+ if (!query.isEmpty()) {
+ const auto& queryDictionary = queryKeysAndValues(query);
+ String videoID = valueForKey(queryDictionary, "v");
+
+ if (!videoID.isEmpty()) {
+ String timeID = valueForKey(queryDictionary, "t");
+ return createYouTubeURL(videoID, timeID);
+ }
+ }
+ }
+ } else if (hasCaseInsensitivePrefix(path, "/v/") || hasCaseInsensitivePrefix(path, "/e/")) {
+ String lastPathComponent = url.lastPathComponent();
+ String videoID;
+ String pathAfterFirstAmpersand;
+
+ size_t ampersandLocation = lastPathComponent.find('&');
+ if (ampersandLocation != notFound) {
+ // Some URLs we care about use & in place of ? for the first query parameter.
+ videoID = lastPathComponent.substring(0, ampersandLocation);
+ pathAfterFirstAmpersand = lastPathComponent.substring(ampersandLocation + 1, lastPathComponent.length() - ampersandLocation);
+ } else
+ videoID = lastPathComponent;
+
+ if (!videoID.isEmpty()) {
+ outPathAfterFirstAmpersand = pathAfterFirstAmpersand;
+ return createYouTubeURL(videoID, emptyString());
+ }
+ }
+
+ return URL();
+}
+
+String YouTubePluginReplacement::youTubeURL(const String& srcString)
+{
+ URL srcURL = m_parentElement->document().completeURL(stripLeadingAndTrailingHTMLSpaces(srcString));
+ return youTubeURLFromAbsoluteURL(srcURL, srcString);
+}
+
+String YouTubePluginReplacement::youTubeURLFromAbsoluteURL(const URL& srcURL, const String& srcString)
+{
+ bool isYouTubeShortenedURL = false;
+ String possibleMalformedQuery;
+ URL youTubeURL = processAndCreateYouTubeURL(srcURL, isYouTubeShortenedURL, possibleMalformedQuery);
+ if (srcURL.isEmpty() || youTubeURL.isEmpty())
+ return srcString;
+
+ // Transform the youtubeURL (youtube:VideoID) to iframe embed url which has the format: http://www.youtube.com/embed/VideoID
+ const String& srcPath = srcURL.path();
+ const String& videoID = youTubeURL.string().substring(youTubeURL.protocol().length() + 1);
+ size_t locationOfVideoIDInPath = srcPath.find(videoID);
+
+ size_t locationOfPathBeforeVideoID = notFound;
+ if (locationOfVideoIDInPath != notFound) {
+ ASSERT(locationOfVideoIDInPath);
+
+ // From the original URL, we need to get the part before /path/VideoId.
+ locationOfPathBeforeVideoID = srcString.find(srcPath.substring(0, locationOfVideoIDInPath));
+ } else if (equalLettersIgnoringASCIICase(srcPath, "/watch")) {
+ // From the original URL, we need to get the part before /watch/#!v=VideoID
+ // FIXME: Shouldn't this be ASCII case-insensitive?
+ locationOfPathBeforeVideoID = srcString.find("/watch");
+ } else
+ return srcString;
+
+ ASSERT(locationOfPathBeforeVideoID != notFound);
+
+ const String& srcURLPrefix = srcString.substring(0, locationOfPathBeforeVideoID);
+ String query = srcURL.query();
+ // If the URL has no query, use the possibly malformed query we found.
+ if (query.isEmpty())
+ query = possibleMalformedQuery;
+
+ // Append the query string if it is valid.
+ StringBuilder finalURL;
+ if (isYouTubeShortenedURL)
+ finalURL.appendLiteral("http://www.youtube.com");
+ else
+ finalURL.append(srcURLPrefix);
+ finalURL.appendLiteral("/embed/");
+ finalURL.append(videoID);
+ if (!query.isEmpty()) {
+ finalURL.append('?');
+ finalURL.append(query);
+ }
+ return finalURL.toString();
+}
+
+bool YouTubePluginReplacement::supportsURL(const URL& url)
+{
+ return isYouTubeURL(url);
+}
+
+bool YouTubePluginReplacement::isEnabledBySettings(const Settings& settings)
+{
+ return settings.youTubeFlashPluginReplacementEnabled();
+}
+
+}
diff --git a/Source/WebCore/Modules/plugins/YouTubePluginReplacement.h b/Source/WebCore/Modules/plugins/YouTubePluginReplacement.h
new file mode 100644
index 000000000..dc8c0974d
--- /dev/null
+++ b/Source/WebCore/Modules/plugins/YouTubePluginReplacement.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2014 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "PluginReplacement.h"
+#include <wtf/HashMap.h>
+
+namespace WebCore {
+
+class YouTubeEmbedShadowElement;
+
+class YouTubePluginReplacement final : public PluginReplacement {
+public:
+ static void registerPluginReplacement(PluginReplacementRegistrar);
+
+ typedef HashMap<String, String> KeyValueMap;
+
+ WEBCORE_EXPORT static String youTubeURLFromAbsoluteURL(const URL& srcURL, const String& srcString);
+
+private:
+ YouTubePluginReplacement(HTMLPlugInElement&, const Vector<String>& paramNames, const Vector<String>& paramValues);
+ static Ref<PluginReplacement> create(HTMLPlugInElement&, const Vector<String>& paramNames, const Vector<String>& paramValues);
+ static bool supportsMimeType(const String&);
+ static bool supportsFileExtension(const String&);
+ static bool supportsURL(const URL&);
+ static bool isEnabledBySettings(const Settings&);
+
+ bool installReplacement(ShadowRoot&) final;
+
+ String youTubeURL(const String& rawURL);
+
+ bool willCreateRenderer() final { return m_embedShadowElement; }
+ RenderPtr<RenderElement> createElementRenderer(HTMLPlugInElement&, RenderStyle&&, const RenderTreePosition&) final;
+
+ HTMLPlugInElement* m_parentElement;
+ RefPtr<YouTubeEmbedShadowElement> m_embedShadowElement;
+ KeyValueMap m_attributes;
+};
+
+}