Title: [215070] trunk
Revision
215070
Author
simon.fra...@apple.com
Date
2017-04-06 17:04:10 -0700 (Thu, 06 Apr 2017)

Log Message

Throttle requestAnimationFrame in cross-origin iframes to 30fps
https://bugs.webkit.org/show_bug.cgi?id=170534

Reviewed by Dan Bates.

Source/WebCore:

Add a throttling reason to ScriptedAnimationController which is NonInteractedCrossOriginFrame,
set on cross-origin iframes whose documents have never seen a user interaction. It's cleared
as soon as an interaction on this frame or a child frame is detected.

Move the initialization of the LowPowerMode throttling reason to Document::requestAnimationFrame(),
since it's more appropriate to compute NonInteractedCrossOriginFrame here than down in ScriptedAnimationController,
and best to do both in the same place.

Tests: http/tests/frame-throttling/raf-throttle-in-cross-origin-subframe.html

* dom/Document.cpp:
(WebCore::Document::requestAnimationFrame):
(WebCore::Document::updateLastHandledUserGestureTimestamp):
* dom/Document.h:
(WebCore::Document::hasHadUserInteraction):
* dom/ScriptedAnimationController.cpp:
(WebCore::ScriptedAnimationController::ScriptedAnimationController):
(WebCore::throttlingReasonToString):
(WebCore::ScriptedAnimationController::interval):
* dom/ScriptedAnimationController.h:
* loader/FrameLoader.cpp:
(WebCore::shouldAskForNavigationConfirmation):

LayoutTests:

* http/tests/frame-throttling/raf-throttle-in-cross-origin-subframe-expected.txt: Added.
* http/tests/frame-throttling/raf-throttle-in-cross-origin-subframe.html: Added.
* http/tests/frame-throttling/resources/requestAnimationFrame-frame.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (215069 => 215070)


--- trunk/LayoutTests/ChangeLog	2017-04-07 00:01:29 UTC (rev 215069)
+++ trunk/LayoutTests/ChangeLog	2017-04-07 00:04:10 UTC (rev 215070)
@@ -1,3 +1,14 @@
+2017-04-05  Simon Fraser  <simon.fra...@apple.com>
+
+        Throttle requestAnimationFrame in cross-origin iframes to 30fps
+        https://bugs.webkit.org/show_bug.cgi?id=170534
+
+        Reviewed by Dan Bates.
+
+        * http/tests/frame-throttling/raf-throttle-in-cross-origin-subframe-expected.txt: Added.
+        * http/tests/frame-throttling/raf-throttle-in-cross-origin-subframe.html: Added.
+        * http/tests/frame-throttling/resources/requestAnimationFrame-frame.html: Added.
+
 2017-04-06  Ryan Haddad  <ryanhad...@apple.com>
 
         Unreviewed, rolling out r215041.

Added: trunk/LayoutTests/http/tests/frame-throttling/raf-throttle-in-cross-origin-subframe-expected.txt (0 => 215070)


--- trunk/LayoutTests/http/tests/frame-throttling/raf-throttle-in-cross-origin-subframe-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/http/tests/frame-throttling/raf-throttle-in-cross-origin-subframe-expected.txt	2017-04-07 00:04:10 UTC (rev 215070)
@@ -0,0 +1,22 @@
+Tests that requestAnimationFrame is throttled in subframes that are cross-origin, and not in same-origin frames
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Received message: frameload
+Received message: frameload
+Checking that requestAnimationFrame is throttled in cross origin frame
+Received message: throttled[cross]: true
+Received message: throttled[same]: false
+PASS throttledState['cross'] is "true"
+PASS throttledState['same'] is "false"
+Interacted with cross-origin frame
+Interacted with same-origin frame
+Received message: throttled[cross]: false
+Received message: throttled[same]: false
+PASS throttledState['cross'] is "false"
+PASS throttledState['same'] is "false"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+ 

Added: trunk/LayoutTests/http/tests/frame-throttling/raf-throttle-in-cross-origin-subframe.html (0 => 215070)


