source: webkit/trunk/Source/WebCore/Modules/plugins/YouTubePluginReplacement.cpp

Last change on this file was 211964, checked in by akling@apple.com, 7 weeks ago

Document should always have a Settings.
<https://webkit.org/b/120172>

Reviewed by Antti Koivisto.

Since a Document can't move between Frames, and it's either frameless
or partnered with a Frame provided at construction time, we know that
it will never need to switch between different Settings objects either.

Give Document a Ref<Settings> that contains either the Frame's Settings
or a default-constructed Settings object if frameless.
Document::settings() is promoted to a reference getter.

The bulk of this patch is removing now-unnecessary null checks.

  • Modules/encryptedmedia/legacy/WebKitMediaKeySession.cpp:

(WebCore::WebKitMediaKeySession::mediaKeysStorageDirectory):

  • Modules/plugins/PluginReplacement.h:

(WebCore::ReplacementPlugin::isEnabledBySettings):

  • Modules/plugins/QuickTimePluginReplacement.h:
  • Modules/plugins/QuickTimePluginReplacement.mm:

(WebCore::QuickTimePluginReplacement::isEnabledBySettings):

  • Modules/plugins/YouTubePluginReplacement.cpp:

(WebCore::YouTubePluginReplacement::isEnabledBySettings):

  • Modules/plugins/YouTubePluginReplacement.h:
  • Modules/webaudio/AudioContext.cpp:

(WebCore::AudioContext::constructCommon):

  • bindings/js/CachedScriptFetcher.cpp:

(WebCore::CachedScriptFetcher::requestScriptWithCache):

  • css/CSSFontFace.cpp:

(WebCore::CSSFontFace::appendSources):
(WebCore::CSSFontFace::webFontsShouldAlwaysFallBack):

  • css/CSSFontSelector.cpp:

(WebCore::CSSFontSelector::fallbackFontCount):
(WebCore::CSSFontSelector::fallbackFontAt):

  • css/StyleBuilderConverter.h:

(WebCore::StyleBuilderConverter::convertResize):

  • css/StyleBuilderCustom.h:

(WebCore::StyleBuilderCustom::applyValueFontFamily):

  • css/StyleResolver.cpp:

(WebCore::StyleResolver::StyleResolver):
(WebCore::StyleResolver::defaultStyleForElement):
(WebCore::StyleResolver::adjustRenderStyle):
(WebCore::StyleResolver::checkForGenericFamilyChange):
(WebCore::StyleResolver::initializeFontStyle):

  • css/StyleResolver.h:

(WebCore::StyleResolver::settings):
(WebCore::StyleResolver::documentSettings): Deleted.

  • css/parser/CSSParser.cpp:

(WebCore::CSSParserContext::CSSParserContext):

  • dom/Document.cpp:

(WebCore::Document::Document):
(WebCore::Document::setReadyState):
(WebCore::Document::setVisualUpdatesAllowed):
(WebCore::Document::defaultCharsetForLegacyBindings):
(WebCore::Document::recalcStyle):
(WebCore::Document::implicitClose):
(WebCore::Document::minimumLayoutDelay):
(WebCore::Document::setDomain):
(WebCore::Document::audioPlaybackRequiresUserGesture):
(WebCore::Document::videoPlaybackRequiresUserGesture):
(WebCore::Document::storageBlockingStateDidChange):
(WebCore::Document::isTelephoneNumberParsingEnabled):
(WebCore::Document::initSecurityContext):
(WebCore::Document::initDNSPrefetch):
(WebCore::Document::getCachedLocale):
(WebCore::Document::shouldEnforceContentDispositionAttachmentSandbox):
(WebCore::Document::settings): Deleted.

  • dom/Document.h:

(WebCore::Document::settings):

  • dom/Element.cpp:

(WebCore::subpixelMetricsEnabled):

  • dom/ExtensionStyleSheets.cpp:

(WebCore::ExtensionStyleSheets::pageUserSheet):

  • dom/ScriptExecutionContext.cpp:

(WebCore::ScriptExecutionContext::dispatchErrorEvent):

  • dom/ScriptableDocumentParser.cpp:

(WebCore::ScriptableDocumentParser::ScriptableDocumentParser):

  • dom/ScriptedAnimationController.cpp:

(WebCore::ScriptedAnimationController::requestAnimationFrameEnabled):

  • dom/make_names.pl:

(printConstructorInterior):

  • editing/Editor.cpp:
  • editing/markup.cpp:

