Plugins created during user gestures (or soon after) should not be snapshotted
authordino@apple.com <dino@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 11 Mar 2013 22:51:43 +0000 (22:51 +0000)
committerdino@apple.com <dino@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 11 Mar 2013 22:51:43 +0000 (22:51 +0000)
https://bugs.webkit.org/show_bug.cgi?id=111975

Reviewed by Tim Horton.

There are sites which create plugins in response to user actions, such as clicking
on an image that is acting like a poster frame. In those cases we should never snapshot.

There are some other sites which also create plugins in response to user actions,
but don't necessarily create the content themselves. Instead they run some script
that injects an iframe, and the frame loads a plugin. In order to make sure we don't
snapshot in those cases, we're adding the concept of a blessed plugin. Anything that
is created soon after a *handled* user gesture is not snapshotted. To do this we
mark a timestamp in the document when we've called an event listener for a user
gesture. The plugin element then compares its creation time with the most recent
user action time.

* dom/Document.cpp:
(WebCore::Document::Document): Initialise new timestamp.
(WebCore::Document::resetLastHandledUserGestureTimestamp): Sets the member variable
    to the current time.
* dom/Document.h:
(WebCore::Document::lastHandledUserGestureTimestamp): Getter.

* dom/EventTarget.cpp:
(WebCore::EventTarget::fireEventListeners): If there were some event listeners and
    we were processing a user gesture, then reset the timestamp in the document.

* html/HTMLPlugInImageElement.cpp:
(WebCore::HTMLPlugInImageElement::HTMLPlugInImageElement): Remember if we were created
    during a user gesture.
(WebCore::HTMLPlugInImageElement::subframeLoaderWillCreatePlugIn): Start the plugin
    if we were created during a user gesture, or if we are close enough in time
    to a listener that fired in relation to a user gesture.
* html/HTMLPlugInImageElement.h: New private member flag indicating if we were
    in a user gesture when constructed.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@145421 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebCore/ChangeLog
Source/WebCore/dom/Document.cpp
Source/WebCore/dom/Document.h
Source/WebCore/dom/EventTarget.cpp
Source/WebCore/html/HTMLPlugInImageElement.cpp
Source/WebCore/html/HTMLPlugInImageElement.h

index 0c50abf..eeca537 100644 (file)
@@ -1,3 +1,42 @@
+2013-03-11  Dean Jackson  <dino@apple.com>
+
+        Plugins created during user gestures (or soon after) should not be snapshotted
+        https://bugs.webkit.org/show_bug.cgi?id=111975
+
+        Reviewed by Tim Horton.
+
+        There are sites which create plugins in response to user actions, such as clicking
+        on an image that is acting like a poster frame. In those cases we should never snapshot.
+
+        There are some other sites which also create plugins in response to user actions,
+        but don't necessarily create the content themselves. Instead they run some script
+        that injects an iframe, and the frame loads a plugin. In order to make sure we don't
+        snapshot in those cases, we're adding the concept of a blessed plugin. Anything that
+        is created soon after a *handled* user gesture is not snapshotted. To do this we
+        mark a timestamp in the document when we've called an event listener for a user
+        gesture. The plugin element then compares its creation time with the most recent
+        user action time.
+
+        * dom/Document.cpp:
+        (WebCore::Document::Document): Initialise new timestamp.
+        (WebCore::Document::resetLastHandledUserGestureTimestamp): Sets the member variable
+            to the current time.
+        * dom/Document.h:
+        (WebCore::Document::lastHandledUserGestureTimestamp): Getter.
+
+        * dom/EventTarget.cpp:
+        (WebCore::EventTarget::fireEventListeners): If there were some event listeners and
+            we were processing a user gesture, then reset the timestamp in the document.
+
+        * html/HTMLPlugInImageElement.cpp:
+        (WebCore::HTMLPlugInImageElement::HTMLPlugInImageElement): Remember if we were created
+            during a user gesture.
+        (WebCore::HTMLPlugInImageElement::subframeLoaderWillCreatePlugIn): Start the plugin
+            if we were created during a user gesture, or if we are close enough in time
+            to a listener that fired in relation to a user gesture.
+        * html/HTMLPlugInImageElement.h: New private member flag indicating if we were
+            in a user gesture when constructed.
+
 2013-03-11  Jeffrey Pfau  <jpfau@apple.com>
 
         List cache partitions as units instead of as their contents
index fcede2d..c5847af 100644 (file)
@@ -476,6 +476,7 @@ Document::Document(Frame* frame, const KURL& url, bool isXHTML, bool isHTML)
     , m_writeRecursionIsTooDeep(false)
     , m_writeRecursionDepth(0)
     , m_wheelEventHandlerCount(0)
+    , m_lastHandledUserGestureTimestamp(0)
     , m_pendingTasksTimer(this, &Document::pendingTasksTimerFired)
     , m_scheduledTasksAreSuspended(false)
     , m_visualUpdatesAllowed(true)