--- trunk/LayoutTests/http/tests/frame-throttling/raf-throttle-in-cross-origin-subframe.html	                        (rev 0)
+++ trunk/LayoutTests/http/tests/frame-throttling/raf-throttle-in-cross-origin-subframe.html	2017-04-07 00:04:10 UTC (rev 215070)
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <style>
+        iframe {
+            height: 200px;
+            width: 400px;
+        }
+    </style>
+    <script src=""
+    <script src=""
+    
+    <script>
+    description("Tests that requestAnimationFrame is throttled in subframes that are cross-origin, and not in same-origin frames");
+    window.jsTestIsAsync = true;
+    
+    var crossOriginFrame;
+    var sameOriginFrame
+
+    var throttledState = {
+        'cross' : undefined,
+        'same' : undefined,
+    }
+
+    var messageHandler;
+    var messagesReceived = 0;
+
+    function interactWithSubframes()
+    {
+        UIHelper.activateAt(crossOriginFrame.offsetLeft + 5, crossOriginFrame.offsetTop + 5).then(function() {
+            debug("Interacted with cross-origin frame");
+            UIHelper.activateAt(sameOriginFrame.offsetLeft + 5, sameOriginFrame.offsetTop + 5).then(function() {
+                debug("Interacted with same-origin frame");
+                messageHandler = checkUnthrottledAfterInteraction;
+                messagesReceived = 0;
+                crossOriginFrame.contentWindow.postMessage("report-throttle-cross", "*");
+                sameOriginFrame.contentWindow.postMessage("report-throttle-same", "*");
+            });
+        });
+    }
+
+    function runTest()
+    {
+        crossOriginFrame = document.getElementById("cross-origin-frame");
+        sameOriginFrame = document.getElementById("same-origin-frame");
+
+        debug("Checking that requestAnimationFrame is throttled in cross origin frame");
+        
+        messageHandler = checkInitiallyThrottled;
+        messagesReceived = 0;
+        crossOriginFrame.contentWindow.postMessage("report-throttle-cross", "*");
+        sameOriginFrame.contentWindow.postMessage("report-throttle-same", "*");
+    }
+
+    function checkInitiallyThrottled()
+    {
+        shouldBeEqualToString("throttledState['cross']", "true");
+        shouldBeEqualToString("throttledState['same']", "false");
+        interactWithSubframes();
+    }
+
+    function checkUnthrottledAfterInteraction()
+    {
+        shouldBeEqualToString("throttledState['cross']", "false");
+        shouldBeEqualToString("throttledState['same']", "false");
+        finishJSTest();
+    }
+
+    window._onmessage_ = function(message)
+    {
+        debug("Received message: " + message.data);
+        if (message.data ="" "frameload") {
+            if (++messagesReceived == 2)
+                runTest();
+            return;
+        }
+
+        var re = /throttled\[(\w+)\]: (true|false)/;
+        var match = re.exec(message.data);
+        if (match) {
+            frameID = match[1];
+            throttledState[frameID] = match[2];
+            if (++messagesReceived == 2)
+                messageHandler();
+            return;
+        }
+        
+        debug("Failed to handle message " + message.data);
+    }
+    </script>
+</head>
+<body>
+    <iframe id="cross-origin-frame" src=""
+    <iframe id="same-origin-frame" src=""
+
+    <script src=""
+</body>
+</html>

Added: trunk/LayoutTests/http/tests/frame-throttling/resources/requestAnimationFrame-frame.html (0 => 215070)


--- trunk/LayoutTests/http/tests/frame-throttling/resources/requestAnimationFrame-frame.html	                        (rev 0)
+++ trunk/LayoutTests/http/tests/frame-throttling/resources/requestAnimationFrame-frame.html	2017-04-07 00:04:10 UTC (rev 215070)
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script>
+        var i = 0;
+        function step()
+        {
+            i++;
+            requestAnimationFrame(step);
+        }
+        step();
+
+        window._onmessage_ = function(message)
+        {
+            var re = /report-throttle-(cross|same)/;
+            var match = re.exec(message.data);
+
+            if (match) {
+                var frameId = match[1];
+                if (window.internals)
+                    parent.window.postMessage("throttled[" + frameId + "]: " + internals.isRequestAnimationFrameThrottled(), "*");
+            }
+        }
+
+        window.addEventListener("load", function() {
+            parent.window.postMessage("frameload", "*");
+        }, false);
+    </script>
+</head>
+<body>
+    Child frame.
+</body>
+</html>

