+2013-04-15 Dean Jackson <dino@apple.com>
+
+ Plugins that resize might need to be snapshotted.
+ https://bugs.webkit.org/show_bug.cgi?id=102157
+ <rdar://problem/12696259>
+
+ Reviewed by Tim Horton.
+
+ A plugin could avoid snapshotting if it started very
+ small (below the threshold where we autostart), and then
+ resized to be large (above the threshold). Detect this
+ case and tell the plugin to snapshot.
+
+ There unfortunately is a bit of state to track when doing
+ this. We have to start the snapshotting in a post-layout
+ task, so we carry a flag to say we're checking size but
+ not wanting the plugin to update (which would restart it).
+ We also might be a plugin that would have already restarted
+ due to a similar plugin being clicked or detected as dominant.
+ So this patch introduces a member variable that tracks what
+ decision has been made on snapshotting.
+
+ I also added some more logging to be more clear about what is
+ happening to plugins going through the snapshot process, and
+ changed the order of the tests in the algorithm so that domain
+ detection comes before examining the size.
+
+ * html/HTMLPlugInImageElement.cpp:
+ (WebCore::HTMLPlugInImageElement::HTMLPlugInImageElement): Initialise
+ the two new member variables.
+ (WebCore::HTMLPlugInImageElement::setDisplayState): Mark a restarted
+ plugin as NeverSnapshot. This means that if it later resizes above the
+ threshold, it won't trigger the snapshot detection.
+ (WebCore::HTMLPlugInImageElement::checkSnapshotStatus): Renamed from
+ updateSnapshotInfo. This now updates the snapshot, but also runs
+ the check for size changes.
+ (WebCore::addPlugInsFromNodeListMatchingPlugInOrigin): Gather all plugins
+ that look like a restarting plugin, not just those snapshotted. That
+ way they can all be marked as NeverSnapshot.
+ (WebCore::HTMLPlugInImageElement::restartSimilarPlugIns): Bless every
+ plugin that looks similar, whether or not it is snapshotted.
+ (WebCore::HTMLPlugInImageElement::userDidClickSnapshot): More logging.
+ (WebCore::HTMLPlugInImageElement::setIsPrimarySnapshottedPlugIn): Ditto.
+ (WebCore::HTMLPlugInImageElement::checkSizeChangeForSnapshotting): New
+ method. If the plugin was below the threshold and is now above it,
+ begin the snapshotting process.
+ (WebCore::HTMLPlugInImageElement::subframeLoaderWillCreatePlugIn): Plugins
+ that were marked as NeverSnapshot should start immediately. Move the origin
+ test earlier in the method. If a plugin avoided snapshotting due to size,
+ remember the size.
+ (WebCore::HTMLPlugInImageElement::subframeLoaderDidCreatePlugIn): Mark the
+ plugin as NeverSnapshot.
+
+ * html/HTMLPlugInImageElement.h:
+ (HTMLPlugInImageElement): Four new member variables:
+ - the decision we made regarding snapshotting (or not), uses the SnapshotDecision enum
+ - the size when it avoided snapshotting
+ - a flag to indicate the post layout update was triggered due to
+ a size change
+ (WebCore::HTMLPlugInImageElement::needsCheckForSizeChange): New method.
+ (WebCore::HTMLPlugInImageElement::setNeedsCheckForSizeChange): New method.
+ (WebCore::HTMLPlugInImageElement::snapshotDecision): Return the decision
+ regarding snapshotting or not snapshotting.
+
+ * page/FrameView.cpp:
+ (WebCore::FrameView::addWidgetToUpdate): Guard updating the widget. We don't
+ want to do it when we're checking for a size change in the plugin.
+ (WebCore::FrameView::updateWidget): Call new name.
+
+ * plugins/PluginViewBase.h:
+ (WebCore::PluginViewBase::beginSnapshottingRunningPlugin): Empty virtual
+ method to snapshot a running plugin.
+
+ * rendering/RenderEmbeddedObject.cpp:
+ (WebCore::RenderEmbeddedObject::layout): If the plugin has increased in
+ size add it to the post layout list so that it will be checked.
+
2013-04-15 Chris Fleizach <cfleizach@apple.com>
activating a focused link to an in-page fragment ID should transfer focus to the target of the link when possible
, m_removeSnapshotTimer(this, &HTMLPlugInImageElement::removeSnapshotTimerFired)
, m_createdDuringUserGesture(ScriptController::processingUserGesture())
, m_restartedPlugin(false)
+ , m_needsCheckForSizeChange(false)
+ , m_snapshotDecision(SnapshotNotYetDecided)
{
setHasCustomStyleCallbacks();
}
#if PLATFORM(MAC)
if (state == RestartingWithPendingMouseClick || state == Restarting) {
m_restartedPlugin = true;
+ m_snapshotDecision = NeverSnapshot;
if (displayState() == DisplayingSnapshot)
m_removeSnapshotTimer.startOneShot(removeSnapshotTimerDelay);
}
return plugInLargeSizeClassName;
}
-void HTMLPlugInImageElement::updateSnapshotInfo()
+void HTMLPlugInImageElement::checkSnapshotStatus()
{
+ if (!renderer()->isSnapshottedPlugIn()) {
+ if (displayState() == Playing)
+ checkSizeChangeForSnapshotting();
+ return;
+ }
+
ShadowRoot* root = userAgentShadowRoot();
if (!root)
return;
Node* node = collection->item(i);
if (node->isPluginElement()) {
HTMLPlugInElement* plugInElement = toHTMLPlugInElement(node);
- if (plugInElement->isPlugInImageElement() && plugInElement->displayState() <= HTMLPlugInElement::DisplayingSnapshot) {
+ if (plugInElement->isPlugInImageElement()) {
HTMLPlugInImageElement* plugInImageElement = toHTMLPlugInImageElement(node);
const KURL& loadedURL = plugInImageElement->loadedUrl();
String otherMimeType = plugInImageElement->loadedMimeType();
String plugInOrigin = m_loadedUrl.host();
String mimeType = loadedMimeType();
- HTMLPlugInImageElementList pluginsNeedingRestart;
+ HTMLPlugInImageElementList similarPlugins;
if (!document()->page())
return;
for (Frame* frame = document()->page()->mainFrame(); frame; frame = frame->tree()->traverseNext()) {
- if (frame->loader()->subframeLoader()->containsPlugins()) {
- if (!frame->document())
- continue;
-
- RefPtr<NodeList> plugins = frame->document()->getElementsByTagName(embedTag.localName());
- if (plugins)
- addPlugInsFromNodeListMatchingPlugInOrigin(pluginsNeedingRestart, plugins, plugInOrigin, mimeType);
-
- plugins = frame->document()->getElementsByTagName(objectTag.localName());
- if (plugins)
- addPlugInsFromNodeListMatchingPlugInOrigin(pluginsNeedingRestart, plugins, plugInOrigin, mimeType);
- }
+ if (!frame->loader()->subframeLoader()->containsPlugins())
+ continue;
+
+ 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 (size_t i = 0, length = pluginsNeedingRestart.size(); i < length; i++) {
- pluginsNeedingRestart[i]->setDisplayState(Playing);
- pluginsNeedingRestart[i]->restartSnapshottedPlugIn();
+ for (size_t i = 0, length = similarPlugins.size(); i < length; ++i) {
+ HTMLPlugInImageElement* plugInToRestart = similarPlugins[i].get();
+ if (plugInToRestart->displayState() <= HTMLPlugInElement::DisplayingSnapshot) {
+ LOG(Plugins, "%p Plug-in looks similar to a restarted plug-in. Restart.", plugInToRestart);
+ plugInToRestart->setDisplayState(Playing);
+ plugInToRestart->restartSnapshottedPlugIn();
+ }
+ plugInToRestart->m_snapshotDecision = NeverSnapshot;
}
}
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());
+ LOG(Plugins, "%p User clicked on snapshotted plug-in. Restart.", this);
restartSnapshottedPlugIn();
restartSimilarPlugIns();
}
return;
if (isPrimarySnapshottedPlugIn) {
+ LOG(Plugins, "%p Plug-in was detected as the primary element in the page. Restart.", this);
restartSnapshottedPlugIn();
restartSimilarPlugIns();
}
m_pendingClickEventFromSnapshot = nullptr;
}
+void HTMLPlugInImageElement::checkSizeChangeForSnapshotting()
+{
+ if (!m_needsCheckForSizeChange || m_snapshotDecision != MaySnapshotWhenResized)
+ return;
+
+ m_needsCheckForSizeChange = false;
+ LayoutRect contentBoxRect = toRenderBox(renderer())->contentBoxRect();
+ int contentWidth = contentBoxRect.width();
+ int contentHeight = contentBoxRect.height();
+
+ if (contentWidth <= sizingTinyDimensionThreshold || contentHeight <= sizingTinyDimensionThreshold)
+ return;
+
+ 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();
+}
+
void HTMLPlugInImageElement::subframeLoaderWillCreatePlugIn(const KURL& url)
{
LOG(Plugins, "%p Plug-in URL: %s", this, m_url.utf8().data());
m_loadedUrl = url;
- if (!document()->page()
- || !document()->page()->settings()->plugInSnapshottingEnabled())
+ if (!document()->page() || !document()->page()->settings()->plugInSnapshottingEnabled()) {
+ m_snapshotDecision = NeverSnapshot;
return;
+ }
if (displayState() == Restarting) {
- setDisplayState(Playing);
LOG(Plugins, "%p Plug-in is explicitly restarting", this);
+ m_snapshotDecision = NeverSnapshot;
+ setDisplayState(Playing);
return;
}
if (displayState() == RestartingWithPendingMouseClick) {
LOG(Plugins, "%p Plug-in is explicitly restarting but also waiting for a click", this);
+ m_snapshotDecision = NeverSnapshot;
return;
}
+ if (m_snapshotDecision == NeverSnapshot) {
+ LOG(Plugins, "%p Plug-in is blessed, allow it to start", this);
+ return;
+ }
+
bool inMainFrame = document()->frame() == document()->page()->mainFrame();
if (document()->isPluginDocument() && inMainFrame) {
LOG(Plugins, "%p Plug-in document in main frame", this);
+ m_snapshotDecision = NeverSnapshot;
return;
}
if (ScriptController::processingUserGesture()) {
LOG(Plugins, "%p Script is currently processing user gesture, set to play", this);
+ m_snapshotDecision = NeverSnapshot;
return;
}
if (m_createdDuringUserGesture) {
LOG(Plugins, "%p Plug-in was created when processing user gesture, set to play", this);
+ m_snapshotDecision = NeverSnapshot;
return;
}
lastKnownUserGestureTimestamp = std::max(lastKnownUserGestureTimestamp, document()->page()->mainFrame()->document()->lastHandledUserGestureTimestamp());
if (currentTime() - lastKnownUserGestureTimestamp < autostartSoonAfterUserGestureThreshold) {
LOG(Plugins, "%p Plug-in was created shortly after a user gesture, set to play", this);
+ m_snapshotDecision = NeverSnapshot;
return;
}
if (document()->page()->settings()->snapshotAllPlugIns()) {
LOG(Plugins, "%p Plug-in forced to snapshot by user preference", this);
+ m_snapshotDecision = Snapshotted;
setDisplayState(WaitingForSnapshot);
return;
}
+ if (document()->page()->settings()->autostartOriginPlugInSnapshottingEnabled() && document()->page()->plugInClient()->shouldAutoStartFromOrigin(document()->page()->mainFrame()->document()->baseURL().host(), url.host(), loadedMimeType())) {
+ LOG(Plugins, "%p Plug-in from (%s, %s) is marked to auto-start, set to play", this, document()->page()->mainFrame()->document()->baseURL().host().utf8().data(), url.host().utf8().data());
+ m_snapshotDecision = NeverSnapshot;
+ return;
+ }
+
RenderBox* renderEmbeddedObject = toRenderBox(renderer());
Length styleWidth = renderEmbeddedObject->style()->width();
Length styleHeight = renderEmbeddedObject->style()->height();
&& styleHeight.isPercent() && (styleHeight.percent() == 100)
&& (static_cast<float>(contentArea) / visibleArea > sizingFullPageAreaRatioThreshold)) {
LOG(Plugins, "%p Plug-in is top level full page, set to play", this);
+ m_snapshotDecision = NeverSnapshot;
return;
}
if (contentWidth <= sizingTinyDimensionThreshold || contentHeight <= sizingTinyDimensionThreshold) {
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_snapshotDecision = MaySnapshotWhenResized;
return;
}
if (!document()->page()->plugInClient()) {
+ LOG(Plugins, "%p There is no plug-in client. Set to wait for snapshot", this);
+ m_snapshotDecision = NeverSnapshot;
setDisplayState(WaitingForSnapshot);
return;
}
- if (document()->page()->settings()->autostartOriginPlugInSnapshottingEnabled() && document()->page()->plugInClient()->shouldAutoStartFromOrigin(document()->page()->mainFrame()->document()->baseURL().host(), url.host(), loadedMimeType())) {
- LOG(Plugins, "%p Plug-in from (%s, %s) is marked to auto-start, set to play", this, document()->page()->mainFrame()->document()->baseURL().host().utf8().data(), url.host().utf8().data());
- 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);
+ m_snapshotDecision = Snapshotted;
setDisplayState(WaitingForSnapshot);
}
void HTMLPlugInImageElement::subframeLoaderDidCreatePlugIn(const Widget* widget)
{
- if (!widget->isPluginViewBase()
- || !static_cast<const PluginViewBase*>(widget)->shouldAlwaysAutoStart())
+ if (!widget->isPluginViewBase() || !toPluginViewBase(widget)->shouldAlwaysAutoStart())
return;
LOG(Plugins, "%p Plug-in should auto-start, set to play", this);
+ m_snapshotDecision = NeverSnapshot;
setDisplayState(Playing);
}