@@ -5677,6 +5678,11 @@ void Document::didRemoveEventTargetNode(Node* handler)
 }
 #endif
 
+void Document::resetLastHandledUserGestureTimestamp()
+{
+    m_lastHandledUserGestureTimestamp = currentTime();
+}
+
 HTMLIFrameElement* Document::seamlessParentIFrame() const
 {
     if (!shouldDisplaySeamlesslyWithParent())
index 3f5dc20..a41a05d 100644 (file)
@@ -1101,6 +1101,9 @@ public:
     void didAddWheelEventHandler();
     void didRemoveWheelEventHandler();
 
+    double lastHandledUserGestureTimestamp() const { return m_lastHandledUserGestureTimestamp; }
+    void resetLastHandledUserGestureTimestamp();
+
 #if ENABLE(TOUCH_EVENTS)
     bool hasTouchEventHandlers() const { return (m_touchEventTargets.get()) ? m_touchEventTargets->size() : false; }
 #else
@@ -1513,6 +1516,8 @@ private:
     OwnPtr<TouchEventTargetSet> m_touchEventTargets;
 #endif
 
+    double m_lastHandledUserGestureTimestamp;
+
 #if ENABLE(REQUEST_ANIMATION_FRAME)
     RefPtr<ScriptedAnimationController> m_scriptedAnimationController;
 #endif
index d0ad520..c11d20f 100644 (file)
@@ -232,6 +232,7 @@ void EventTarget::fireEventListeners(Event* event, EventTargetData* d, EventList
     // dispatch. Conveniently, all new event listeners will be added after 'end',
     // so iterating to 'end' naturally excludes new event listeners.
 
+    bool userEventWasHandled = false;
     size_t i = 0;
     size_t end = entry.size();
     if (!d->firingEventIterators)
@@ -254,9 +255,18 @@ void EventTarget::fireEventListeners(Event* event, EventTargetData* d, EventList
         // To match Mozilla, the AT_TARGET phase fires both capturing and bubbling
         // event listeners, even though that violates some versions of the DOM spec.
         registeredListener.listener->handleEvent(context, event);
+        if (!userEventWasHandled && ScriptController::processingUserGesture())
+            userEventWasHandled = true;
         InspectorInstrumentation::didHandleEvent(cookie);
     }
     d->firingEventIterators->removeLast();
+    if (userEventWasHandled) {
+        ScriptExecutionContext* context = scriptExecutionContext();
+        if (context && context->isDocument()) {
+            Document* document = static_cast<Document*>(context);
+            document->resetLastHandledUserGestureTimestamp();
+        }
+    }
 }
 
 const EventListenerVector& EventTarget::getEventListeners(const AtomicString& eventType)
index 5d63541..1be940e 100644 (file)
@@ -58,6 +58,7 @@ static const int sizingSmallWidthThreshold = 250;
 static const int sizingMediumWidthThreshold = 450;
 static const int sizingMediumHeightThreshold = 300;
 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;
@@ -74,6 +75,7 @@ HTMLPlugInImageElement::HTMLPlugInImageElement(const QualifiedName& tagName, Doc
     , m_isPrimarySnapshottedPlugIn(false)
     , m_simulatedMouseClickTimer(this, &HTMLPlugInImageElement::simulatedMouseClickTimerFired, simulatedMouseClickTimerDelay)
     , m_swapRendererTimer(this, &HTMLPlugInImageElement::swapRendererTimerFired)
+    , m_createdDuringUserGesture(ScriptController::processingUserGesture())
 {
     setHasCustomStyleCallbacks();
 }
@@ -444,6 +446,19 @@ void HTMLPlugInImageElement::subframeLoaderWillCreatePlugIn(const KURL& url)
         return;
     }
 
+    if (m_createdDuringUserGesture) {
+        LOG(Plugins, "%p Plug-in was created when processing user gesture, set to play", this);
+        return;
+    }
+
+    double lastKnownUserGestureTimestamp = document()->lastHandledUserGestureTimestamp();
+    if (!inMainFrame && document()->page()->mainFrame() && document()->page()->mainFrame()->document())
+        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);
+        return;
+    }
+
     RenderBox* renderEmbeddedObject = toRenderBox(renderer());
     Length styleWidth = renderEmbeddedObject->style()->width();
     Length styleHeight = renderEmbeddedObject->style()->height();
index 07fee5d..7b72d8f 100644 (file)
@@ -125,6 +125,7 @@ private:
     DeferrableOneShotTimer<HTMLPlugInImageElement> m_simulatedMouseClickTimer;
     Timer<HTMLPlugInImageElement> m_swapRendererTimer;
     RefPtr<Image> m_snapshotImage;
+    bool m_createdDuringUserGesture;
 };
 
 inline HTMLPlugInImageElement* toHTMLPlugInImageElement(Node* node)