diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/Modules/plugins | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/Modules/plugins')
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; +}; + +} |