(WebCore::createMarkupInternal):

  • html/FTPDirectoryDocument.cpp:

(WebCore::createTemplateDocumentData):

  • html/HTMLAnchorElement.cpp:

(WebCore::HTMLAnchorElement::setActive):
(WebCore::HTMLAnchorElement::sendPings):
(WebCore::HTMLAnchorElement::treatLinkAsLiveForEventType):

  • html/HTMLAppletElement.cpp:

(WebCore::HTMLAppletElement::canEmbedJava):

  • html/HTMLCanvasElement.cpp:

(WebCore::HTMLCanvasElement::getContext2d):
(WebCore::shouldEnableWebGL):
(WebCore::HTMLCanvasElement::shouldAccelerate):

  • html/HTMLFormElement.cpp:

(WebCore::HTMLFormElement::submitImplicitly):

  • html/HTMLFrameElementBase.cpp:

(WebCore::HTMLFrameElementBase::setLocation):

  • html/HTMLImageElement.cpp:

(WebCore::HTMLImageElement::updateImageControls):

  • html/HTMLImageLoader.cpp:

(WebCore::HTMLImageLoader::sourceURI):

  • html/HTMLInputElement.cpp:

(WebCore::HTMLInputElement::setupDateTimeChooserParameters):

  • html/HTMLLinkElement.cpp:

(WebCore::HTMLLinkElement::process):

  • html/HTMLMediaElement.cpp:

(WebCore::HTMLMediaElement::HTMLMediaElement):
(WebCore::HTMLMediaElement::mediaPlayerMediaKeysStorageDirectory):
(WebCore::HTMLMediaElement::mediaPlayerAcceleratedCompositingEnabled):
(WebCore::HTMLMediaElement::enterFullscreen):
(WebCore::HTMLMediaElement::exitFullscreen):
(WebCore::HTMLMediaElement::mediaPlayerNeedsSiteSpecificHacks):
(WebCore::HTMLMediaElement::mediaPlayerNetworkInterfaceName):
(WebCore::HTMLMediaElement::setMediaControlsDependOnPageScaleFactor):
(WebCore::HTMLMediaElement::doesHaveAttribute):

  • html/HTMLVideoElement.cpp:

(WebCore::HTMLVideoElement::HTMLVideoElement):

  • html/MediaElementSession.cpp:

(WebCore::MediaElementSession::wirelessVideoPlaybackDisabled):
(WebCore::MediaElementSession::requiresFullscreenForVideoPlayback):
(WebCore::MediaElementSession::allowsAutomaticMediaDataLoading):
(WebCore::MediaElementSession::allowsPictureInPicture):
(WebCore::MediaElementSession::maximumMediaSourceBufferSize):

  • html/parser/HTMLParserOptions.cpp:

(WebCore::HTMLParserOptions::HTMLParserOptions):

  • html/shadow/MediaControlElements.cpp:

(WebCore::MediaControlFullscreenButtonElement::defaultEventHandler):

  • loader/LinkLoader.cpp:

(WebCore::LinkLoader::loadLink):

  • page/csp/ContentSecurityPolicy.cpp:

(WebCore::ContentSecurityPolicy::allowContentSecurityPolicySourceStarToMatchAnyProtocol):

  • rendering/SimpleLineLayout.cpp:

(WebCore::SimpleLineLayout::toggleSimpleLineLayout):

  • style/StyleFontSizeFunctions.cpp:

(WebCore::Style::computedFontSizeFromSpecifiedSize):
(WebCore::Style::fontSizeForKeyword):
(WebCore::Style::legacyFontSizeForPixelSize):

  • style/StyleScope.cpp:

(WebCore::Style::Scope::collectActiveStyleSheets):

  • xml/XMLHttpRequest.cpp:

(WebCore::XMLHttpRequest::usesDashboardBackwardCompatibilityMode):