Modified: trunk/Source/WebCore/ChangeLog (215069 => 215070)


--- trunk/Source/WebCore/ChangeLog	2017-04-07 00:01:29 UTC (rev 215069)
+++ trunk/Source/WebCore/ChangeLog	2017-04-07 00:04:10 UTC (rev 215070)
@@ -1,5 +1,35 @@
 2017-04-05  Simon Fraser  <simon.fra...@apple.com>
 
+        Throttle requestAnimationFrame in cross-origin iframes to 30fps
+        https://bugs.webkit.org/show_bug.cgi?id=170534
+
+        Reviewed by Dan Bates.
+
+        Add a throttling reason to ScriptedAnimationController which is NonInteractedCrossOriginFrame,
+        set on cross-origin iframes whose documents have never seen a user interaction. It's cleared
+        as soon as an interaction on this frame or a child frame is detected.
+
+        Move the initialization of the LowPowerMode throttling reason to Document::requestAnimationFrame(),
+        since it's more appropriate to compute NonInteractedCrossOriginFrame here than down in ScriptedAnimationController,
+        and best to do both in the same place.
+
+        Tests: http/tests/frame-throttling/raf-throttle-in-cross-origin-subframe.html
+
+        * dom/Document.cpp:
+        (WebCore::Document::requestAnimationFrame):
+        (WebCore::Document::updateLastHandledUserGestureTimestamp):
+        * dom/Document.h:
+        (WebCore::Document::hasHadUserInteraction):
+        * dom/ScriptedAnimationController.cpp:
+        (WebCore::ScriptedAnimationController::ScriptedAnimationController):
+        (WebCore::throttlingReasonToString):
+        (WebCore::ScriptedAnimationController::interval):
+        * dom/ScriptedAnimationController.h:
+        * loader/FrameLoader.cpp:
+        (WebCore::shouldAskForNavigationConfirmation):
+
+2017-04-05  Simon Fraser  <simon.fra...@apple.com>
+
         Use the Accelerate framework to optimize FEColorMatrix operations
         https://bugs.webkit.org/show_bug.cgi?id=170518
 

Modified: trunk/Source/WebCore/dom/Document.cpp (215069 => 215070)


--- trunk/Source/WebCore/dom/Document.cpp	2017-04-07 00:01:29 UTC (rev 215069)
+++ trunk/Source/WebCore/dom/Document.cpp	2017-04-07 00:04:10 UTC (rev 215070)
@@ -6011,6 +6011,12 @@
         if (!page() || page()->scriptedAnimationsSuspended())
             m_scriptedAnimationController->suspend();
 
+        if (page() && page()->isLowPowerModeEnabled())
+            m_scriptedAnimationController->addThrottlingReason(ScriptedAnimationController::ThrottlingReason::LowPowerMode);
+
+        if (!topOrigin().canAccess(securityOrigin()) && !hasHadUserInteraction())
+            m_scriptedAnimationController->addThrottlingReason(ScriptedAnimationController::ThrottlingReason::NonInteractedCrossOriginFrame);
+
         if (settings().shouldDispatchRequestAnimationFrameEvents()) {
             if (!page())
                 dispatchEvent(Event::create("raf-no-page", false, false));
@@ -6315,6 +6321,11 @@
 void Document::updateLastHandledUserGestureTimestamp(MonotonicTime time)
 {
     m_lastHandledUserGestureTimestamp = time;
+
+    if (static_cast<bool>(time) && m_scriptedAnimationController) {
+        // It's OK to always remove NonInteractedCrossOriginFrame even if this frame isn't cross-origin.
+        m_scriptedAnimationController->removeThrottlingReason(ScriptedAnimationController::ThrottlingReason::NonInteractedCrossOriginFrame);
+    }
     
     if (HTMLFrameOwnerElement* element = ownerElement())
         element->document().updateLastHandledUserGestureTimestamp(time);

Modified: trunk/Source/WebCore/dom/Document.h (215069 => 215070)


--- trunk/Source/WebCore/dom/Document.h	2017-04-07 00:01:29 UTC (rev 215069)
+++ trunk/Source/WebCore/dom/Document.h	2017-04-07 00:04:10 UTC (rev 215070)
@@ -1144,6 +1144,7 @@
     void didRemoveWheelEventHandler(Node&, EventHandlerRemoval = EventHandlerRemoval::One);
 
     MonotonicTime lastHandledUserGestureTimestamp() const { return m_lastHandledUserGestureTimestamp; }
+    bool hasHadUserInteraction() const { return static_cast<bool>(m_lastHandledUserGestureTimestamp); }
     void updateLastHandledUserGestureTimestamp(MonotonicTime);
 
     // Used for testing. Count handlers in the main document, and one per frame which contains handlers.

Modified: trunk/Source/WebCore/dom/ScriptedAnimationController.cpp (215069 => 215070)


--- trunk/Source/WebCore/dom/ScriptedAnimationController.cpp	2017-04-07 00:01:29 UTC (rev 215069)
+++ trunk/Source/WebCore/dom/ScriptedAnimationController.cpp	2017-04-07 00:04:10 UTC (rev 215070)
@@ -64,10 +64,6 @@
 #endif
 {
     windowScreenDidChange(displayID);
-
-    auto* page = document.page();
-    if (page && page->isLowPowerModeEnabled())
-        addThrottlingReason(ThrottlingReason::LowPowerMode);
 }
 
 ScriptedAnimationController::~ScriptedAnimationController()
@@ -110,6 +106,8 @@
         return "OutsideViewport";
     case ScriptedAnimationController::ThrottlingReason::LowPowerMode:
         return "LowPowerMode";
+    case ScriptedAnimationController::ThrottlingReason::NonInteractedCrossOriginFrame:
+        return "NonInteractiveCrossOriginFrame";
     }
 }
 
