summaryrefslogtreecommitdiff
path: root/Source/WebCore/html/HTMLPlugInImageElement.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/html/HTMLPlugInImageElement.cpp')
-rw-r--r--Source/WebCore/html/HTMLPlugInImageElement.cpp561
1 files changed, 279 insertions, 282 deletions
diff --git a/Source/WebCore/html/HTMLPlugInImageElement.cpp b/Source/WebCore/html/HTMLPlugInImageElement.cpp
index 18ef20628..3fded5cce 100644
--- a/Source/WebCore/html/HTMLPlugInImageElement.cpp
+++ b/Source/WebCore/html/HTMLPlugInImageElement.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008, 2011, 2012 Apple Inc. All rights reserved.
+ * Copyright (C) 2008-2017 Apple Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
@@ -23,104 +23,76 @@
#include "Chrome.h"
#include "ChromeClient.h"
-#include "Event.h"
-#include "EventHandler.h"
-#include "Frame.h"
-#include "FrameLoader.h"
+#include "CommonVM.h"
+#include "ContentSecurityPolicy.h"
+#include "EventNames.h"
#include "FrameLoaderClient.h"
-#include "FrameView.h"
#include "HTMLImageLoader.h"
-#include "JSDocumentFragment.h"
+#include "JSShadowRoot.h"
#include "LocalizedStrings.h"
#include "Logging.h"
#include "MainFrame.h"
#include "MouseEvent.h"
-#include "NodeList.h"
-#include "NodeRenderStyle.h"
#include "Page.h"
#include "PlugInClient.h"
#include "PluginViewBase.h"
-#include "RenderEmbeddedObject.h"
#include "RenderImage.h"
#include "RenderSnapshottedPlugIn.h"
+#include "RenderTreeUpdater.h"
#include "SchemeRegistry.h"
#include "ScriptController.h"
#include "SecurityOrigin.h"
#include "Settings.h"
#include "ShadowRoot.h"
-#include "StyleResolver.h"
+#include "StyleTreeResolver.h"
#include "SubframeLoader.h"
-#include <JavaScriptCore/APICast.h>
-#include <JavaScriptCore/JSBase.h>
-#include <wtf/HashMap.h>
-#include <wtf/text/StringHash.h>
-
-#if PLATFORM(IOS)
-#include "HTMLIFrameElement.h"
-#include "RenderBlockFlow.h"
-#include "YouTubeEmbedShadowElement.h"
-#endif
+#include "TypedElementDescendantIterator.h"
namespace WebCore {
-using namespace HTMLNames;
-
-typedef Vector<RefPtr<HTMLPlugInImageElement>> HTMLPlugInImageElementList;
-typedef HashMap<String, String> MimeTypeToLocalizedStringMap;
-
static const int sizingTinyDimensionThreshold = 40;
static const float sizingFullPageAreaRatioThreshold = 0.96;
static const float autostartSoonAfterUserGestureThreshold = 5.0;
// This delay should not exceed the snapshot delay in PluginView.cpp
-static const double simulatedMouseClickTimerDelay = .75;
-static const double removeSnapshotTimerDelay = 1.5;
+static const auto simulatedMouseClickTimerDelay = std::chrono::milliseconds { 750 };
-static const String titleText(Page* page, String mimeType)
-{
- DEFINE_STATIC_LOCAL(MimeTypeToLocalizedStringMap, mimeTypeToLabelTitleMap, ());
- String titleText = mimeTypeToLabelTitleMap.get(mimeType);
- if (!titleText.isEmpty())
- return titleText;
+#if PLATFORM(COCOA)
+static const auto removeSnapshotTimerDelay = std::chrono::milliseconds { 1500 };
+#endif
- titleText = page->chrome().client().plugInStartLabelTitle(mimeType);
- if (titleText.isEmpty())
- titleText = snapshottedPlugInLabelTitle();
- mimeTypeToLabelTitleMap.set(mimeType, titleText);
- return titleText;
+static const String titleText(Page& page, const String& mimeType)
+{
+ // FIXME: It's not consistent to get a string from the page's chrome client, but then cache it globally.
+ // If it's global, it should come from elsewhere. If it's per-page then it should be cached per page.
+ static NeverDestroyed<HashMap<String, String>> mimeTypeToLabelTitleMap;
+ return mimeTypeToLabelTitleMap.get().ensure(mimeType, [&] {
+ auto title = page.chrome().client().plugInStartLabelTitle(mimeType);
+ if (!title.isEmpty())
+ return title;
+ return snapshottedPlugInLabelTitle();
+ }).iterator->value;
};
-static const String subtitleText(Page* page, String mimeType)
-{
- DEFINE_STATIC_LOCAL(MimeTypeToLocalizedStringMap, mimeTypeToLabelSubtitleMap, ());
- String subtitleText = mimeTypeToLabelSubtitleMap.get(mimeType);
- if (!subtitleText.isEmpty())
- return subtitleText;
-
- subtitleText = page->chrome().client().plugInStartLabelSubtitle(mimeType);
- if (subtitleText.isEmpty())
- subtitleText = snapshottedPlugInLabelSubtitle();
- mimeTypeToLabelSubtitleMap.set(mimeType, subtitleText);
- return subtitleText;
+static const String subtitleText(Page& page, const String& mimeType)
+{
+ // FIXME: It's not consistent to get a string from the page's chrome client, but then cache it globally.
+ // If it's global, it should come from elsewhere. If it's per-page then it should be cached per page.
+ static NeverDestroyed<HashMap<String, String>> mimeTypeToLabelSubtitleMap;
+ return mimeTypeToLabelSubtitleMap.get().ensure(mimeType, [&] {
+ auto subtitle = page.chrome().client().plugInStartLabelSubtitle(mimeType);
+ if (!subtitle.isEmpty())
+ return subtitle;
+ return snapshottedPlugInLabelSubtitle();
+ }).iterator->value;
};
-HTMLPlugInImageElement::HTMLPlugInImageElement(const QualifiedName& tagName, Document& document, bool createdByParser, PreferPlugInsForImagesOption preferPlugInsForImagesOption)
+HTMLPlugInImageElement::HTMLPlugInImageElement(const QualifiedName& tagName, Document& document, bool createdByParser)
: HTMLPlugInElement(tagName, document)
- // m_needsWidgetUpdate(!createdByParser) allows HTMLObjectElement to delay
- // widget updates until after all children are parsed. For HTMLEmbedElement
- // this delay is unnecessary, but it is simpler to make both classes share
- // the same codepath in this class.
- , m_needsWidgetUpdate(!createdByParser)
- , m_shouldPreferPlugInsForImages(preferPlugInsForImagesOption == ShouldPreferPlugInsForImages)
- , m_needsDocumentActivationCallbacks(false)
- , m_simulatedMouseClickTimer(this, &HTMLPlugInImageElement::simulatedMouseClickTimerFired, simulatedMouseClickTimerDelay)
- , m_removeSnapshotTimer(this, &HTMLPlugInImageElement::removeSnapshotTimerFired)
+ , m_needsWidgetUpdate(!createdByParser) // Set true in finishParsingChildren.
+ , m_simulatedMouseClickTimer(*this, &HTMLPlugInImageElement::simulatedMouseClickTimerFired, simulatedMouseClickTimerDelay)
+ , m_removeSnapshotTimer(*this, &HTMLPlugInImageElement::removeSnapshotTimerFired)
, m_createdDuringUserGesture(ScriptController::processingUserGesture())
- , m_isRestartedPlugin(false)
- , m_needsCheckForSizeChange(false)
- , m_plugInWasCreated(false)
- , m_deferredPromotionToPrimaryPlugIn(false)
- , m_snapshotDecision(SnapshotNotYetDecided)
{
setHasCustomStyleResolveCallbacks();
}
@@ -128,16 +100,16 @@ HTMLPlugInImageElement::HTMLPlugInImageElement(const QualifiedName& tagName, Doc
HTMLPlugInImageElement::~HTMLPlugInImageElement()
{
if (m_needsDocumentActivationCallbacks)
- document().unregisterForPageCacheSuspensionCallbacks(this);
+ document().unregisterForDocumentSuspensionCallbacks(this);
}
void HTMLPlugInImageElement::setDisplayState(DisplayState state)
{
-#if PLATFORM(MAC)
+#if PLATFORM(COCOA)
if (state == RestartingWithPendingMouseClick || state == Restarting) {
m_isRestartedPlugin = true;
m_snapshotDecision = NeverSnapshot;
- setNeedsStyleRecalc(SyntheticStyleChange);
+ invalidateStyleAndLayerComposition();
if (displayState() == DisplayingSnapshot)
m_removeSnapshotTimer.startOneShot(removeSnapshotTimerDelay);
}
@@ -148,11 +120,8 @@ void HTMLPlugInImageElement::setDisplayState(DisplayState state)
RenderEmbeddedObject* HTMLPlugInImageElement::renderEmbeddedObject() const
{
- // HTMLObjectElement and HTMLEmbedElement may return arbitrary renderers
- // when using fallback content.
- if (!renderer() || !renderer()->isEmbeddedObject())
- return 0;
- return toRenderEmbeddedObject(renderer());
+ // HTMLObjectElement and HTMLEmbedElement may return arbitrary renderers when using fallback content.
+ return is<RenderEmbeddedObject>(renderer()) ? downcast<RenderEmbeddedObject>(renderer()) : nullptr;
}
bool HTMLPlugInImageElement::isImageType()
@@ -160,112 +129,114 @@ bool HTMLPlugInImageElement::isImageType()
if (m_serviceType.isEmpty() && protocolIs(m_url, "data"))
m_serviceType = mimeTypeFromDataURL(m_url);
- if (Frame* frame = document().frame()) {
- URL completedURL = document().completeURL(m_url);
- return frame->loader().client().objectContentType(completedURL, m_serviceType, shouldPreferPlugInsForImages()) == ObjectContentImage;
- }
+ if (auto* frame = document().frame())
+ return frame->loader().client().objectContentType(document().completeURL(m_url), m_serviceType) == ObjectContentType::Image;
return Image::supportsType(m_serviceType);
}
-// We don't use m_url, as it may not be the final URL that the object loads,
-// depending on <param> values.
+// We don't use m_url, as it may not be the final URL that the object loads, depending on <param> values.
bool HTMLPlugInImageElement::allowedToLoadFrameURL(const String& url)
{
URL completeURL = document().completeURL(url);
-
- if (contentFrame() && protocolIsJavaScript(completeURL)
- && !document().securityOrigin()->canAccess(contentDocument()->securityOrigin()))
+ if (contentFrame() && protocolIsJavaScript(completeURL) && !document().securityOrigin().canAccess(contentDocument()->securityOrigin()))
return false;
-
return document().frame()->isURLAllowed(completeURL);
}
// We don't use m_url, or m_serviceType as they may not be the final values
// that <object> uses depending on <param> values.
-bool HTMLPlugInImageElement::wouldLoadAsNetscapePlugin(const String& url, const String& serviceType)
+bool HTMLPlugInImageElement::wouldLoadAsPlugIn(const String& url, const String& serviceType)
{
ASSERT(document().frame());
URL completedURL;
if (!url.isEmpty())
completedURL = document().completeURL(url);
-
- FrameLoader& frameLoader = document().frame()->loader();
- if (frameLoader.client().objectContentType(completedURL, serviceType, shouldPreferPlugInsForImages()) == ObjectContentNetscapePlugin)
- return true;
- return false;
+ return document().frame()->loader().client().objectContentType(completedURL, serviceType) == ObjectContentType::PlugIn;
}
-RenderPtr<RenderElement> HTMLPlugInImageElement::createElementRenderer(PassRef<RenderStyle> style)
+RenderPtr<RenderElement> HTMLPlugInImageElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition& insertionPosition)
{
- ASSERT(!document().inPageCache());
+ ASSERT(document().pageCacheState() == Document::NotInPageCache);
if (displayState() >= PreparingPluginReplacement)
- return HTMLPlugInElement::createElementRenderer(std::move(style));
+ return HTMLPlugInElement::createElementRenderer(WTFMove(style), insertionPosition);
- // Once a PlugIn Element creates its renderer, it needs to be told when the Document goes
+ // Once a plug-in element creates its renderer, it needs to be told when the document goes
// inactive or reactivates so it can clear the renderer before going into the page cache.
if (!m_needsDocumentActivationCallbacks) {
m_needsDocumentActivationCallbacks = true;
- document().registerForPageCacheSuspensionCallbacks(this);
+ document().registerForDocumentSuspensionCallbacks(this);
}
if (displayState() == DisplayingSnapshot) {
- auto renderSnapshottedPlugIn = createRenderer<RenderSnapshottedPlugIn>(*this, std::move(style));
- renderSnapshottedPlugIn->updateSnapshot(m_snapshotImage);
- return std::move(renderSnapshottedPlugIn);
+ auto renderSnapshottedPlugIn = createRenderer<RenderSnapshottedPlugIn>(*this, WTFMove(style));
+ renderSnapshottedPlugIn->updateSnapshot(m_snapshotImage.get());
+ return WTFMove(renderSnapshottedPlugIn);
}
- // Fallback content breaks the DOM->Renderer class relationship of this
- // class and all superclasses because createObject won't necessarily
- // return a RenderEmbeddedObject or RenderWidget.
if (useFallbackContent())
- return RenderElement::createFor(*this, std::move(style));
+ return RenderElement::createFor(*this, WTFMove(style));
if (isImageType())
- return createRenderer<RenderImage>(*this, std::move(style));
+ return createRenderer<RenderImage>(*this, WTFMove(style));
-#if PLATFORM(IOS)
- if (ShadowRoot* shadowRoot = this->shadowRoot()) {
- Element* shadowElement = toElement(shadowRoot->firstChild());
- if (shadowElement && shadowElement->shadowPseudoId() == "-apple-youtube-shadow-iframe")
- return createRenderer<RenderBlockFlow>(*this, std::move(style));
- }
-#endif
- return HTMLPlugInElement::createElementRenderer(std::move(style));
+ return HTMLPlugInElement::createElementRenderer(WTFMove(style), insertionPosition);
}
-bool HTMLPlugInImageElement::willRecalcStyle(Style::Change)
+bool HTMLPlugInImageElement::childShouldCreateRenderer(const Node& child) const
{
+ if (is<RenderSnapshottedPlugIn>(renderer()) && !hasShadowRootParent(child))
+ return false;
+
+ return HTMLPlugInElement::childShouldCreateRenderer(child);
+}
+
+void HTMLPlugInImageElement::willRecalcStyle(Style::Change change)
+{
+ // Make sure style recalcs scheduled by a child shadow tree don't trigger reconstruction and cause flicker.
+ if (change == Style::NoChange && styleValidity() == Style::Validity::Valid)
+ return;
+
// FIXME: There shoudn't be need to force render tree reconstruction here.
// It is only done because loading and load event dispatching is tied to render tree construction.
if (!useFallbackContent() && needsWidgetUpdate() && renderer() && !isImageType() && (displayState() != DisplayingSnapshot))
- setNeedsStyleRecalc(ReconstructRenderTree);
- return true;
+ invalidateStyleAndRenderersForSubtree();
}
void HTMLPlugInImageElement::didAttachRenderers()
{
if (!isImageType()) {
- queuePostAttachCallback(&HTMLPlugInImageElement::updateWidgetCallback, *this);
+ RefPtr<HTMLPlugInImageElement> element = this;
+ Style::queuePostResolutionCallback([element]{
+ element->updateWidgetIfNecessary();
+ });
return;
}
if (!renderer() || useFallbackContent())
return;
- // Image load might complete synchronously and cause us to re-enter attach.
- queuePostAttachCallback(&HTMLPlugInImageElement::startLoadingImageCallback, *this);
+ // Image load might complete synchronously and cause us to re-enter.
+ RefPtr<HTMLPlugInImageElement> element = this;
+ Style::queuePostResolutionCallback([element]{
+ element->startLoadingImage();
+ });
}
void HTMLPlugInImageElement::willDetachRenderers()
{
// FIXME: Because of the insanity that is HTMLPlugInImageElement::willRecalcStyle,
// we can end up detaching during an attach() call, before we even have a
- // renderer. In that case, don't mark the widget for update.
+ // renderer. In that case, don't mark the widget for update.
if (renderer() && !useFallbackContent()) {
// Update the widget the next time we attach (detaching destroys the plugin).
setNeedsWidgetUpdate(true);
}
+
+ auto* widget = pluginWidget(PluginLoadingPolicy::DoNotLoad);
+ if (is<PluginViewBase>(widget))
+ downcast<PluginViewBase>(*widget).willDetatchRenderer();
+
HTMLPlugInElement::willDetachRenderers();
}
@@ -279,7 +250,7 @@ void HTMLPlugInImageElement::updateWidgetIfNecessary()
if (!renderEmbeddedObject() || renderEmbeddedObject()->isPluginUnavailable())
return;
- updateWidget(CreateOnlyNonNetscapePlugins);
+ updateWidget(CreatePlugins::No);
}
void HTMLPlugInImageElement::finishParsingChildren()
@@ -288,87 +259,71 @@ void HTMLPlugInImageElement::finishParsingChildren()
if (useFallbackContent())
return;
+ // HTMLObjectElement needs to delay widget updates until after all children are parsed,
+ // For HTMLEmbedElement this delay is unnecessary, but there is no harm in doing the same.
setNeedsWidgetUpdate(true);
- if (inDocument())
- setNeedsStyleRecalc();
+ if (isConnected())
+ invalidateStyleForSubtree();
}
-void HTMLPlugInImageElement::didMoveToNewDocument(Document* oldDocument)
+void HTMLPlugInImageElement::didMoveToNewDocument(Document& oldDocument)
{
if (m_needsDocumentActivationCallbacks) {
- if (oldDocument)
- oldDocument->unregisterForPageCacheSuspensionCallbacks(this);
- document().registerForPageCacheSuspensionCallbacks(this);
+ oldDocument.unregisterForDocumentSuspensionCallbacks(this);
+ document().registerForDocumentSuspensionCallbacks(this);
}
if (m_imageLoader)
m_imageLoader->elementDidMoveToNewDocument();
+
HTMLPlugInElement::didMoveToNewDocument(oldDocument);
}
-void HTMLPlugInImageElement::documentWillSuspendForPageCache()
+void HTMLPlugInImageElement::prepareForDocumentSuspension()
{
if (renderer())
- Style::detachRenderTree(*this);
+ RenderTreeUpdater::tearDownRenderers(*this);
- HTMLPlugInElement::documentWillSuspendForPageCache();
+ HTMLPlugInElement::prepareForDocumentSuspension();
}
-void HTMLPlugInImageElement::documentDidResumeFromPageCache()
+void HTMLPlugInImageElement::resumeFromDocumentSuspension()
{
- setNeedsStyleRecalc(ReconstructRenderTree);
+ invalidateStyleAndRenderersForSubtree();
- HTMLPlugInElement::documentDidResumeFromPageCache();
-}
-
-void HTMLPlugInImageElement::updateWidgetCallback(Node& node, unsigned)
-{
- toHTMLPlugInImageElement(node).updateWidgetIfNecessary();
+ HTMLPlugInElement::resumeFromDocumentSuspension();
}
void HTMLPlugInImageElement::startLoadingImage()
{
if (!m_imageLoader)
- m_imageLoader = adoptPtr(new HTMLImageLoader(this));
+ m_imageLoader = std::make_unique<HTMLImageLoader>(*this);
m_imageLoader->updateFromElement();
}
-void HTMLPlugInImageElement::startLoadingImageCallback(Node& node, unsigned)
-{
- toHTMLPlugInImageElement(node).startLoadingImage();
-}
-
-void HTMLPlugInImageElement::updateSnapshot(PassRefPtr<Image> image)
+void HTMLPlugInImageElement::updateSnapshot(Image* image)
{
if (displayState() > DisplayingSnapshot)
return;
m_snapshotImage = image;
- if (renderer()->isSnapshottedPlugIn()) {
- toRenderSnapshottedPlugIn(renderer())->updateSnapshot(image);
+ auto* renderer = this->renderer();
+ if (!renderer)
return;
- }
- if (renderer()->isEmbeddedObject())
- renderer()->repaint();
-}
-
-void HTMLPlugInImageElement::checkSnapshotStatus()
-{
- if (!renderer()->isSnapshottedPlugIn()) {
- if (displayState() == Playing)
- checkSizeChangeForSnapshotting();
+ if (is<RenderSnapshottedPlugIn>(*renderer)) {
+ downcast<RenderSnapshottedPlugIn>(*renderer).updateSnapshot(image);
return;
}
- // Notify the shadow root that the size changed so that we may update the overlay layout.
- ensureUserAgentShadowRoot().dispatchEvent(Event::create(eventNames().resizeEvent, true, false));
+ if (is<RenderEmbeddedObject>(*renderer))
+ renderer->repaint();
}
static DOMWrapperWorld& plugInImageElementIsolatedWorld()
{
- static DOMWrapperWorld& isolatedWorld = *DOMWrapperWorld::create(JSDOMWindow::commonVM()).leakRef();
+ static auto& isolatedWorld = DOMWrapperWorld::create(commonVM()).leakRef();
return isolatedWorld;
}
@@ -378,7 +333,7 @@ void HTMLPlugInImageElement::didAddUserAgentShadowRoot(ShadowRoot* root)
if (displayState() >= PreparingPluginReplacement)
return;
- Page* page = document().page();
+ auto* page = document().page();
if (!page)
return;
@@ -389,96 +344,66 @@ void HTMLPlugInImageElement::didAddUserAgentShadowRoot(ShadowRoot* root)
String mimeType = loadedMimeType();
- DOMWrapperWorld& isolatedWorld = plugInImageElementIsolatedWorld();
+ auto& isolatedWorld = plugInImageElementIsolatedWorld();
document().ensurePlugInsInjectedScript(isolatedWorld);
- ScriptController& scriptController = page->mainFrame().script();
- JSDOMGlobalObject* globalObject = JSC::jsCast<JSDOMGlobalObject*>(scriptController.globalObject(isolatedWorld));
- JSC::ExecState* exec = globalObject->globalExec();
+ auto& scriptController = document().frame()->script();
+ auto& globalObject = *JSC::jsCast<JSDOMGlobalObject*>(scriptController.globalObject(isolatedWorld));
- JSC::JSLockHolder lock(exec);
+ auto& vm = globalObject.vm();
+ JSC::JSLockHolder lock(vm);
+ auto scope = DECLARE_CATCH_SCOPE(vm);
+ auto& state = *globalObject.globalExec();
JSC::MarkedArgumentBuffer argList;
- argList.append(toJS(exec, globalObject, root));
- argList.append(jsString(exec, titleText(page, mimeType)));
- argList.append(jsString(exec, subtitleText(page, mimeType)));
+ argList.append(toJS<IDLInterface<ShadowRoot>>(state, globalObject, root));
+ argList.append(toJS<IDLDOMString>(state, titleText(*page, mimeType)));
+ argList.append(toJS<IDLDOMString>(state, subtitleText(*page, mimeType)));
// This parameter determines whether or not the snapshot overlay should always be visible over the plugin snapshot.
// If no snapshot was found then we want the overlay to be visible.
- argList.append(JSC::jsBoolean(!m_snapshotImage));
+ argList.append(toJS<IDLBoolean>(!m_snapshotImage));
// It is expected the JS file provides a createOverlay(shadowRoot, title, subtitle) function.
- JSC::JSObject* overlay = globalObject->get(exec, JSC::Identifier(exec, "createOverlay")).toObject(exec);
+ auto* overlay = globalObject.get(&state, JSC::Identifier::fromString(&state, "createOverlay")).toObject(&state);
+ if (!overlay) {
+ ASSERT(scope.exception());
+ scope.clearException();
+ return;
+ }
JSC::CallData callData;
- JSC::CallType callType = overlay->methodTable()->getCallData(overlay, callData);
- if (callType == JSC::CallTypeNone)
+ auto callType = overlay->methodTable()->getCallData(overlay, callData);
+ if (callType == JSC::CallType::None)
return;
- JSC::call(exec, overlay, callType, callData, globalObject, argList);
-}
-
-bool HTMLPlugInImageElement::partOfSnapshotOverlay(Node* node)
-{
- DEFINE_STATIC_LOCAL(AtomicString, selector, (".snapshot-overlay", AtomicString::ConstructFromLiteral));
- RefPtr<Element> snapshotLabel = ensureUserAgentShadowRoot().querySelector(selector, ASSERT_NO_EXCEPTION);
- return node && snapshotLabel && (node == snapshotLabel.get() || node->isDescendantOf(snapshotLabel.get()));
+ call(&state, overlay, callType, callData, &globalObject, argList);
+ scope.clearException();
}
-#if PLATFORM(IOS)
-void HTMLPlugInImageElement::createShadowIFrameSubtree(const String& src)
+bool HTMLPlugInImageElement::partOfSnapshotOverlay(const Node* node) const
{
- if (this->shadowRoot())
- return;
-
- if (src.isEmpty())
- return;
-
- RefPtr<YouTubeEmbedShadowElement> shadowElement = YouTubeEmbedShadowElement::create(document());
- ShadowRoot& root = this->ensureUserAgentShadowRoot();
- root.appendChild(shadowElement, ASSERT_NO_EXCEPTION);
-
- RefPtr<HTMLIFrameElement> iframeElement = HTMLIFrameElement::create(HTMLNames::iframeTag, document());
- if (hasAttribute(HTMLNames::widthAttr))
- iframeElement->setAttribute(HTMLNames::widthAttr, AtomicString("100%", AtomicString::ConstructFromLiteral));
- if (hasAttribute(HTMLNames::heightAttr)) {
- iframeElement->setAttribute(HTMLNames::styleAttr, AtomicString("max-height: 100%", AtomicString::ConstructFromLiteral));
- iframeElement->setAttribute(HTMLNames::heightAttr, getAttribute(HTMLNames::heightAttr));
- }
- iframeElement->setAttribute(HTMLNames::srcAttr, src);
- iframeElement->setAttribute(HTMLNames::frameborderAttr, AtomicString("0", AtomicString::ConstructFromLiteral));
-
- // Disable frame flattening for this iframe.
- iframeElement->setAttribute(HTMLNames::scrollingAttr, AtomicString("no", AtomicString::ConstructFromLiteral));
- shadowElement->appendChild(iframeElement, ASSERT_NO_EXCEPTION);
+ static NeverDestroyed<AtomicString> selector(".snapshot-overlay", AtomicString::ConstructFromLiteral);
+ auto* shadow = userAgentShadowRoot();
+ if (!shadow)
+ return false;
+ if (!node)
+ return false;
+ auto queryResult = shadow->querySelector(selector.get());
+ if (queryResult.hasException())
+ return false;
+ auto* snapshotLabel = queryResult.releaseReturnValue();
+ return snapshotLabel && snapshotLabel->contains(node);
}
-#endif
-void HTMLPlugInImageElement::removeSnapshotTimerFired(Timer<HTMLPlugInImageElement>&)
+void HTMLPlugInImageElement::removeSnapshotTimerFired()
{
m_snapshotImage = nullptr;
m_isRestartedPlugin = false;
- setNeedsStyleRecalc(SyntheticStyleChange);
+ invalidateStyleAndLayerComposition();
if (renderer())
renderer()->repaint();
}
-static void addPlugInsFromNodeListMatchingPlugInOrigin(HTMLPlugInImageElementList& plugInList, PassRefPtr<NodeList> collection, const String& plugInOrigin, const String& mimeType)
-{
- for (unsigned i = 0, length = collection->length(); i < length; i++) {
- Node* node = collection->item(i);
- if (node->isPluginElement()) {
- HTMLPlugInElement* plugInElement = toHTMLPlugInElement(node);
- if (plugInElement->isPlugInImageElement()) {
- HTMLPlugInImageElement* plugInImageElement = toHTMLPlugInImageElement(node);
- const URL& loadedURL = plugInImageElement->loadedUrl();
- String otherMimeType = plugInImageElement->loadedMimeType();
- if (plugInOrigin == loadedURL.host() && mimeType == otherMimeType)
- plugInList.append(plugInImageElement);
- }
- }
- }
-}
-
void HTMLPlugInImageElement::restartSimilarPlugIns()
{
// Restart any other snapshotted plugins in the page with the same origin. Note that they
@@ -486,7 +411,7 @@ void HTMLPlugInImageElement::restartSimilarPlugIns()
String plugInOrigin = m_loadedUrl.host();
String mimeType = loadedMimeType();
- HTMLPlugInImageElementList similarPlugins;
+ Vector<Ref<HTMLPlugInImageElement>> similarPlugins;
if (!document().page())
return;
@@ -498,33 +423,29 @@ void HTMLPlugInImageElement::restartSimilarPlugIns()
if (!frame->document())
continue;
- RefPtr<NodeList> plugIns = frame->document()->getElementsByTagName(embedTag.localName());
- if (plugIns)
- addPlugInsFromNodeListMatchingPlugInOrigin(similarPlugins, plugIns, plugInOrigin, mimeType);
-
- plugIns = frame->document()->getElementsByTagName(objectTag.localName());
- if (plugIns)
- addPlugInsFromNodeListMatchingPlugInOrigin(similarPlugins, plugIns, plugInOrigin, mimeType);
+ for (auto& element : descendantsOfType<HTMLPlugInImageElement>(*frame->document())) {
+ if (plugInOrigin == element.loadedUrl().host() && mimeType == element.loadedMimeType())
+ similarPlugins.append(element);
+ }
}
- for (size_t i = 0, length = similarPlugins.size(); i < length; ++i) {
- HTMLPlugInImageElement* plugInToRestart = similarPlugins[i].get();
+ for (auto& plugInToRestart : similarPlugins) {
if (plugInToRestart->displayState() <= HTMLPlugInElement::DisplayingSnapshot) {
- LOG(Plugins, "%p Plug-in looks similar to a restarted plug-in. Restart.", plugInToRestart);
+ LOG(Plugins, "%p Plug-in looks similar to a restarted plug-in. Restart.", plugInToRestart.ptr());
plugInToRestart->restartSnapshottedPlugIn();
}
plugInToRestart->m_snapshotDecision = NeverSnapshot;
}
}
-void HTMLPlugInImageElement::userDidClickSnapshot(PassRefPtr<MouseEvent> event, bool forwardEvent)
+void HTMLPlugInImageElement::userDidClickSnapshot(MouseEvent& event, bool forwardEvent)
{
if (forwardEvent)
- m_pendingClickEventFromSnapshot = event;
+ m_pendingClickEventFromSnapshot = &event;
String plugInOrigin = m_loadedUrl.host();
- if (document().page() && !SchemeRegistry::shouldTreatURLSchemeAsLocal(document().page()->mainFrame().document()->baseURL().protocol()) && document().page()->settings().autostartOriginPlugInSnapshottingEnabled())
- document().page()->plugInClient()->didStartFromOrigin(document().page()->mainFrame().document()->baseURL().host(), plugInOrigin, loadedMimeType());
+ if (document().page() && !SchemeRegistry::shouldTreatURLSchemeAsLocal(document().page()->mainFrame().document()->baseURL().protocol().toStringWithoutCopying()) && document().page()->settings().autostartOriginPlugInSnapshottingEnabled())
+ document().page()->plugInClient()->didStartFromOrigin(document().page()->mainFrame().document()->baseURL().host(), plugInOrigin, loadedMimeType(), document().page()->sessionID());
LOG(Plugins, "%p User clicked on snapshotted plug-in. Restart.", this);
restartSnapshottedPlugIn();
@@ -556,7 +477,7 @@ void HTMLPlugInImageElement::restartSnapshottedPlugIn()
return;
setDisplayState(Restarting);
- setNeedsStyleRecalc(ReconstructRenderTree);
+ invalidateStyleAndRenderersForSubtree();
}
void HTMLPlugInImageElement::dispatchPendingMouseClick()
@@ -565,7 +486,7 @@ void HTMLPlugInImageElement::dispatchPendingMouseClick()
m_simulatedMouseClickTimer.restart();
}
-void HTMLPlugInImageElement::simulatedMouseClickTimerFired(DeferrableOneShotTimer<HTMLPlugInImageElement>&)
+void HTMLPlugInImageElement::simulatedMouseClickTimerFired()
{
ASSERT(displayState() == RestartingWithPendingMouseClick);
ASSERT(m_pendingClickEventFromSnapshot);
@@ -579,14 +500,10 @@ void HTMLPlugInImageElement::simulatedMouseClickTimerFired(DeferrableOneShotTime
static bool documentHadRecentUserGesture(Document& document)
{
double lastKnownUserGestureTimestamp = document.lastHandledUserGestureTimestamp();
-
if (document.frame() != &document.page()->mainFrame() && document.page()->mainFrame().document())
lastKnownUserGestureTimestamp = std::max(lastKnownUserGestureTimestamp, document.page()->mainFrame().document()->lastHandledUserGestureTimestamp());
- if (monotonicallyIncreasingTime() - lastKnownUserGestureTimestamp < autostartSoonAfterUserGestureThreshold)
- return true;
-
- return false;
+ return monotonicallyIncreasingTime() - lastKnownUserGestureTimestamp < autostartSoonAfterUserGestureThreshold;
}
void HTMLPlugInImageElement::checkSizeChangeForSnapshotting()
@@ -595,7 +512,8 @@ void HTMLPlugInImageElement::checkSizeChangeForSnapshotting()
return;
m_needsCheckForSizeChange = false;
- LayoutRect contentBoxRect = toRenderBox(renderer())->contentBoxRect();
+
+ auto contentBoxRect = downcast<RenderBox>(*renderer()).contentBoxRect();
int contentWidth = contentBoxRect.width();
int contentHeight = contentBoxRect.height();
@@ -605,11 +523,66 @@ void HTMLPlugInImageElement::checkSizeChangeForSnapshotting()
LOG(Plugins, "%p Plug-in originally avoided snapshotting because it was sized %dx%d. Now it is %dx%d. Tell it to snapshot.\n", this, m_sizeWhenSnapshotted.width(), m_sizeWhenSnapshotted.height(), contentWidth, contentHeight);
setDisplayState(WaitingForSnapshot);
m_snapshotDecision = Snapshotted;
- Widget* widget = pluginWidget();
- if (widget && widget->isPluginViewBase())
- toPluginViewBase(widget)->beginSnapshottingRunningPlugin();
+ auto* widget = pluginWidget();
+ if (is<PluginViewBase>(widget))
+ downcast<PluginViewBase>(*widget).beginSnapshottingRunningPlugin();
}
+static inline bool is100Percent(Length length)
+{
+ return length.isPercent() && length.percent() == 100;
+}
+
+static inline bool isSmallerThanTinySizingThreshold(const RenderEmbeddedObject& renderer)
+{
+ auto contentRect = renderer.contentBoxRect();
+ return contentRect.width() <= sizingTinyDimensionThreshold || contentRect.height() <= sizingTinyDimensionThreshold;
+}
+
+bool HTMLPlugInImageElement::isTopLevelFullPagePlugin(const RenderEmbeddedObject& renderer) const
+{
+ ASSERT(document().frame());
+ auto& frame = *document().frame();
+ if (!frame.isMainFrame())
+ return false;
+
+ auto& style = renderer.style();
+ auto visibleSize = frame.view()->visibleSize();
+ auto contentRect = renderer.contentBoxRect();
+ float contentWidth = contentRect.width();
+ float contentHeight = contentRect.height();
+ return is100Percent(style.width()) && is100Percent(style.height()) && contentWidth * contentHeight > visibleSize.area().unsafeGet() * sizingFullPageAreaRatioThreshold;
+}
+
+void HTMLPlugInImageElement::checkSnapshotStatus()
+{
+ if (!is<RenderSnapshottedPlugIn>(*renderer())) {
+ if (displayState() == Playing)
+ checkSizeChangeForSnapshotting();
+ return;
+ }
+
+ // If width and height styles were previously not set and we've snapshotted the plugin we may need to restart the plugin so that its state can be updated appropriately.
+ if (!document().page()->settings().snapshotAllPlugIns() && displayState() <= DisplayingSnapshot && !m_plugInDimensionsSpecified) {
+ auto& renderer = downcast<RenderSnapshottedPlugIn>(*this->renderer());
+ if (!renderer.style().logicalWidth().isSpecified() && !renderer.style().logicalHeight().isSpecified())
+ return;
+
+ m_plugInDimensionsSpecified = true;
+ if (isTopLevelFullPagePlugin(renderer)) {
+ m_snapshotDecision = NeverSnapshot;
+ restartSnapshottedPlugIn();
+ } else if (isSmallerThanTinySizingThreshold(renderer)) {
+ m_snapshotDecision = MaySnapshotWhenResized;
+ restartSnapshottedPlugIn();
+ }
+ return;
+ }
+
+ // Notify the shadow root that the size changed so that we may update the overlay layout.
+ ensureUserAgentShadowRoot().dispatchEvent(Event::create(eventNames().resizeEvent, true, false));
+}
+
void HTMLPlugInImageElement::subframeLoaderWillCreatePlugIn(const URL& url)
{
LOG(Plugins, "%p Plug-in URL: %s", this, m_url.utf8().data());
@@ -688,33 +661,28 @@ void HTMLPlugInImageElement::subframeLoaderWillCreatePlugIn(const URL& url)
return;
}
- if (!SchemeRegistry::shouldTreatURLSchemeAsLocal(m_loadedUrl.protocol()) && !m_loadedUrl.host().isEmpty() && m_loadedUrl.host() == document().page()->mainFrame().document()->baseURL().host()) {
+ if (!SchemeRegistry::shouldTreatURLSchemeAsLocal(m_loadedUrl.protocol().toStringWithoutCopying()) && !m_loadedUrl.host().isEmpty() && m_loadedUrl.host() == document().page()->mainFrame().document()->baseURL().host()) {
LOG(Plugins, "%p Plug-in is served from page's domain, set to play", this);
m_snapshotDecision = NeverSnapshot;
return;
}
-
- RenderBox* renderEmbeddedObject = toRenderBox(renderer());
- Length styleWidth = renderEmbeddedObject->style().width();
- Length styleHeight = renderEmbeddedObject->style().height();
- LayoutRect contentBoxRect = renderEmbeddedObject->contentBoxRect();
- int contentWidth = contentBoxRect.width();
- int contentHeight = contentBoxRect.height();
- int contentArea = contentWidth * contentHeight;
- IntSize visibleViewSize = document().frame()->view()->visibleSize();
- int visibleArea = visibleViewSize.width() * visibleViewSize.height();
-
- if (inMainFrame && styleWidth.isPercent() && (styleWidth.percent() == 100)
- && styleHeight.isPercent() && (styleHeight.percent() == 100)
- && (static_cast<float>(contentArea) / visibleArea > sizingFullPageAreaRatioThreshold)) {
+
+ auto& renderer = downcast<RenderEmbeddedObject>(*this->renderer());
+ auto contentRect = renderer.contentBoxRect();
+ int contentWidth = contentRect.width();
+ int contentHeight = contentRect.height();
+
+ m_plugInDimensionsSpecified = renderer.style().logicalWidth().isSpecified() || renderer.style().logicalHeight().isSpecified();
+
+ if (isTopLevelFullPagePlugin(renderer)) {
LOG(Plugins, "%p Plug-in is top level full page, set to play", this);
m_snapshotDecision = NeverSnapshot;
return;
}
- if (contentWidth <= sizingTinyDimensionThreshold || contentHeight <= sizingTinyDimensionThreshold) {
+ if (isSmallerThanTinySizingThreshold(renderer)) {
LOG(Plugins, "%p Plug-in is very small %dx%d, set to play", this, contentWidth, contentHeight);
- m_sizeWhenSnapshotted = IntSize(contentBoxRect.width().toInt(), contentBoxRect.height().toInt());
+ m_sizeWhenSnapshotted = IntSize(contentWidth, contentHeight);
m_snapshotDecision = MaySnapshotWhenResized;
return;
}
@@ -726,16 +694,16 @@ void HTMLPlugInImageElement::subframeLoaderWillCreatePlugIn(const URL& url)
return;
}
- LOG(Plugins, "%p Plug-in from (%s, %s) is not auto-start, sized at %dx%d, set to wait for snapshot", this, document().page()->mainFrame().document()->baseURL().host().utf8().data(), url.host().utf8().data(), contentWidth, contentHeight);
+ LOG(Plugins, "%p Plug-in from (%s, %s) is not auto-start, sized at %dx%d, set to wait for snapshot", this, document().topDocument().baseURL().host().utf8().data(), url.host().utf8().data(), contentWidth, contentHeight);
m_snapshotDecision = Snapshotted;
setDisplayState(WaitingForSnapshot);
}
-void HTMLPlugInImageElement::subframeLoaderDidCreatePlugIn(const Widget* widget)
+void HTMLPlugInImageElement::subframeLoaderDidCreatePlugIn(const Widget& widget)
{
m_plugInWasCreated = true;
- if (widget->isPluginViewBase() && toPluginViewBase(widget)->shouldAlwaysAutoStart()) {
+ if (is<PluginViewBase>(widget) && downcast<PluginViewBase>(widget).shouldAlwaysAutoStart()) {
LOG(Plugins, "%p Plug-in should auto-start, set to play", this);
m_snapshotDecision = NeverSnapshot;
setDisplayState(Playing);
@@ -749,29 +717,58 @@ void HTMLPlugInImageElement::subframeLoaderDidCreatePlugIn(const Widget* widget)
}
}
-void HTMLPlugInImageElement::defaultEventHandler(Event* event)
+void HTMLPlugInImageElement::defaultEventHandler(Event& event)
{
- RenderElement* r = renderer();
- if (r && r->isEmbeddedObject()) {
- if (isPlugInImageElement() && displayState() == WaitingForSnapshot && event->isMouseEvent() && event->type() == eventNames().clickEvent) {
- MouseEvent* mouseEvent = toMouseEvent(event);
- if (mouseEvent->button() == LeftButton) {
- userDidClickSnapshot(mouseEvent, true);
- event->setDefaultHandled();
- return;
- }
+ if (is<RenderEmbeddedObject>(renderer()) && displayState() == WaitingForSnapshot && is<MouseEvent>(event) && event.type() == eventNames().clickEvent) {
+ auto& mouseEvent = downcast<MouseEvent>(event);
+ if (mouseEvent.button() == LeftButton) {
+ userDidClickSnapshot(mouseEvent, true);
+ mouseEvent.setDefaultHandled();
+ return;
}
}
HTMLPlugInElement::defaultEventHandler(event);
}
+bool HTMLPlugInImageElement::allowedToLoadPluginContent(const String& url, const String& mimeType) const
+{
+ // Elements in user agent show tree should load whatever the embedding document policy is.
+ if (isInUserAgentShadowTree())
+ return true;
+
+ URL completedURL;
+ if (!url.isEmpty())
+ completedURL = document().completeURL(url);
+
+ ASSERT(document().contentSecurityPolicy());
+ const ContentSecurityPolicy& contentSecurityPolicy = *document().contentSecurityPolicy();
+
+ contentSecurityPolicy.upgradeInsecureRequestIfNeeded(completedURL, ContentSecurityPolicy::InsecureRequestType::Load);
+
+ if (!contentSecurityPolicy.allowObjectFromSource(completedURL))
+ return false;
+
+ auto& declaredMimeType = document().isPluginDocument() && document().ownerElement() ?
+ document().ownerElement()->attributeWithoutSynchronization(HTMLNames::typeAttr) : attributeWithoutSynchronization(HTMLNames::typeAttr);
+ return contentSecurityPolicy.allowPluginType(mimeType, declaredMimeType, completedURL);
+}
+
bool HTMLPlugInImageElement::requestObject(const String& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues)
{
+ ASSERT(document().frame());
+
+ if (url.isEmpty() && mimeType.isEmpty())
+ return false;
+
+ if (!allowedToLoadPluginContent(url, mimeType)) {
+ renderEmbeddedObject()->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginBlockedByContentSecurityPolicy);
+ return false;
+ }
+
if (HTMLPlugInElement::requestObject(url, mimeType, paramNames, paramValues))
return true;
- SubframeLoader& loader = document().frame()->loader().subframeLoader();
- return loader.requestObject(*this, url, getNameAttribute(), mimeType, paramNames, paramValues);
+ return document().frame()->loader().subframeLoader().requestObject(*this, url, getNameAttribute(), mimeType, paramNames, paramValues);
}
} // namespace WebCore