File size: 13.6 KB
Line 
1/*
2 * Copyright (C) 2014 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "YouTubePluginReplacement.h"
28
29#include "HTMLIFrameElement.h"
30#include "HTMLNames.h"
31#include "HTMLParserIdioms.h"
32#include "HTMLPlugInElement.h"
33#include "RenderElement.h"
34#include "Settings.h"
35#include "ShadowRoot.h"
36#include "YouTubeEmbedShadowElement.h"
37#include <wtf/text/StringBuilder.h>
38
39namespace WebCore {
40
41void YouTubePluginReplacement::registerPluginReplacement(PluginReplacementRegistrar registrar)
42{
43    registrar(ReplacementPlugin(create, supportsMimeType, supportsFileExtension, supportsURL, isEnabledBySettings));
44}
45
46Ref<PluginReplacement> YouTubePluginReplacement::create(HTMLPlugInElement& plugin, const Vector<String>& paramNames, const Vector<String>& paramValues)
47{
48    return adoptRef(*new YouTubePluginReplacement(plugin, paramNames, paramValues));
49}
50
51bool YouTubePluginReplacement::supportsMimeType(const String& mimeType)
52{
53    return equalLettersIgnoringASCIICase(mimeType, "application/x-shockwave-flash")
54        || equalLettersIgnoringASCIICase(mimeType, "application/futuresplash");
55}
56
57bool YouTubePluginReplacement::supportsFileExtension(const String& extension)
58{
59    return equalLettersIgnoringASCIICase(extension, "spl") || equalLettersIgnoringASCIICase(extension, "swf");
60}
61
62YouTubePluginReplacement::YouTubePluginReplacement(HTMLPlugInElement& plugin, const Vector<String>& paramNames, const Vector<String>& paramValues)
63    : m_parentElement(&plugin)
64{
65    ASSERT(paramNames.size() == paramValues.size());
66    for (size_t i = 0; i < paramNames.size(); ++i)
67        m_attributes.add(paramNames[i], paramValues[i]);
68}
69
70RenderPtr<RenderElement> YouTubePluginReplacement::createElementRenderer(HTMLPlugInElement& plugin, RenderStyle&& style, const RenderTreePosition& insertionPosition)
71{
72    ASSERT_UNUSED(plugin, m_parentElement == &plugin);
73
74    if (!m_embedShadowElement)
75        return nullptr;
76   
77    return m_embedShadowElement->createElementRenderer(WTFMove(style), insertionPosition);
78}
79
80bool YouTubePluginReplacement::installReplacement(ShadowRoot& root)
81{
82    m_embedShadowElement = YouTubeEmbedShadowElement::create(m_parentElement->document());
83
84    root.appendChild(*m_embedShadowElement);
85
86    auto iframeElement = HTMLIFrameElement::create(HTMLNames::iframeTag, m_parentElement->document());
87    if (m_attributes.contains("width"))
88        iframeElement->setAttributeWithoutSynchronization(HTMLNames::widthAttr, AtomicString("100%", AtomicString::ConstructFromLiteral));
89   
90    const auto& heightValue = m_attributes.find("height");
91    if (heightValue != m_attributes.end()) {
92        iframeElement->setAttribute(HTMLNames::styleAttr, AtomicString("max-height: 100%", AtomicString::ConstructFromLiteral));
93        iframeElement->setAttributeWithoutSynchronization(HTMLNames::heightAttr, heightValue->value);
94    }
95
96    iframeElement->setAttributeWithoutSynchronization(HTMLNames::srcAttr, youTubeURL(m_attributes.get("src")));
97    iframeElement->setAttributeWithoutSynchronization(HTMLNames::frameborderAttr, AtomicString("0", AtomicString::ConstructFromLiteral));
98   
99    // Disable frame flattening for this iframe.
100    iframeElement->setAttributeWithoutSynchronization(HTMLNames::scrollingAttr, AtomicString("no", AtomicString::ConstructFromLiteral));
101    m_embedShadowElement->appendChild(iframeElement);
102
103    return true;
104}
105   
106static inline URL createYouTubeURL(const String& videoID, const String& timeID)
107{
108    ASSERT(!videoID.isEmpty());
109    ASSERT(videoID != "/");
110   
111    URL result(URL(), "youtube:" + videoID);
112    if (!timeID.isEmpty())
113        result.setQuery("t=" + timeID);
114   
115    return result;
116}
117   
118static YouTubePluginReplacement::KeyValueMap queryKeysAndValues(const String& queryString)
119{
120    YouTubePluginReplacement::KeyValueMap queryDictionary;
121   
122    size_t queryLength = queryString.length();
123    if (!queryLength)
124        return queryDictionary;
125   
126    size_t equalSearchLocation = 0;
127    size_t equalSearchLength = queryLength;
128   
129    while (equalSearchLocation < queryLength - 1 && equalSearchLength) {
130       
131        // Search for "=".
132        size_t equalLocation = queryString.find('=', equalSearchLocation);
133        if (equalLocation == notFound)
134            break;
135       
136        size_t indexAfterEqual = equalLocation + 1;
137        if (indexAfterEqual > queryLength - 1)
138            break;
139       
140        // Get the key before the "=".
141        size_t keyLocation = equalSearchLocation;
142        size_t keyLength = equalLocation - equalSearchLocation;
143       
144        // Seach for the ampersand.
145        size_t ampersandLocation = queryString.find('&', indexAfterEqual);
146       
147        // Get the value after the "=", before the ampersand.
148        size_t valueLocation = indexAfterEqual;
149        size_t valueLength;
150        if (ampersandLocation != notFound)
151            valueLength = ampersandLocation - indexAfterEqual;
152        else
153            valueLength = queryLength - indexAfterEqual;
154       
155        // Save the key and the value.
156        if (keyLength && valueLength) {
157            String key = queryString.substring(keyLocation, keyLength).convertToASCIILowercase();
158            String value = queryString.substring(valueLocation, valueLength);
159            value.replace('+', ' ');
160
161            if (!key.isEmpty() && !value.isEmpty())
162                queryDictionary.add(key, value);
163        }
164       
165        if (ampersandLocation == notFound)
166            break;
167       
168        // Continue searching after the ampersand.
169        size_t indexAfterAmpersand = ampersandLocation + 1;
170        equalSearchLocation = indexAfterAmpersand;
171        equalSearchLength = queryLength - indexAfterAmpersand;
172    }
173   
174    return queryDictionary;
175}
176   
177static bool hasCaseInsensitivePrefix(const String& input, const String& prefix)
178{
179    return input.startsWith(prefix, false);
180}
181   
182static bool isYouTubeURL(const URL& url)
183{
184    String hostName = url.host();
185    return equalLettersIgnoringASCIICase(hostName, "m.youtube.com")
186        || equalLettersIgnoringASCIICase(hostName, "youtu.be")
187        || equalLettersIgnoringASCIICase(hostName, "www.youtube.com")
188        || equalLettersIgnoringASCIICase(hostName, "youtube.com")
189        || equalLettersIgnoringASCIICase(hostName, "www.youtube-nocookie.com")
190        || equalLettersIgnoringASCIICase(hostName, "youtube-nocookie.com");
191}
192
193static const String& valueForKey(const YouTubePluginReplacement::KeyValueMap& dictionary, const String& key)
194{
195    const auto& value = dictionary.find(key);
196    if (value == dictionary.end())
197        return emptyString();
198
199    return value->value;
200}
201
202static URL processAndCreateYouTubeURL(const URL& url, bool& isYouTubeShortenedURL, String& outPathAfterFirstAmpersand)
203{
204    if (!url.protocolIsInHTTPFamily())
205        return URL();
206
207    // Bail out early if we aren't even on www.youtube.com or youtube.com.
208    if (!isYouTubeURL(url))
209        return URL();
210
211    String hostName = url.host();
212    bool isYouTubeMobileWebAppURL = equalLettersIgnoringASCIICase(hostName, "m.youtube.com");
213    isYouTubeShortenedURL = equalLettersIgnoringASCIICase(hostName, "youtu.be");
214
215    // Short URL of the form: http://youtu.be/v1d301D
216    if (isYouTubeShortenedURL) {
217        String videoID = url.lastPathComponent();
218        if (videoID.isEmpty() || videoID == "/")
219            return URL();
220        return createYouTubeURL(videoID, emptyString());
221    }
222   
223    String path = url.path();
224    String query = url.query();
225    String fragment = url.fragmentIdentifier();
226   
227    // On the YouTube mobile web app, the path and query string are put into the
228    // fragment so that one web page is only ever loaded (see <rdar://problem/9550639>).
229    if (isYouTubeMobileWebAppURL) {
230        size_t location = fragment.find('?');
231        if (location == notFound) {
232            path = fragment;
233            query = emptyString();
234        } else {
235            path = fragment.substring(0, location);
236            query = fragment.substring(location + 1);
237        }
238        fragment = emptyString();
239    }
240   
241    if (equalLettersIgnoringASCIICase(path, "/watch")) {
242        if (!query.isEmpty()) {
243            const auto& queryDictionary = queryKeysAndValues(query);
244            String videoID = valueForKey(queryDictionary, "v");
245           
246            if (!videoID.isEmpty()) {
247                const auto& fragmentDictionary = queryKeysAndValues(url.fragmentIdentifier());
248                String timeID = valueForKey(fragmentDictionary, "t");
249                return createYouTubeURL(videoID, timeID);
250            }
251        }
252       
253        // May be a new-style link (see <rdar://problem/7733692>).
254        if (fragment.startsWith('!')) {
255            query = fragment.substring(1);
256           
257            if (!query.isEmpty()) {
258                const auto& queryDictionary = queryKeysAndValues(query);
259                String videoID = valueForKey(queryDictionary, "v");
260               
261                if (!videoID.isEmpty()) {
262                    String timeID = valueForKey(queryDictionary, "t");
263                    return createYouTubeURL(videoID, timeID);
264                }
265            }
266        }
267    } else if (hasCaseInsensitivePrefix(path, "/v/") || hasCaseInsensitivePrefix(path, "/e/")) {
268        String lastPathComponent = url.lastPathComponent();
269        String videoID;
270        String pathAfterFirstAmpersand;
271
272        size_t ampersandLocation = lastPathComponent.find('&');
273        if (ampersandLocation != notFound) {
274            // Some URLs we care about use & in place of ? for the first query parameter.
275            videoID = lastPathComponent.substring(0, ampersandLocation);
276            pathAfterFirstAmpersand = lastPathComponent.substring(ampersandLocation + 1, lastPathComponent.length() - ampersandLocation);
277        } else
278            videoID = lastPathComponent;
279
280        if (!videoID.isEmpty()) {
281            outPathAfterFirstAmpersand = pathAfterFirstAmpersand;
282            return createYouTubeURL(videoID, emptyString());
283        }
284    }
285   
286    return URL();
287}
288
289String YouTubePluginReplacement::youTubeURL(const String& srcString)
290{
291    URL srcURL = m_parentElement->document().completeURL(stripLeadingAndTrailingHTMLSpaces(srcString));
292    return youTubeURLFromAbsoluteURL(srcURL, srcString);
293}
294
295String YouTubePluginReplacement::youTubeURLFromAbsoluteURL(const URL& srcURL, const String& srcString)
296{
297    bool isYouTubeShortenedURL = false;
298    String possibleMalformedQuery;
299    URL youTubeURL = processAndCreateYouTubeURL(srcURL, isYouTubeShortenedURL, possibleMalformedQuery);
300    if (srcURL.isEmpty() || youTubeURL.isEmpty())
301        return srcString;
302
303    // Transform the youtubeURL (youtube:VideoID) to iframe embed url which has the format: http://www.youtube.com/embed/VideoID
304    const String& srcPath = srcURL.path();
305    const String& videoID = youTubeURL.string().substring(youTubeURL.protocol().length() + 1);
306    size_t locationOfVideoIDInPath = srcPath.find(videoID);
307
308    size_t locationOfPathBeforeVideoID = notFound;
309    if (locationOfVideoIDInPath != notFound) {
310        ASSERT(locationOfVideoIDInPath);
311   
312        // From the original URL, we need to get the part before /path/VideoId.
313        locationOfPathBeforeVideoID = srcString.find(srcPath.substring(0, locationOfVideoIDInPath));
314    } else if (equalLettersIgnoringASCIICase(srcPath, "/watch")) {
315        // From the original URL, we need to get the part before /watch/#!v=VideoID
316        // FIXME: Shouldn't this be ASCII case-insensitive?
317        locationOfPathBeforeVideoID = srcString.find("/watch");
318    } else
319        return srcString;
320
321    ASSERT(locationOfPathBeforeVideoID != notFound);
322
323    const String& srcURLPrefix = srcString.substring(0, locationOfPathBeforeVideoID);
324    String query = srcURL.query();
325    // If the URL has no query, use the possibly malformed query we found.
326    if (query.isEmpty())
327        query = possibleMalformedQuery;
328
329    // Append the query string if it is valid.
330    StringBuilder finalURL;
331    if (isYouTubeShortenedURL)
332        finalURL.appendLiteral("http://www.youtube.com");
333    else
334        finalURL.append(srcURLPrefix);
335    finalURL.appendLiteral("/embed/");
336    finalURL.append(videoID);
337    if (!query.isEmpty()) {
338        finalURL.append('?');
339        finalURL.append(query);
340    }
341    return finalURL.toString();
342}
343   
344bool YouTubePluginReplacement::supportsURL(const URL& url)
345{
346    return isYouTubeURL(url);
347}
348
349bool YouTubePluginReplacement::isEnabledBySettings(const Settings& settings)
350{
351    return settings.youTubeFlashPluginReplacementEnabled();
352}
353   
354}
Note: See TracBrowser for help on using the repository browser.