@@ -268,8 +266,13 @@
 #if USE(REQUEST_ANIMATION_FRAME_TIMER) && USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
     if (m_throttlingReasons.contains(ThrottlingReason::VisuallyIdle) || m_throttlingReasons.contains(ThrottlingReason::OutsideViewport))
         return aggressiveThrottlingAnimationInterval;
+
     if (m_throttlingReasons.contains(ThrottlingReason::LowPowerMode))
         return halfSpeedThrottlingAnimationInterval;
+
+    if (m_throttlingReasons.contains(ThrottlingReason::NonInteractedCrossOriginFrame))
+        return halfSpeedThrottlingAnimationInterval;
+
     ASSERT(m_throttlingReasons.isEmpty());
 #endif
     return fullSpeedAnimationInterval;

Modified: trunk/Source/WebCore/dom/ScriptedAnimationController.h (215069 => 215070)


--- trunk/Source/WebCore/dom/ScriptedAnimationController.h	2017-04-07 00:01:29 UTC (rev 215069)
+++ trunk/Source/WebCore/dom/ScriptedAnimationController.h	2017-04-07 00:04:10 UTC (rev 215070)
@@ -73,9 +73,10 @@
     void resume();
 
     enum class ThrottlingReason {
-        VisuallyIdle    = 1 << 0,
-        OutsideViewport = 1 << 1,
-        LowPowerMode    = 1 << 2,
+        VisuallyIdle                    = 1 << 0,
+        OutsideViewport                 = 1 << 1,
+        LowPowerMode                    = 1 << 2,
+        NonInteractedCrossOriginFrame   = 1 << 3,
     };
     void addThrottlingReason(ThrottlingReason);
     void removeThrottlingReason(ThrottlingReason);

Modified: trunk/Source/WebCore/loader/FrameLoader.cpp (215069 => 215070)


--- trunk/Source/WebCore/loader/FrameLoader.cpp	2017-04-07 00:01:29 UTC (rev 215069)
+++ trunk/Source/WebCore/loader/FrameLoader.cpp	2017-04-07 00:04:10 UTC (rev 215070)
@@ -3036,7 +3036,7 @@
 
 static bool shouldAskForNavigationConfirmation(Document& document, const BeforeUnloadEvent& event)
 {
-    bool userDidInteractWithPage = static_cast<bool>(document.topDocument().lastHandledUserGestureTimestamp());
+    bool userDidInteractWithPage = document.topDocument().hasHadUserInteraction();
     // Web pages can request we ask for confirmation before navigating by:
     // - Cancelling the BeforeUnloadEvent (modern way)
     // - Setting the returnValue attribute on the BeforeUnloadEvent to a non-empty string.
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to