Throttling requestAnimationFrame should be controlled by RenderingUpdateScheduler
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 4 May 2020 21:24:49 +0000 (21:24 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 4 May 2020 21:24:49 +0000 (21:24 +0000)
https://bugs.webkit.org/show_bug.cgi?id=204713

Patch by Said Abou-Hallawa <sabouhallawa@apple.com> on 2020-05-04
Reviewed by Simon Fraser.

Source/WebCore:

rAF and Page rendering were managed by two different timers. Throttling
rAF was implemented by changing its timer. After r242624, RenderingUpdate
steps have been managed by RenderingUpdateScheduler. This means rAF is
now serviced by the preferredFramesPerSecond which is 60 fps regardless
it's throttled or not. Moreover the rAF throttling timer was mistakenly
kept and it has been running under the old assumption which is: rAF is
serviced by a timer only. This means rAF will be serviced by its timer
and by the RenderingUpdate steps at the same time when it is supposed to
throttle. This will make it fire more than 60 fps in cases which it is
supposed to run less than 60 fps.

The solution is to have two throttling types:

1) Page throttling (or full throttling): This slows down all the steps
   of RenderingUpdate for the main document and all the sub-documents.
   Page throttling reasons are:
   -- VisuallyIdle: Aggressive throttling.
   -- LowPowerMode: Half speed throttling.
2) Document throttling (or partial throttling): This only slows down the
   rAF of a certain document. Document throttling reasons are:
   -- OutsideViewport: Aggressive throttling.
   -- NonInteractedCrossOriginFrame: Half speed throttling.

RenderingUpdate steps will still be managed by RenderingUpdateScheduler
which can be throttled. The assumption is none of these steps will need
to run faster than the Page preferredFramesPerSecond. If rAF wants to
run slower than the Page because of a Document throttling reason, no rAF
callbacks will be serviced before its preferredFrameInterval has elapsed.

In this patch, "Half speed throttling" is only implemented for the Page
and the Document throttling. The "Aggressive throttling" will be done in
following patches. Page rendering was never throttled before. We need to
make sure this is not going to affect PLT. Some tests need to be changed
and new tests need to be written.  All of the throttling tests checks the
state of the code but none of them checks the real user's experience.

* Headers.cmake:
* WebCore.xcodeproj/project.pbxproj:

* animation/DocumentTimeline.cpp:
(WebCore::DocumentTimeline::animationInterval const):
(WebCore::DocumentTimeline::updateThrottlingState): Deleted.
* animation/DocumentTimeline.h:
There is no need to have DocumentTimeline throttling. It is already
throttled when the page RenderingUpdate is throttled.

* dom/Document.cpp:
(WebCore::Document::requestAnimationFrame):
(WebCore::Document::updateLastHandledUserGestureTimestamp):
LowPowerMode throttling is now handled by the Page. So remove its handling
from the Document.

* dom/ScriptedAnimationController.cpp:
(WebCore::ScriptedAnimationController::ScriptedAnimationController):
(WebCore::ScriptedAnimationController::page const):
(WebCore::ScriptedAnimationController::interval const):
(WebCore::ScriptedAnimationController::preferredScriptedAnimationInterval const):
(WebCore::ScriptedAnimationController::throttlingReasons const):
(WebCore::ScriptedAnimationController::isThrottledRelativeToPage const):
(WebCore::ScriptedAnimationController::shouldRescheduleRequestAnimationFrame const):
(WebCore::ScriptedAnimationController::registerCallback):
(WebCore::ScriptedAnimationController::cancelCallback):
(WebCore::ScriptedAnimationController::serviceRequestAnimationFrameCallbacks):
(WebCore::ScriptedAnimationController::scheduleAnimation):
(WebCore::throttlingReasonToString): Deleted.
(WebCore::throttlingReasonsToString): Deleted.
(WebCore::ScriptedAnimationController::addThrottlingReason): Deleted.
(WebCore::ScriptedAnimationController::removeThrottlingReason): Deleted.
(WebCore::ScriptedAnimationController::isThrottled const): Deleted.
(WebCore::ScriptedAnimationController::animationTimerFired): Deleted.
* dom/ScriptedAnimationController.h:
(WebCore::ScriptedAnimationController::addThrottlingReason):
(WebCore::ScriptedAnimationController::removeThrottlingReason):
Get rid of the rAF throttling timer. Service the rAF callback only when
the period from the current time stamp till the last service time stamp
is greater than the preferred rAF interval.

* page/FrameView.cpp:
(WebCore::FrameView::updateScriptedAnimationsAndTimersThrottlingState):
ThrottlingReason is now defined outside ScriptedAnimationController.

* page/Page.cpp:
(WebCore::m_loadsFromNetwork):
(WebCore::Page::setLowPowerModeEnabledOverrideForTesting):

(WebCore::Page::preferredRenderingUpdateInterval const):
Calculate the preferred RenderingUpdate interval from the throttling
reasons.

(WebCore::Page::setIsVisuallyIdleInternal):
(WebCore::Page::handleLowModePowerChange):
Call adjustRenderingUpdateFrequency() when isLowPowerModeEnabled or
IsVisuallyIdle is toggled.

(WebCore::Page::isLowPowerModeEnabled const): Deleted.
(WebCore::updateScriptedAnimationsThrottlingReason): Deleted.
* page/Page.h:
(WebCore::Page::isLowPowerModeEnabled const):
(WebCore::Page::throttlingReasons const):
(WebCore::Page::canUpdateThrottlingReason const):

* page/RenderingUpdateScheduler.cpp:
(WebCore::RenderingUpdateScheduler::setPreferredFramesPerSecond):
(WebCore::RenderingUpdateScheduler::scheduleAnimation):
(WebCore::RenderingUpdateScheduler::adjustRenderingUpdateFrequency):
Change the preferredFramesPerSecond of the DisplayRefreshMonitor if the
throttling is not aggressive e.g. 10_s. Otherwise use the timer.

(WebCore::RenderingUpdateScheduler::scheduleTimedRenderingUpdate):
Call adjustFramesPerSecond() when DisplayRefreshMonitor is created.

(WebCore::RenderingUpdateScheduler::startTimer):
* page/RenderingUpdateScheduler.h:
* platform/graphics/AnimationFrameRate.h: Added.
(WebCore::preferredFrameInterval):
(WebCore::preferredFramesPerSecond):
(WebCore::operator<<):
Push names of ThrottlingReasons to a TextStream.

* platform/graphics/DisplayRefreshMonitor.h:
(WebCore::DisplayRefreshMonitor::setPreferredFramesPerSecond):
* platform/graphics/DisplayRefreshMonitorManager.cpp:
(WebCore::DisplayRefreshMonitorManager::monitorForClient):
Rename createMonitorForClient() to monitorForClient() since it may return
a cached DisplayRefreshMonitor.

(WebCore::DisplayRefreshMonitorManager::setPreferredFramesPerSecond):
(WebCore::DisplayRefreshMonitorManager::scheduleAnimation):
(WebCore::DisplayRefreshMonitorManager::windowScreenDidChange):
No need to call registerClient(). This function was just ensuring the
DisplayRefreshMonitor is created. scheduleAnimation() does the same thing.

(WebCore::DisplayRefreshMonitorManager::createMonitorForClient): Deleted.
(WebCore::DisplayRefreshMonitorManager::registerClient): Deleted.
* platform/graphics/DisplayRefreshMonitorManager.h:
(WebCore::DisplayRefreshMonitorManager::DisplayRefreshMonitorManager): Deleted.

* platform/graphics/GraphicsLayerUpdater.cpp:
(WebCore::GraphicsLayerUpdater::GraphicsLayerUpdater):
* platform/graphics/ios/DisplayRefreshMonitorIOS.mm:
(-[WebDisplayLinkHandler setPreferredFramesPerSecond:]):
Set the preferredFramesPerSecond of the CADisplayLink.

* testing/Internals.cpp:
(WebCore::Internals::requestAnimationFrameThrottlingReasons const):
(WebCore::Internals::isRequestAnimationFrameThrottled const): Deleted.
* testing/Internals.h:
* testing/Internals.idl:
Replace isRequestAnimationFrameThrottled() which returns a boolean by
requestAnimationFrameThrottlingReasons() which returns a string. The
string represents the throttling reasons.

Source/WebKit:

Create an IPC message on the DrawingArea to send a message from the
WebProcess to the UIProcess to setPreferredFramesPerSecond of the
DisplayRefreshMonitor.

* UIProcess/RemoteLayerTree/RemoteLayerTreeDrawingAreaProxy.h:
* UIProcess/RemoteLayerTree/RemoteLayerTreeDrawingAreaProxy.messages.in:
* UIProcess/RemoteLayerTree/RemoteLayerTreeDrawingAreaProxy.mm:
(-[WKOneShotDisplayLinkHandler setPreferredFramesPerSecond:]):
(WebKit::RemoteLayerTreeDrawingAreaProxy::setPreferredFramesPerSecond):
Set the preferredFramesPerSecond of the CADisplayLink.

* WebProcess/WebPage/RemoteLayerTree/RemoteLayerTreeDisplayRefreshMonitor.h:
* WebProcess/WebPage/RemoteLayerTree/RemoteLayerTreeDisplayRefreshMonitor.mm:
(WebKit::RemoteLayerTreeDisplayRefreshMonitor::setPreferredFramesPerSecond):
Forward the call to RemoteLayerTreeDrawingArea.

* WebProcess/WebPage/RemoteLayerTree/RemoteLayerTreeDrawingArea.h:
* WebProcess/WebPage/RemoteLayerTree/RemoteLayerTreeDrawingArea.mm:
(WebKit::RemoteLayerTreeDrawingArea::setPreferredFramesPerSecond):
Send the IPC message from the WebProcess to the UIProcess.

LayoutTests:

* fast/animation/request-animation-frame-throttle-inside-overflow-scroll-expected.txt:
* fast/animation/request-animation-frame-throttle-inside-overflow-scroll.html:
* fast/animation/request-animation-frame-throttle-subframe-display-none-expected.txt:
* fast/animation/request-animation-frame-throttle-subframe-display-none.html:
* fast/animation/request-animation-frame-throttle-subframe-expected.txt:
* fast/animation/request-animation-frame-throttle-subframe-zero-size-expected.txt:
* fast/animation/request-animation-frame-throttle-subframe-zero-size.html:
* fast/animation/request-animation-frame-throttle-subframe.html:
* fast/animation/request-animation-frame-throttling-detached-iframe-expected.txt:
* fast/animation/request-animation-frame-throttling-detached-iframe.html:
Replace the call isRequestAnimationFrameThrottled() by requestAnimationFrameThrottlingReasons().

* fast/animation/request-animation-frame-throttling-lowPowerMode-expected.txt:
* fast/animation/request-animation-frame-throttling-lowPowerMode.html:
Ensure the actual rAF interval is > 30ms for lowPowerMode.

* http/tests/frame-throttling/raf-throttle-in-cross-origin-subframe-expected.txt:
* http/tests/frame-throttling/raf-throttle-in-cross-origin-subframe.html:
* http/tests/frame-throttling/resources/requestAnimationFrame-frame.html:
Replace the call isRequestAnimationFrameThrottled() by requestAnimationFrameThrottlingReasons().

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

46 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/animation/request-animation-frame-throttle-inside-overflow-scroll-expected.txt
LayoutTests/fast/animation/request-animation-frame-throttle-inside-overflow-scroll.html
LayoutTests/fast/animation/request-animation-frame-throttle-subframe-display-none-expected.txt
LayoutTests/fast/animation/request-animation-frame-throttle-subframe-display-none.html
LayoutTests/fast/animation/request-animation-frame-throttle-subframe-expected.txt
LayoutTests/fast/animation/request-animation-frame-throttle-subframe-zero-size-expected.txt
LayoutTests/fast/animation/request-animation-frame-throttle-subframe-zero-size.html
LayoutTests/fast/animation/request-animation-frame-throttle-subframe.html
LayoutTests/fast/animation/request-animation-frame-throttling-detached-iframe-expected.txt
LayoutTests/fast/animation/request-animation-frame-throttling-detached-iframe.html
LayoutTests/fast/animation/request-animation-frame-throttling-lowPowerMode-expected.txt
LayoutTests/fast/animation/request-animation-frame-throttling-lowPowerMode.html
LayoutTests/http/tests/frame-throttling/raf-throttle-in-cross-origin-subframe-expected.txt
LayoutTests/http/tests/frame-throttling/raf-throttle-in-cross-origin-subframe.html
LayoutTests/http/tests/frame-throttling/resources/requestAnimationFrame-frame.html
Source/WebCore/ChangeLog
Source/WebCore/Headers.cmake
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/animation/DocumentTimeline.cpp
Source/WebCore/animation/DocumentTimeline.h
Source/WebCore/dom/Document.cpp
Source/WebCore/dom/ScriptedAnimationController.cpp
Source/WebCore/dom/ScriptedAnimationController.h
Source/WebCore/page/FrameView.cpp
Source/WebCore/page/Page.cpp
Source/WebCore/page/Page.h
Source/WebCore/page/RenderingUpdateScheduler.cpp
Source/WebCore/page/RenderingUpdateScheduler.h
Source/WebCore/platform/graphics/AnimationFrameRate.h [new file with mode: 0644]
Source/WebCore/platform/graphics/DisplayRefreshMonitor.h
Source/WebCore/platform/graphics/DisplayRefreshMonitorManager.cpp
Source/WebCore/platform/graphics/DisplayRefreshMonitorManager.h
Source/WebCore/platform/graphics/GraphicsLayerUpdater.cpp
Source/WebCore/platform/graphics/ios/DisplayRefreshMonitorIOS.mm
Source/WebCore/testing/Internals.cpp
Source/WebCore/testing/Internals.h
Source/WebCore/testing/Internals.idl
Source/WebKit/ChangeLog
Source/WebKit/UIProcess/RemoteLayerTree/RemoteLayerTreeDrawingAreaProxy.h
Source/WebKit/UIProcess/RemoteLayerTree/RemoteLayerTreeDrawingAreaProxy.messages.in
Source/WebKit/UIProcess/RemoteLayerTree/RemoteLayerTreeDrawingAreaProxy.mm
Source/WebKit/WebProcess/WebPage/RemoteLayerTree/RemoteLayerTreeDisplayRefreshMonitor.h
Source/WebKit/WebProcess/WebPage/RemoteLayerTree/RemoteLayerTreeDisplayRefreshMonitor.mm
Source/WebKit/WebProcess/WebPage/RemoteLayerTree/RemoteLayerTreeDrawingArea.h
Source/WebKit/WebProcess/WebPage/RemoteLayerTree/RemoteLayerTreeDrawingArea.mm

index e8af798..4c5d4b8 100644 (file)
@@ -1,3 +1,31 @@
+2020-05-04  Said Abou-Hallawa  <sabouhallawa@apple.com>
+
+        Throttling requestAnimationFrame should be controlled by RenderingUpdateScheduler
+        https://bugs.webkit.org/show_bug.cgi?id=204713
+
+        Reviewed by Simon Fraser.
+
+        * fast/animation/request-animation-frame-throttle-inside-overflow-scroll-expected.txt:
+        * fast/animation/request-animation-frame-throttle-inside-overflow-scroll.html:
+        * fast/animation/request-animation-frame-throttle-subframe-display-none-expected.txt:
+        * fast/animation/request-animation-frame-throttle-subframe-display-none.html:
+        * fast/animation/request-animation-frame-throttle-subframe-expected.txt:
+        * fast/animation/request-animation-frame-throttle-subframe-zero-size-expected.txt:
+        * fast/animation/request-animation-frame-throttle-subframe-zero-size.html:
+        * fast/animation/request-animation-frame-throttle-subframe.html:
+        * fast/animation/request-animation-frame-throttling-detached-iframe-expected.txt:
+        * fast/animation/request-animation-frame-throttling-detached-iframe.html:
+        Replace the call isRequestAnimationFrameThrottled() by requestAnimationFrameThrottlingReasons().
+
+        * fast/animation/request-animation-frame-throttling-lowPowerMode-expected.txt:
+        * fast/animation/request-animation-frame-throttling-lowPowerMode.html:
+        Ensure the actual rAF interval is > 30ms for lowPowerMode.
+
+        * http/tests/frame-throttling/raf-throttle-in-cross-origin-subframe-expected.txt:
+        * http/tests/frame-throttling/raf-throttle-in-cross-origin-subframe.html:
+        * http/tests/frame-throttling/resources/requestAnimationFrame-frame.html:
+        Replace the call isRequestAnimationFrameThrottled() by requestAnimationFrameThrottlingReasons().
+
 2020-05-04  Eric Carlson  <eric.carlson@apple.com>
 
         (r261004) platform/mac/media/media-source/media-source-change-source.html is a flaky timeout
index 6cdbcb8..7f254d2 100644 (file)
@@ -4,10 +4,10 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE
 
 
 Frame is initially inside the viewport so requestAnimationFrame should not be throttled
-PASS testFrame.contentWindow.internals.isRequestAnimationFrameThrottled() is false
+PASS testFrame.contentWindow.internals.requestAnimationFrameThrottlingReasons() is "[Unthrottled]"
 Scrolling overflow.
 requestAnimationFrame should still not be throttled
-PASS testFrame.contentWindow.internals.isRequestAnimationFrameThrottled() is false
+PASS testFrame.contentWindow.internals.requestAnimationFrameThrottlingReasons() is "[Unthrottled]"
 PASS successfullyParsed is true
 
 TEST COMPLETE
index fbe5f06..d29e1b4 100644 (file)
@@ -14,7 +14,7 @@ function scrollOverflow()
     container.scrollLeft = window.innerWidth + 10;
 
     debug("requestAnimationFrame should still not be throttled");
-    shouldBeFalse("testFrame.contentWindow.internals.isRequestAnimationFrameThrottled()");
+    shouldBeEqualToString("testFrame.contentWindow.internals.requestAnimationFrameThrottlingReasons()", "[Unthrottled]");
     
     finishJSTest();
 }
@@ -23,7 +23,7 @@ function runTest()
 {
     testFrame = document.getElementById("testFrame");
     debug("Frame is initially inside the viewport so requestAnimationFrame should not be throttled");
-    shouldBeFalse("testFrame.contentWindow.internals.isRequestAnimationFrameThrottled()");
+    shouldBeEqualToString("testFrame.contentWindow.internals.requestAnimationFrameThrottlingReasons()", "[Unthrottled]");
     scrollOverflow();
 }
 </script>
index a85561c..2eb1bf2 100644 (file)
@@ -3,7 +3,7 @@ Tests that requestAnimationFrame is not throttled in subframes that are display:
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 
-PASS testFrame.contentWindow.internals.isRequestAnimationFrameThrottled() is false
+PASS testFrame.contentWindow.internals.requestAnimationFrameThrottlingReasons() is "[Unthrottled]"
 PASS successfullyParsed is true
 
 TEST COMPLETE
index 3004f88..0a528f1 100644 (file)
@@ -13,7 +13,7 @@ function runTest()
     // Force layout.
     document.body.offsetTop;
 
-    shouldBeFalse("testFrame.contentWindow.internals.isRequestAnimationFrameThrottled()");
+    shouldBeEqualToString("testFrame.contentWindow.internals.requestAnimationFrameThrottlingReasons()", "[Unthrottled]");
     finishJSTest();
 }
 </script>
index 05983b3..1b84d84 100644 (file)
@@ -4,20 +4,20 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE
 
 
 Frame is initially outside the viewport so requestAnimationFrame should be throttled
-PASS testFrame.contentWindow.internals.isRequestAnimationFrameThrottled() became true
-PASS internals.isRequestAnimationFrameThrottled() is false
-PASS testFrame.contentWindow.internals.isRequestAnimationFrameThrottled() is true
-PASS grandChildFrame.contentWindow.internals.isRequestAnimationFrameThrottled() is true
+PASS testFrame.contentWindow.internals.requestAnimationFrameThrottlingReasons() became "OutsideViewport"
+PASS internals.requestAnimationFrameThrottlingReasons() is "[Unthrottled]"
+PASS testFrame.contentWindow.internals.requestAnimationFrameThrottlingReasons() is "OutsideViewport"
+PASS grandChildFrame.contentWindow.internals.requestAnimationFrameThrottlingReasons() is "OutsideViewport"
 Scrolling frame into view.
 RequestAnimationFrame should no longer be throttled
-PASS internals.isRequestAnimationFrameThrottled() is false
-PASS grandChildFrame.contentWindow.internals.isRequestAnimationFrameThrottled() is false
-PASS testFrame.contentWindow.internals.isRequestAnimationFrameThrottled() is false
+PASS internals.requestAnimationFrameThrottlingReasons() is "[Unthrottled]"
+PASS grandChildFrame.contentWindow.internals.requestAnimationFrameThrottlingReasons() is "[Unthrottled]"
+PASS testFrame.contentWindow.internals.requestAnimationFrameThrottlingReasons() is "[Unthrottled]"
 Scrolling frame out of view again.
-PASS internals.isRequestAnimationFrameThrottled() is false
-PASS testFrame.contentWindow.internals.isRequestAnimationFrameThrottled() became true
-PASS testFrame.contentWindow.internals.isRequestAnimationFrameThrottled() is true
-PASS grandChildFrame.contentWindow.internals.isRequestAnimationFrameThrottled() is true
+PASS internals.requestAnimationFrameThrottlingReasons() is "[Unthrottled]"
+PASS testFrame.contentWindow.internals.requestAnimationFrameThrottlingReasons() became "OutsideViewport"
+PASS testFrame.contentWindow.internals.requestAnimationFrameThrottlingReasons() is "OutsideViewport"
+PASS grandChildFrame.contentWindow.internals.requestAnimationFrameThrottlingReasons() is "OutsideViewport"
 PASS successfullyParsed is true
 
 TEST COMPLETE
index 656a490..9f37fe7 100644 (file)
@@ -3,7 +3,7 @@ Tests that requestAnimationFrame is not throttled in zero-sized subframes
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 
-PASS testFrame.contentWindow.internals.isRequestAnimationFrameThrottled() is false
+PASS testFrame.contentWindow.internals.requestAnimationFrameThrottlingReasons() is "[Unthrottled]"
 PASS successfullyParsed is true
 
 TEST COMPLETE
index c133e1b..e3e76e0 100644 (file)
@@ -13,7 +13,7 @@ function runTest()
     // Force layout.
     document.body.offsetTop;
 
-    shouldBeFalse("testFrame.contentWindow.internals.isRequestAnimationFrameThrottled()");
+    shouldBeEqualToString("testFrame.contentWindow.internals.requestAnimationFrameThrottlingReasons()", "[Unthrottled]");
     finishJSTest();
 }
 </script>
index f8940f3..aa50fb8 100644 (file)
@@ -8,8 +8,8 @@ window.jsTestIsAsync = true;
 
 function checkSubframesThrottled()
 {
-    shouldBeTrue("testFrame.contentWindow.internals.isRequestAnimationFrameThrottled()");
-    shouldBeTrue("grandChildFrame.contentWindow.internals.isRequestAnimationFrameThrottled()");
+    shouldBeEqualToString("testFrame.contentWindow.internals.requestAnimationFrameThrottlingReasons()", "OutsideViewport");
+    shouldBeEqualToString("grandChildFrame.contentWindow.internals.requestAnimationFrameThrottlingReasons()", "OutsideViewport");
 
     finishJSTest();
 }
@@ -19,23 +19,23 @@ function scrollFrameOutOfView()
     debug("Scrolling frame out of view again.");
     window.scroll(0, 0);
 
-    shouldBeFalse("internals.isRequestAnimationFrameThrottled()");
-    shouldBecomeEqual("testFrame.contentWindow.internals.isRequestAnimationFrameThrottled()", "true", checkSubframesThrottled);
+    shouldBeEqualToString("internals.requestAnimationFrameThrottlingReasons()", "[Unthrottled]");
+    shouldBecomeEqualToString("testFrame.contentWindow.internals.requestAnimationFrameThrottlingReasons()", "OutsideViewport", checkSubframesThrottled);
 }
 
 function scrollFrameIntoView()
 {
-    shouldBeFalse("internals.isRequestAnimationFrameThrottled()");
-    shouldBeTrue("testFrame.contentWindow.internals.isRequestAnimationFrameThrottled()");
-    shouldBeTrue("grandChildFrame.contentWindow.internals.isRequestAnimationFrameThrottled()");
+    shouldBeEqualToString("internals.requestAnimationFrameThrottlingReasons()", "[Unthrottled]");
+    shouldBeEqualToString("testFrame.contentWindow.internals.requestAnimationFrameThrottlingReasons()", "OutsideViewport");
+    shouldBeEqualToString("grandChildFrame.contentWindow.internals.requestAnimationFrameThrottlingReasons()", "OutsideViewport");
 
     debug("Scrolling frame into view.");
     window.internals.scrollElementToRect(testFrame, 0, 0, 300, 300);
 
     debug("RequestAnimationFrame should no longer be throttled");
-    shouldBeFalse("internals.isRequestAnimationFrameThrottled()");
-    shouldBeFalse("grandChildFrame.contentWindow.internals.isRequestAnimationFrameThrottled()");
-    shouldBeFalse("testFrame.contentWindow.internals.isRequestAnimationFrameThrottled()");
+    shouldBeEqualToString("internals.requestAnimationFrameThrottlingReasons()", "[Unthrottled]");
+    shouldBeEqualToString("grandChildFrame.contentWindow.internals.requestAnimationFrameThrottlingReasons()", "[Unthrottled]");
+    shouldBeEqualToString("testFrame.contentWindow.internals.requestAnimationFrameThrottlingReasons()", "[Unthrottled]");
 
     scrollFrameOutOfView();
 }
@@ -45,7 +45,7 @@ function runTest()
     testFrame = document.getElementById("testFrame");
     grandChildFrame = testFrame.contentDocument.getElementById("grandChildFrame");
     debug("Frame is initially outside the viewport so requestAnimationFrame should be throttled");
-    shouldBecomeEqual("testFrame.contentWindow.internals.isRequestAnimationFrameThrottled()", "true", scrollFrameIntoView);
+    shouldBecomeEqualToString("testFrame.contentWindow.internals.requestAnimationFrameThrottlingReasons()", "OutsideViewport", scrollFrameIntoView);
 }
 
 var i = 0;
index 7286488..5536f4b 100644 (file)
@@ -3,27 +3,27 @@ Test that requestAnimationFrame gets the right throttling in an iframe when inse
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 
-PASS internals.isRequestAnimationFrameThrottled() is false
+PASS internals.requestAnimationFrameThrottlingReasons() is "[Unthrottled]"
 PASS internals.requestAnimationFrameInterval is 0.015
-PASS frame.contentWindow.internals.isRequestAnimationFrameThrottled() is false
+PASS frame.contentWindow.internals.requestAnimationFrameThrottlingReasons() is "[Unthrottled]"
 PASS frame.contentWindow.internals.requestAnimationFrameInterval is 0.015
 internals.setLowPowerModeEnabled(true);
-PASS internals.isRequestAnimationFrameThrottled() is true
+PASS internals.requestAnimationFrameThrottlingReasons() is "LowPowerMode"
 PASS internals.requestAnimationFrameInterval is 0.030
-PASS frame.contentWindow.internals.isRequestAnimationFrameThrottled() is true
+PASS frame.contentWindow.internals.requestAnimationFrameThrottlingReasons() is "LowPowerMode"
 PASS frame.contentWindow.internals.requestAnimationFrameInterval is 0.030
 frame.remove()
 document.body.appendChild(frame)
-PASS internals.isRequestAnimationFrameThrottled() is true
+PASS internals.requestAnimationFrameThrottlingReasons() is "LowPowerMode"
 PASS internals.requestAnimationFrameInterval is 0.030
-PASS frame.contentWindow.internals.isRequestAnimationFrameThrottled() is true
+PASS frame.contentWindow.internals.requestAnimationFrameThrottlingReasons() is "LowPowerMode"
 PASS frame.contentWindow.internals.requestAnimationFrameInterval is 0.030
 frame.remove()
 internals.setLowPowerModeEnabled(false);
-PASS internals.isRequestAnimationFrameThrottled() is false
+PASS internals.requestAnimationFrameThrottlingReasons() is "[Unthrottled]"
 PASS internals.requestAnimationFrameInterval is 0.015
 document.body.appendChild(frame)
-PASS frame.contentWindow.internals.isRequestAnimationFrameThrottled() is false
+PASS frame.contentWindow.internals.requestAnimationFrameThrottlingReasons() is "[Unthrottled]"
 PASS frame.contentWindow.internals.requestAnimationFrameInterval is 0.015
 PASS successfullyParsed is true
 
index 74280c3..753658d 100644 (file)
@@ -18,33 +18,33 @@ requestAnimationFrame(doWork);
 const frame = document.createElement("iframe");
 frame.src = "resources/frame-with-animation.html";
 frame.onload = function() {
-    shouldBeFalse("internals.isRequestAnimationFrameThrottled()");
+    shouldBeEqualToString("internals.requestAnimationFrameThrottlingReasons()", "[Unthrottled]");
     shouldBe("internals.requestAnimationFrameInterval", "0.015");
-    shouldBeFalse("frame.contentWindow.internals.isRequestAnimationFrameThrottled()");
+    shouldBeEqualToString("frame.contentWindow.internals.requestAnimationFrameThrottlingReasons()", "[Unthrottled]");
     shouldBe("frame.contentWindow.internals.requestAnimationFrameInterval", "0.015");
 
     evalAndLog("internals.setLowPowerModeEnabled(true);");
-    shouldBeTrue("internals.isRequestAnimationFrameThrottled()");
+    shouldBeEqualToString("internals.requestAnimationFrameThrottlingReasons()", "LowPowerMode");
     shouldBe("internals.requestAnimationFrameInterval", "0.030");
-    shouldBeTrue("frame.contentWindow.internals.isRequestAnimationFrameThrottled()");
+    shouldBeEqualToString("frame.contentWindow.internals.requestAnimationFrameThrottlingReasons()", "LowPowerMode");
     shouldBe("frame.contentWindow.internals.requestAnimationFrameInterval", "0.030");
     evalAndLog("frame.remove()");
 
     evalAndLog("document.body.appendChild(frame)");
     frame.onload = function() {
-        shouldBeTrue("internals.isRequestAnimationFrameThrottled()");
+        shouldBeEqualToString("internals.requestAnimationFrameThrottlingReasons()", "LowPowerMode");
         shouldBe("internals.requestAnimationFrameInterval", "0.030");
-        shouldBeTrue("frame.contentWindow.internals.isRequestAnimationFrameThrottled()");
+        shouldBeEqualToString("frame.contentWindow.internals.requestAnimationFrameThrottlingReasons()", "LowPowerMode");
         shouldBe("frame.contentWindow.internals.requestAnimationFrameInterval", "0.030");
 
         evalAndLog("frame.remove()");
         evalAndLog("internals.setLowPowerModeEnabled(false);");
-        shouldBeFalse("internals.isRequestAnimationFrameThrottled()");
+        shouldBeEqualToString("internals.requestAnimationFrameThrottlingReasons()", "[Unthrottled]");
         shouldBe("internals.requestAnimationFrameInterval", "0.015");
 
         evalAndLog("document.body.appendChild(frame)");
         frame.onload = function() {
-            shouldBeFalse("frame.contentWindow.internals.isRequestAnimationFrameThrottled()");
+            shouldBeEqualToString("frame.contentWindow.internals.requestAnimationFrameThrottlingReasons()", "[Unthrottled]");
             shouldBe("frame.contentWindow.internals.requestAnimationFrameInterval", "0.015");
             finishJSTest();
         }
index 9833ffb..353c21e 100644 (file)
@@ -3,23 +3,7 @@ Test that requestAnimationFrame gets throttled in low power mode.
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 
-PASS internals.isRequestAnimationFrameThrottled() is false
-PASS internals.requestAnimationFrameInterval is Infinity
-rAFHandle = requestAnimationFrame(doWork);
-PASS internals.isRequestAnimationFrameThrottled() is false
-PASS internals.requestAnimationFrameInterval is 0.015
-internals.setLowPowerModeEnabled(true);
-PASS internals.isRequestAnimationFrameThrottled() is true
-PASS internals.requestAnimationFrameInterval is 0.030
-cancelAnimationFrame(rAFHandle);
-PASS internals.isRequestAnimationFrameThrottled() is true
-PASS internals.requestAnimationFrameInterval is 0.030
-rAFHandle = requestAnimationFrame(doWork);
-PASS internals.isRequestAnimationFrameThrottled() is true
-PASS internals.requestAnimationFrameInterval is 0.030
-internals.setLowPowerModeEnabled(false);
-PASS internals.isRequestAnimationFrameThrottled() is false
-PASS internals.requestAnimationFrameInterval is 0.015
+PASS farmesPerSecond < 35 is true
 PASS successfullyParsed is true
 
 TEST COMPLETE
index dbae09f..a93c69c 100644 (file)
@@ -1,36 +1,31 @@
 <!DOCTYPE html>
 <html>
 <body>
-<script src="../../resources/js-test-pre.js"></script>
-<script>
-description("Test that requestAnimationFrame gets throttled in low power mode.");
+    <script src="../../resources/js-test-pre.js"></script>
+    <script>
+        description("Test that requestAnimationFrame gets throttled in low power mode.");
+        window.jsTestIsAsync = true;
 
-let rAFHandle;
-let i = 0;
-function doWork()
-{
-    i++;
-    rAFHandle = requestAnimationFrame(doWork);
-}
+        if (window.internals)
+            internals.setLowPowerModeEnabled(true);
 
-shouldBeFalse("internals.isRequestAnimationFrameThrottled()");
-shouldBe("internals.requestAnimationFrameInterval", "Infinity");
-evalAndLog("rAFHandle = requestAnimationFrame(doWork);");
-shouldBeFalse("internals.isRequestAnimationFrameThrottled()");
-shouldBe("internals.requestAnimationFrameInterval", "0.015");
-evalAndLog("internals.setLowPowerModeEnabled(true);");
-shouldBeTrue("internals.isRequestAnimationFrameThrottled()");
-shouldBe("internals.requestAnimationFrameInterval", "0.030");
-evalAndLog("cancelAnimationFrame(rAFHandle);");
-shouldBeTrue("internals.isRequestAnimationFrameThrottled()");
-shouldBe("internals.requestAnimationFrameInterval", "0.030");
-evalAndLog("rAFHandle = requestAnimationFrame(doWork);");
-shouldBeTrue("internals.isRequestAnimationFrameThrottled()");
-shouldBe("internals.requestAnimationFrameInterval", "0.030");
-evalAndLog("internals.setLowPowerModeEnabled(false);");
-shouldBeFalse("internals.isRequestAnimationFrameThrottled()");
-shouldBe("internals.requestAnimationFrameInterval", "0.015");
-</script>
-<script src="../../resources/js-test-post.js"></script>
+        var start = null;
+        var farmesPerSecond = 0;
+        function doWork(timestamp) {
+            if (!start)
+                start = timestamp;
+            if (timestamp - start < 1000) {
+                ++farmesPerSecond;
+                window.requestAnimationFrame(doWork);
+            }
+            else {
+                // The LowPowerMode throttling interval = 30_ms. The frame rate ~= 33.3 fps.
+                shouldBeTrue("farmesPerSecond < 35");
+                finishJSTest();
+            }
+        }
+        window.requestAnimationFrame(doWork);
+    </script>
+    <script src="../../resources/js-test-post.js"></script>
 </body>
 </html>
index a93e803..1e6198b 100644 (file)
@@ -6,16 +6,16 @@ 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"
+Received message: throttled[cross]: NonInteractiveCrossOriginFrame
+Received message: throttled[same]: [Unthrottled]
+PASS throttledState['cross'] is "NonInteractiveCrossOriginFrame"
+PASS throttledState['same'] is "[Unthrottled]"
 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"
+Received message: throttled[cross]: [Unthrottled]
+Received message: throttled[same]: [Unthrottled]
+PASS throttledState['cross'] is "[Unthrottled]"
+PASS throttledState['same'] is "[Unthrottled]"
 PASS successfullyParsed is true
 
 TEST COMPLETE
index 70f1dfa..82900d3 100644 (file)
 
     function checkInitiallyThrottled()
     {
-        shouldBeEqualToString("throttledState['cross']", "true");
-        shouldBeEqualToString("throttledState['same']", "false");
+        shouldBeEqualToString("throttledState['cross']", "NonInteractiveCrossOriginFrame");
+        shouldBeEqualToString("throttledState['same']", "[Unthrottled]");
         interactWithSubframes();
     }
 
     function checkUnthrottledAfterInteraction()
     {
-        shouldBeEqualToString("throttledState['cross']", "false");
-        shouldBeEqualToString("throttledState['same']", "false");
+        shouldBeEqualToString("throttledState['cross']", "[Unthrottled]");
+        shouldBeEqualToString("throttledState['same']", "[Unthrottled]");
         finishJSTest();
     }
 
@@ -75,7 +75,7 @@
             return;
         }
 
-        var re = /throttled\[(\w+)\]: (true|false)/;
+        var re = /throttled\[(\w+)\]: ((\w+)(\|\w+)*|\[Unthrottled\])/;
         var match = re.exec(message.data);
         if (match) {
             frameID = match[1];
index ce4598d..dab771c 100644 (file)
@@ -18,7 +18,7 @@
             if (match) {
                 var frameId = match[1];
                 if (window.internals)
-                    parent.window.postMessage("throttled[" + frameId + "]: " + internals.isRequestAnimationFrameThrottled(), "*");
+                    parent.window.postMessage("throttled[" + frameId + "]: " + internals.requestAnimationFrameThrottlingReasons(), "*");
             }
         }
 
index ba47ca6..f45f083 100644 (file)
@@ -1,3 +1,162 @@
+2020-05-04  Said Abou-Hallawa  <sabouhallawa@apple.com>
+
+        Throttling requestAnimationFrame should be controlled by RenderingUpdateScheduler
+        https://bugs.webkit.org/show_bug.cgi?id=204713
+
+        Reviewed by Simon Fraser.
+
+        rAF and Page rendering were managed by two different timers. Throttling
+        rAF was implemented by changing its timer. After r242624, RenderingUpdate
+        steps have been managed by RenderingUpdateScheduler. This means rAF is
+        now serviced by the preferredFramesPerSecond which is 60 fps regardless
+        it's throttled or not. Moreover the rAF throttling timer was mistakenly
+        kept and it has been running under the old assumption which is: rAF is
+        serviced by a timer only. This means rAF will be serviced by its timer
+        and by the RenderingUpdate steps at the same time when it is supposed to
+        throttle. This will make it fire more than 60 fps in cases which it is
+        supposed to run less than 60 fps.
+
+        The solution is to have two throttling types:
+
+        1) Page throttling (or full throttling): This slows down all the steps
+           of RenderingUpdate for the main document and all the sub-documents.
+           Page throttling reasons are:
+           -- VisuallyIdle: Aggressive throttling.
+           -- LowPowerMode: Half speed throttling.
+        2) Document throttling (or partial throttling): This only slows down the
+           rAF of a certain document. Document throttling reasons are:
+           -- OutsideViewport: Aggressive throttling.
+           -- NonInteractedCrossOriginFrame: Half speed throttling.
+
+        RenderingUpdate steps will still be managed by RenderingUpdateScheduler
+        which can be throttled. The assumption is none of these steps will need
+        to run faster than the Page preferredFramesPerSecond. If rAF wants to
+        run slower than the Page because of a Document throttling reason, no rAF
+        callbacks will be serviced before its preferredFrameInterval has elapsed.
+
+        In this patch, "Half speed throttling" is only implemented for the Page
+        and the Document throttling. The "Aggressive throttling" will be done in
+        following patches. Page rendering was never throttled before. We need to
+        make sure this is not going to affect PLT. Some tests need to be changed
+        and new tests need to be written.  All of the throttling tests checks the
+        state of the code but none of them checks the real user's experience.
+
+        * Headers.cmake:
+        * WebCore.xcodeproj/project.pbxproj:
+
+        * animation/DocumentTimeline.cpp:
+        (WebCore::DocumentTimeline::animationInterval const):
+        (WebCore::DocumentTimeline::updateThrottlingState): Deleted.
+        * animation/DocumentTimeline.h:
+        There is no need to have DocumentTimeline throttling. It is already 
+        throttled when the page RenderingUpdate is throttled.
+
+        * dom/Document.cpp:
+        (WebCore::Document::requestAnimationFrame):
+        (WebCore::Document::updateLastHandledUserGestureTimestamp):
+        LowPowerMode throttling is now handled by the Page. So remove its handling
+        from the Document.
+
+        * dom/ScriptedAnimationController.cpp:
+        (WebCore::ScriptedAnimationController::ScriptedAnimationController):
+        (WebCore::ScriptedAnimationController::page const):
+        (WebCore::ScriptedAnimationController::interval const):
+        (WebCore::ScriptedAnimationController::preferredScriptedAnimationInterval const):
+        (WebCore::ScriptedAnimationController::throttlingReasons const):
+        (WebCore::ScriptedAnimationController::isThrottledRelativeToPage const):
+        (WebCore::ScriptedAnimationController::shouldRescheduleRequestAnimationFrame const):
+        (WebCore::ScriptedAnimationController::registerCallback):
+        (WebCore::ScriptedAnimationController::cancelCallback):
+        (WebCore::ScriptedAnimationController::serviceRequestAnimationFrameCallbacks):
+        (WebCore::ScriptedAnimationController::scheduleAnimation):
+        (WebCore::throttlingReasonToString): Deleted.
+        (WebCore::throttlingReasonsToString): Deleted.
+        (WebCore::ScriptedAnimationController::addThrottlingReason): Deleted.
+        (WebCore::ScriptedAnimationController::removeThrottlingReason): Deleted.
+        (WebCore::ScriptedAnimationController::isThrottled const): Deleted.
+        (WebCore::ScriptedAnimationController::animationTimerFired): Deleted.
+        * dom/ScriptedAnimationController.h:
+        (WebCore::ScriptedAnimationController::addThrottlingReason):
+        (WebCore::ScriptedAnimationController::removeThrottlingReason):
+        Get rid of the rAF throttling timer. Service the rAF callback only when
+        the period from the current time stamp till the last service time stamp
+        is greater than the preferred rAF interval.
+
+        * page/FrameView.cpp:
+        (WebCore::FrameView::updateScriptedAnimationsAndTimersThrottlingState):
+        ThrottlingReason is now defined outside ScriptedAnimationController.
+
+        * page/Page.cpp:
+        (WebCore::m_loadsFromNetwork):
+        (WebCore::Page::setLowPowerModeEnabledOverrideForTesting):
+
+        (WebCore::Page::preferredRenderingUpdateInterval const):
+        Calculate the preferred RenderingUpdate interval from the throttling
+        reasons.
+
+        (WebCore::Page::setIsVisuallyIdleInternal):
+        (WebCore::Page::handleLowModePowerChange):
+        Call adjustRenderingUpdateFrequency() when isLowPowerModeEnabled or 
+        IsVisuallyIdle is toggled.
+
+        (WebCore::Page::isLowPowerModeEnabled const): Deleted.
+        (WebCore::updateScriptedAnimationsThrottlingReason): Deleted.
+        * page/Page.h:
+        (WebCore::Page::isLowPowerModeEnabled const):
+        (WebCore::Page::throttlingReasons const):
+        (WebCore::Page::canUpdateThrottlingReason const):
+
+        * page/RenderingUpdateScheduler.cpp:
+        (WebCore::RenderingUpdateScheduler::setPreferredFramesPerSecond):
+        (WebCore::RenderingUpdateScheduler::scheduleAnimation):
+        (WebCore::RenderingUpdateScheduler::adjustRenderingUpdateFrequency):
+        Change the preferredFramesPerSecond of the DisplayRefreshMonitor if the
+        throttling is not aggressive e.g. 10_s. Otherwise use the timer.
+
+        (WebCore::RenderingUpdateScheduler::scheduleTimedRenderingUpdate):
+        Call adjustFramesPerSecond() when DisplayRefreshMonitor is created.
+
+        (WebCore::RenderingUpdateScheduler::startTimer):
+        * page/RenderingUpdateScheduler.h:
+        * platform/graphics/AnimationFrameRate.h: Added.
+        (WebCore::preferredFrameInterval):
+        (WebCore::preferredFramesPerSecond):
+        (WebCore::operator<<):
+        Push names of ThrottlingReasons to a TextStream.
+
+        * platform/graphics/DisplayRefreshMonitor.h:
+        (WebCore::DisplayRefreshMonitor::setPreferredFramesPerSecond):
+        * platform/graphics/DisplayRefreshMonitorManager.cpp:
+        (WebCore::DisplayRefreshMonitorManager::monitorForClient):
+        Rename createMonitorForClient() to monitorForClient() since it may return
+        a cached DisplayRefreshMonitor.
+
+        (WebCore::DisplayRefreshMonitorManager::setPreferredFramesPerSecond):
+        (WebCore::DisplayRefreshMonitorManager::scheduleAnimation):
+        (WebCore::DisplayRefreshMonitorManager::windowScreenDidChange):
+        No need to call registerClient(). This function was just ensuring the
+        DisplayRefreshMonitor is created. scheduleAnimation() does the same thing.
+
+        (WebCore::DisplayRefreshMonitorManager::createMonitorForClient): Deleted.
+        (WebCore::DisplayRefreshMonitorManager::registerClient): Deleted.
+        * platform/graphics/DisplayRefreshMonitorManager.h:
+        (WebCore::DisplayRefreshMonitorManager::DisplayRefreshMonitorManager): Deleted.
+
+        * platform/graphics/GraphicsLayerUpdater.cpp:
+        (WebCore::GraphicsLayerUpdater::GraphicsLayerUpdater):
+        * platform/graphics/ios/DisplayRefreshMonitorIOS.mm:
+        (-[WebDisplayLinkHandler setPreferredFramesPerSecond:]):
+        Set the preferredFramesPerSecond of the CADisplayLink.
+
+        * testing/Internals.cpp:
+        (WebCore::Internals::requestAnimationFrameThrottlingReasons const):
+        (WebCore::Internals::isRequestAnimationFrameThrottled const): Deleted.
+        * testing/Internals.h:
+        * testing/Internals.idl:
+        Replace isRequestAnimationFrameThrottled() which returns a boolean by
+        requestAnimationFrameThrottlingReasons() which returns a string. The 
+        string represents the throttling reasons.
+
 2020-05-04  Darin Adler  <darin@apple.com>
 
         Remove unneeded USE(MEDIAREMOTE)
index 65c7caf..36302df 100644 (file)
@@ -1072,6 +1072,7 @@ set(WebCore_PRIVATE_FRAMEWORK_HEADERS
 
     platform/graphics/AlphaPremultiplication.h
     platform/graphics/ANGLEWebKitBridge.h
+    platform/graphics/AnimationFrameRate.h
     platform/graphics/AudioTrackPrivate.h
     platform/graphics/BitmapImage.h
     platform/graphics/Color.h
index b1cb5fa..2c8b2e2 100644 (file)
                727A7F3A24078B84004D2931 /* ImageBufferIOSurfaceBackend.h in Headers */ = {isa = PBXBuildFile; fileRef = 72BAC3AB23E1E544008D741C /* ImageBufferIOSurfaceBackend.h */; settings = {ATTRIBUTES = (Private, ); }; };
                7299BC6723D6A53200CC6883 /* AlphaPremultiplication.h in Headers */ = {isa = PBXBuildFile; fileRef = 7299BC6423D686A600CC6883 /* AlphaPremultiplication.h */; settings = {ATTRIBUTES = (Private, ); }; };
                7299BC6823D6A53E00CC6883 /* RenderingMode.h in Headers */ = {isa = PBXBuildFile; fileRef = 7299BC6623D686C600CC6883 /* RenderingMode.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               72A73BEF245A3F90001C9D03 /* AnimationFrameRate.h in Headers */ = {isa = PBXBuildFile; fileRef = 722A815C238FD50500C00583 /* AnimationFrameRate.h */; settings = {ATTRIBUTES = (Private, ); }; };
                72BAC3AE23E1F0B0008D741C /* ImageBufferBackend.h in Headers */ = {isa = PBXBuildFile; fileRef = 72BAC3A523E17328008D741C /* ImageBufferBackend.h */; settings = {ATTRIBUTES = (Private, ); }; };
                7553CFE8108F473F00EA281E /* TimelineRecordFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = 7553CFE6108F473F00EA281E /* TimelineRecordFactory.h */; };
                75793E840D0CE0B3007FC0AC /* MessageEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 75793E810D0CE0B3007FC0AC /* MessageEvent.h */; };
                71FF851822A3F81F005D5959 /* NavigatorMaxTouchPoints.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = NavigatorMaxTouchPoints.idl; sourceTree = "<group>"; };
                721443452240C8BA00F12FF7 /* SVGAnimatedValueProperty.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SVGAnimatedValueProperty.h; sourceTree = "<group>"; };
                721443462240CAD200F12FF7 /* SVGValueProperty.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SVGValueProperty.h; sourceTree = "<group>"; };
+               722A815C238FD50500C00583 /* AnimationFrameRate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AnimationFrameRate.h; sourceTree = "<group>"; };
                724ED3291A3A7E5400F5F13C /* EXTBlendMinMax.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EXTBlendMinMax.cpp; sourceTree = "<group>"; };
                724ED32A1A3A7E5400F5F13C /* EXTBlendMinMax.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EXTBlendMinMax.h; sourceTree = "<group>"; };
                724ED32B1A3A7E5400F5F13C /* EXTBlendMinMax.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = EXTBlendMinMax.idl; sourceTree = "<group>"; };
                                7299BC6423D686A600CC6883 /* AlphaPremultiplication.h */,
                                490707E41219C04300D90E51 /* ANGLEWebKitBridge.cpp */,
                                490707E51219C04300D90E51 /* ANGLEWebKitBridge.h */,
+                               722A815C238FD50500C00583 /* AnimationFrameRate.h */,
                                BEF29EE91715DD0900C4B4C9 /* AudioTrackPrivate.h */,
                                A89943270B42338700D7C802 /* BitmapImage.cpp */,
                                A89943260B42338700D7C802 /* BitmapImage.h */,
                                71E2C42621C935280024F8C8 /* AnimationEffectPhase.h in Headers */,
                                319848011A1D817B00A13318 /* AnimationEvent.h in Headers */,
                                711AD126236D86E5006FF37C /* AnimationEventBase.h in Headers */,
+                               72A73BEF245A3F90001C9D03 /* AnimationFrameRate.h in Headers */,
                                49E912AD0EFAC906009D0CAF /* AnimationList.h in Headers */,
                                714C7C661FDAD2A100F2BEE1 /* AnimationPlaybackEvent.h in Headers */,
                                714C7C671FDAD2A900F2BEE1 /* AnimationPlaybackEventInit.h in Headers */,
index 461a5f7..63074d0 100644 (file)
@@ -46,9 +46,6 @@
 #include "Settings.h"
 #include <JavaScriptCore/VM.h>
 
-static const Seconds defaultAnimationInterval { 15_ms };
-static const Seconds throttledAnimationInterval { 30_ms };
-
 namespace WebCore {
 
 Ref<DocumentTimeline> DocumentTimeline::create(Document& document)
@@ -214,16 +211,11 @@ Vector<RefPtr<WebAnimation>> DocumentTimeline::getAnimations() const
     return animations;
 }
 
-void DocumentTimeline::updateThrottlingState()
-{
-    scheduleAnimationResolution();
-}
-
 Seconds DocumentTimeline::animationInterval() const
 {
     if (!m_document || !m_document->page())
         return Seconds::infinity();
-    return m_document->page()->isLowPowerModeEnabled() ? throttledAnimationInterval : defaultAnimationInterval;
+    return m_document->page()->preferredRenderingUpdateInterval();
 }
 
 void DocumentTimeline::suspendAnimations()
index 02a0d42..a09a435 100644 (file)
@@ -80,7 +80,6 @@ public:
     AnimationEvents prepareForPendingAnimationEventsDispatch();
     void documentDidUpdateAnimationsAndSendEvents();
 
-    void updateThrottlingState();
     WEBCORE_EXPORT Seconds animationInterval() const;
     WEBCORE_EXPORT void suspendAnimations();
     WEBCORE_EXPORT void resumeAnimations();
index ce7abb3..2cc9a86 100644 (file)
@@ -6553,11 +6553,8 @@ int Document::requestAnimationFrame(Ref<RequestAnimationFrameCallback>&& callbac
         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);
+            m_scriptedAnimationController->addThrottlingReason(ThrottlingReason::NonInteractedCrossOriginFrame);
     }
 
     return m_scriptedAnimationController->registerCallback(WTFMove(callback));
@@ -6796,7 +6793,7 @@ void Document::updateLastHandledUserGestureTimestamp(MonotonicTime 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);
+        m_scriptedAnimationController->removeThrottlingReason(ThrottlingReason::NonInteractedCrossOriginFrame);
     }
 
     // DOM Timer alignment may depend on the user having interacted with the document.
index c3d7107..b473591 100644 (file)
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2011 Google Inc. All Rights Reserved.
+ * Copyright (C) 2020 Apple Inc.  All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
 #include "config.h"
 #include "ScriptedAnimationController.h"
 
-#include "Chrome.h"
-#include "ChromeClient.h"
-#include "CustomHeaderFields.h"
-#include "DOMWindow.h"
-#include "Document.h"
-#include "DocumentLoader.h"
-#include "Frame.h"
-#include "FrameView.h"
 #include "InspectorInstrumentation.h"
-#include "Logging.h"
 #include "Page.h"
-#include "Performance.h"
 #include "Quirks.h"
 #include "RequestAnimationFrameCallback.h"
 #include "Settings.h"
-#include <algorithm>
 #include <wtf/Ref.h>
 #include <wtf/SystemTracing.h>
-#include <wtf/text/StringBuilder.h>
-
-// Allow a little more than 60fps to make sure we can at least hit that frame rate.
-static const Seconds fullSpeedAnimationInterval { 15_ms };
-// Allow a little more than 30fps to make sure we can at least hit that frame rate.
-static const Seconds halfSpeedThrottlingAnimationInterval { 30_ms };
-static const Seconds aggressiveThrottlingAnimationInterval { 10_s };
-
-#define RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(page() && page()->isAlwaysOnLoggingAllowed(), PerformanceLogging, "%p - ScriptedAnimationController::" fmt, this, ##__VA_ARGS__)
 
 namespace WebCore {
 
 ScriptedAnimationController::ScriptedAnimationController(Document& document)
     : m_document(makeWeakPtr(document))
-    , m_animationTimer(*this, &ScriptedAnimationController::animationTimerFired)
 {
 }
 
@@ -85,112 +65,70 @@ void ScriptedAnimationController::resume()
         scheduleAnimation();
 }
 
-#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR) && !RELEASE_LOG_DISABLED
-
-static const char* throttlingReasonToString(ScriptedAnimationController::ThrottlingReason reason)
+Page* ScriptedAnimationController::page() const
 {
-    switch (reason) {
-    case ScriptedAnimationController::ThrottlingReason::VisuallyIdle:
-        return "VisuallyIdle";
-    case ScriptedAnimationController::ThrottlingReason::OutsideViewport:
-        return "OutsideViewport";
-    case ScriptedAnimationController::ThrottlingReason::LowPowerMode:
-        return "LowPowerMode";
-    case ScriptedAnimationController::ThrottlingReason::NonInteractedCrossOriginFrame:
-        return "NonInteractiveCrossOriginFrame";
-    }
-    RELEASE_ASSERT_NOT_REACHED();
-    return "";
+    return m_document ? m_document->page() : nullptr;
 }
 
-static String throttlingReasonsToString(OptionSet<ScriptedAnimationController::ThrottlingReason> reasons)
+Seconds ScriptedAnimationController::interval() const
 {
-    if (reasons.isEmpty())
-        return "[Unthrottled]"_s;
-
-    StringBuilder builder;
-    for (auto reason : reasons) {
-        if (!builder.isEmpty())
-            builder.append('|');
-        builder.append(throttlingReasonToString(reason));
-    }
-    return builder.toString();
+    if (auto* page = this->page())
+        return std::max(preferredScriptedAnimationInterval(), page->preferredRenderingUpdateInterval());
+    return FullSpeedAnimationInterval;
 }
 
-#endif
-
-void ScriptedAnimationController::addThrottlingReason(ThrottlingReason reason)
+Seconds ScriptedAnimationController::preferredScriptedAnimationInterval() const
 {
-#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
-    if (m_throttlingReasons.contains(reason))
-        return;
-
-    m_throttlingReasons.add(reason);
-
-    RELEASE_LOG_IF_ALLOWED("addThrottlingReason(%s) -> %s", throttlingReasonToString(reason), throttlingReasonsToString(m_throttlingReasons).utf8().data());
-
-    if (m_animationTimer.isActive()) {
-        m_animationTimer.stop();
-        scheduleAnimation();
-    }
-#else
-    UNUSED_PARAM(reason);
-#endif
+    if (auto* page = this->page())
+        return preferredFrameInterval(m_throttlingReasons);
+    return FullSpeedAnimationInterval;
 }
 
-void ScriptedAnimationController::removeThrottlingReason(ThrottlingReason reason)
+OptionSet<ThrottlingReason> ScriptedAnimationController::throttlingReasons() const
 {
-#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
-    if (!m_throttlingReasons.contains(reason))
-        return;
-
-    m_throttlingReasons.remove(reason);
-
-    RELEASE_LOG_IF_ALLOWED("removeThrottlingReason(%s) -> %s", throttlingReasonToString(reason), throttlingReasonsToString(m_throttlingReasons).utf8().data());
-
-    if (m_animationTimer.isActive()) {
-        m_animationTimer.stop();
-        scheduleAnimation();
-    }
-#else
-    UNUSED_PARAM(reason);
-#endif
+    if (auto* page = this->page())
+        return page->throttlingReasons() | m_throttlingReasons;
+    return { };
 }
 
-bool ScriptedAnimationController::isThrottled() const
+bool ScriptedAnimationController::isThrottledRelativeToPage() const
 {
-#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
-    return !m_throttlingReasons.isEmpty();
-#else
+    if (auto* page = this->page())
+        return preferredScriptedAnimationInterval() > page->preferredRenderingUpdateInterval();
     return false;
-#endif
+}
+
+bool ScriptedAnimationController::shouldRescheduleRequestAnimationFrame(ReducedResolutionSeconds timestamp) const
+{
+    return isThrottledRelativeToPage() && (timestamp - m_lastAnimationFrameTimestamp < preferredScriptedAnimationInterval());
 }
 
 ScriptedAnimationController::CallbackId ScriptedAnimationController::registerCallback(Ref<RequestAnimationFrameCallback>&& callback)
 {
-    ScriptedAnimationController::CallbackId id = ++m_nextCallbackId;
+    CallbackId callbackId = ++m_nextCallbackId;
     callback->m_firedOrCancelled = false;
-    callback->m_id = id;
+    callback->m_id = callbackId;
     m_callbacks.append(WTFMove(callback));
 
     if (m_document)
-        InspectorInstrumentation::didRequestAnimationFrame(*m_document, id);
+        InspectorInstrumentation::didRequestAnimationFrame(*m_document, callbackId);
 
     if (!m_suspendCount)
         scheduleAnimation();
-    return id;
+    return callbackId;
 }
 
-void ScriptedAnimationController::cancelCallback(CallbackId id)
+void ScriptedAnimationController::cancelCallback(CallbackId callbackId)
 {
-    for (size_t i = 0; i < m_callbacks.size(); ++i) {
-        if (m_callbacks[i]->m_id == id) {
-            m_callbacks[i]->m_firedOrCancelled = true;
-            InspectorInstrumentation::didCancelAnimationFrame(*m_document, id);
-            m_callbacks.remove(i);
-            return;
-        }
-    }
+    bool cancelled = m_callbacks.removeFirstMatching([callbackId](auto& callback) {
+        if (callback->m_id != callbackId)
+            return false;
+        callback->m_firedOrCancelled = true;
+        return true;
+    });
+
+    if (cancelled && m_document)
+        InspectorInstrumentation::didCancelAnimationFrame(*m_document, callbackId);
 }
 
 void ScriptedAnimationController::serviceRequestAnimationFrameCallbacks(ReducedResolutionSeconds timestamp)
@@ -198,6 +136,11 @@ void ScriptedAnimationController::serviceRequestAnimationFrameCallbacks(ReducedR
     if (!m_callbacks.size() || m_suspendCount || !requestAnimationFrameEnabled())
         return;
 
+    if (shouldRescheduleRequestAnimationFrame(timestamp)) {
+        scheduleAnimation();
+        return;
+    }
+    
     TraceScope tracingScope(RAFCallbackStart, RAFCallbackEnd);
 
     auto highResNowMs = std::round(1000 * timestamp.seconds());
@@ -228,72 +171,19 @@ void ScriptedAnimationController::serviceRequestAnimationFrameCallbacks(ReducedR
         return callback->m_firedOrCancelled;
     });
 
+    m_lastAnimationFrameTimestamp = timestamp;
+
     if (m_callbacks.size())
         scheduleAnimation();
 }
 
-Seconds ScriptedAnimationController::interval() const
-{
-#if 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;
-}
-
-Page* ScriptedAnimationController::page() const
-{
-    return m_document ? m_document->page() : nullptr;
-}
-
 void ScriptedAnimationController::scheduleAnimation()
 {
     if (!requestAnimationFrameEnabled())
         return;
 
-#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
-    if (!m_isUsingTimer && !isThrottled()) {
-        if (auto* page = this->page()) {
-            page->renderingUpdateScheduler().scheduleTimedRenderingUpdate();
-            return;
-        }
-
-        m_isUsingTimer = true;
-    }
-#endif
-    if (m_animationTimer.isActive())
-        return;
-
-    Seconds animationInterval = interval();
-    Seconds scheduleDelay = std::max(animationInterval - (m_document->domWindow()->nowTimestamp() - m_lastAnimationFrameTimestamp), 0_s);
-
-    if (isThrottled()) {
-        // FIXME: not ideal to snapshot time both in now() and nowTimestamp(), the latter of which also has reduced resolution.
-        MonotonicTime now = MonotonicTime::now();
-
-        MonotonicTime fireTime = now + scheduleDelay;
-        Seconds alignmentInterval = 10_ms;
-        // Snap to the nearest alignmentInterval.
-        Seconds alignment = (fireTime + alignmentInterval / 2) % alignmentInterval;
-        MonotonicTime alignedFireTime = fireTime - alignment;
-        scheduleDelay = alignedFireTime - now;
-    }
-
-    m_animationTimer.startOneShot(scheduleDelay);
-}
-
-void ScriptedAnimationController::animationTimerFired()
-{
-    m_lastAnimationFrameTimestamp = m_document->domWindow()->nowTimestamp();
-    serviceRequestAnimationFrameCallbacks(m_lastAnimationFrameTimestamp);
+    if (auto* page = this->page())
+        page->renderingUpdateScheduler().scheduleTimedRenderingUpdate();
 }
 
 }
index dbcdbed..040dc1c 100644 (file)
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2011 Google Inc. All Rights Reserved.
+ * Copyright (C) 2020 Apple Inc.  All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -25,7 +26,7 @@
 
 #pragma once
 
-#include "DOMHighResTimeStamp.h"
+#include "AnimationFrameRate.h"
 #include "ReducedResolutionSeconds.h"
 #include "Timer.h"
 #include <wtf/OptionSet.h>
@@ -50,49 +51,38 @@ public:
     void clearDocumentPointer() { m_document = nullptr; }
     bool requestAnimationFrameEnabled() const;
 
-    typedef int CallbackId;
-
-    CallbackId registerCallback(Ref<RequestAnimationFrameCallback>&&);
-    void cancelCallback(CallbackId);
-    void serviceRequestAnimationFrameCallbacks(ReducedResolutionSeconds);
+    WEBCORE_EXPORT Seconds interval() const;
+    WEBCORE_EXPORT OptionSet<ThrottlingReason> throttlingReasons() const;
 
     void suspend();
     void resume();
 
-    enum class ThrottlingReason {
-        VisuallyIdle                    = 1 << 0,
-        OutsideViewport                 = 1 << 1,
-        LowPowerMode                    = 1 << 2,
-        NonInteractedCrossOriginFrame   = 1 << 3,
-    };
-    void addThrottlingReason(ThrottlingReason);
-    void removeThrottlingReason(ThrottlingReason);
+    void addThrottlingReason(ThrottlingReason reason) { m_throttlingReasons.add(reason); }
+    void removeThrottlingReason(ThrottlingReason reason) { m_throttlingReasons.remove(reason); }
 
-    WEBCORE_EXPORT bool isThrottled() const;
-    WEBCORE_EXPORT Seconds interval() const;
+    using CallbackId = int;
+    CallbackId registerCallback(Ref<RequestAnimationFrameCallback>&&);
+    void cancelCallback(CallbackId);
+    void serviceRequestAnimationFrameCallbacks(ReducedResolutionSeconds);
 
 private:
     ScriptedAnimationController(Document&);
 
-    void scheduleAnimation();
-    void animationTimerFired();
-
     Page* page() const;
+    Seconds preferredScriptedAnimationInterval() const;
+    bool isThrottledRelativeToPage() const;
+    bool shouldRescheduleRequestAnimationFrame(ReducedResolutionSeconds) const;
+    void scheduleAnimation();
 
-    typedef Vector<RefPtr<RequestAnimationFrameCallback>> CallbackList;
+    using CallbackList = Vector<RefPtr<RequestAnimationFrameCallback>>;
     CallbackList m_callbacks;
 
     WeakPtr<Document> m_document;
     CallbackId m_nextCallbackId { 0 };
     int m_suspendCount { 0 };
 
-    Timer m_animationTimer;
-    ReducedResolutionSeconds m_lastAnimationFrameTimestamp { 0 };
-
-#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
+    ReducedResolutionSeconds m_lastAnimationFrameTimestamp;
     OptionSet<ThrottlingReason> m_throttlingReasons;
-    bool m_isUsingTimer { false };
-#endif
 };
 
 } // namespace WebCore
index 41cfef6..9049bbe 100644 (file)
@@ -2530,9 +2530,9 @@ void FrameView::updateScriptedAnimationsAndTimersThrottlingState(const IntRect&
 
     if (auto* scriptedAnimationController = document->scriptedAnimationController()) {
         if (shouldThrottle)
-            scriptedAnimationController->addThrottlingReason(ScriptedAnimationController::ThrottlingReason::OutsideViewport);
+            scriptedAnimationController->addThrottlingReason(ThrottlingReason::OutsideViewport);
         else
-            scriptedAnimationController->removeThrottlingReason(ScriptedAnimationController::ThrottlingReason::OutsideViewport);
+            scriptedAnimationController->removeThrottlingReason(ThrottlingReason::OutsideViewport);
     }
 
     document->setTimerThrottlingEnabled(shouldThrottle);
index 9820941..f5c4d09 100644 (file)
@@ -22,6 +22,7 @@
 
 #include "ActivityStateChangeObserver.h"
 #include "AlternativeTextClient.h"
+#include "AnimationFrameRate.h"
 #include "ApplicationCacheStorage.h"
 #include "AuthenticatorCoordinator.h"
 #include "BackForwardCache.h"
@@ -333,9 +334,12 @@ Page::Page(PageConfiguration&& pageConfiguration)
 #if USE(LIBWEBRTC)
     m_libWebRTCProvider->supportsH265(RuntimeEnabledFeatures::sharedFeatures().webRTCH265CodecEnabled());
 #endif
-    
+
     if (!pageConfiguration.userScriptsShouldWaitUntilNotification)
         m_hasBeenNotifiedToInjectUserScripts = true;
+
+    if (m_lowPowerModeNotifier->isLowPowerModeEnabled())
+        m_throttlingReasons.add(ThrottlingReason::LowPowerMode);
 }
 
 Page::~Page()
@@ -1170,18 +1174,20 @@ bool Page::isOnlyNonUtilityPage() const
     return !isUtilityPage() && nonUtilityPageCount == 1;
 }
 
-bool Page::isLowPowerModeEnabled() const
+void Page::setLowPowerModeEnabledOverrideForTesting(Optional<bool> isEnabled)
 {
-    if (m_lowPowerModeEnabledOverrideForTesting)
-        return m_lowPowerModeEnabledOverrideForTesting.value();
+    // Remove ThrottlingReason::LowPowerMode so handleLowModePowerChange() can do its work.
+    m_throttlingReasonsOverridenForTesting.remove(ThrottlingReason::LowPowerMode);
 
-    return m_lowPowerModeNotifier->isLowPowerModeEnabled();
-}
+    // Use the current low power mode value of the device.
+    if (!isEnabled) {
+        handleLowModePowerChange(m_lowPowerModeNotifier->isLowPowerModeEnabled());
+        return;
+    }
 
-void Page::setLowPowerModeEnabledOverrideForTesting(Optional<bool> isEnabled)
-{
-    m_lowPowerModeEnabledOverrideForTesting = isEnabled;
-    handleLowModePowerChange(m_lowPowerModeEnabledOverrideForTesting.valueOr(false));
+    // Override the value and add ThrottlingReason::LowPowerMode so it override the device state.
+    handleLowModePowerChange(isEnabled.value());
+    m_throttlingReasonsOverridenForTesting.add(ThrottlingReason::LowPowerMode);
 }
 
 void Page::setTopContentInset(float contentInset)
@@ -1474,34 +1480,34 @@ void Page::resumeScriptedAnimations()
     });
 }
 
-enum class ThrottlingReasonOperation { Add, Remove };
-static void updateScriptedAnimationsThrottlingReason(Page& page, ThrottlingReasonOperation operation, ScriptedAnimationController::ThrottlingReason reason)
+Seconds Page::preferredRenderingUpdateInterval() const
 {
-    page.forEachDocument([&] (Document& document) {
-        if (auto* controller = document.scriptedAnimationController()) {
-            if (operation == ThrottlingReasonOperation::Add)
-                controller->addThrottlingReason(reason);
-            else
-                controller->removeThrottlingReason(reason);
-        }
-    });
+    return preferredFrameInterval(m_throttlingReasons);
 }
 
 void Page::setIsVisuallyIdleInternal(bool isVisuallyIdle)
 {
-    updateScriptedAnimationsThrottlingReason(*this, isVisuallyIdle ? ThrottlingReasonOperation::Add : ThrottlingReasonOperation::Remove, ScriptedAnimationController::ThrottlingReason::VisuallyIdle);
+    if (isVisuallyIdle == m_throttlingReasons.contains(ThrottlingReason::VisuallyIdle))
+        return;
+
+    m_throttlingReasons = m_throttlingReasons ^ ThrottlingReason::VisuallyIdle;
+    renderingUpdateScheduler().adjustRenderingUpdateFrequency();
 }
 
 void Page::handleLowModePowerChange(bool isLowPowerModeEnabled)
 {
-    updateScriptedAnimationsThrottlingReason(*this, isLowPowerModeEnabled ? ThrottlingReasonOperation::Add : ThrottlingReasonOperation::Remove, ScriptedAnimationController::ThrottlingReason::LowPowerMode);
-    if (RuntimeEnabledFeatures::sharedFeatures().webAnimationsCSSIntegrationEnabled()) {
-        forEachDocument([] (Document& document) {
-            if (auto timeline = document.existingTimeline())
-                timeline->updateThrottlingState();
-        });
-    } else
+    if (!canUpdateThrottlingReason(ThrottlingReason::LowPowerMode))
+        return;
+
+    if (isLowPowerModeEnabled == m_throttlingReasons.contains(ThrottlingReason::LowPowerMode))
+        return;
+
+    m_throttlingReasons = m_throttlingReasons ^ ThrottlingReason::LowPowerMode;
+    renderingUpdateScheduler().adjustRenderingUpdateFrequency();
+
+    if (!RuntimeEnabledFeatures::sharedFeatures().webAnimationsCSSIntegrationEnabled())
         mainFrame().legacyAnimation().updateThrottlingState();
+
     updateDOMTimerAlignmentInterval();
 }
 
index 8e299b8..0a9813a 100644 (file)
@@ -21,6 +21,7 @@
 #pragma once
 
 #include "ActivityState.h"
+#include "AnimationFrameRate.h"
 #include "DisabledAdaptations.h"
 #include "Document.h"
 #include "FindOptions.h"
@@ -720,9 +721,12 @@ public:
     bool loadsSubresources() const { return m_loadsSubresources; }
     bool loadsFromNetwork() const { return m_loadsFromNetwork; }
 
-    bool isLowPowerModeEnabled() const;
+    bool isLowPowerModeEnabled() const { return m_throttlingReasons.contains(ThrottlingReason::LowPowerMode); }
     WEBCORE_EXPORT void setLowPowerModeEnabledOverrideForTesting(Optional<bool>);
 
+    OptionSet<ThrottlingReason> throttlingReasons() const { return m_throttlingReasons; }
+    Seconds preferredRenderingUpdateInterval() const;
+
     WEBCORE_EXPORT void applicationWillResignActive();
     WEBCORE_EXPORT void applicationDidEnterBackground();
     WEBCORE_EXPORT void applicationWillEnterForeground();
@@ -790,6 +794,8 @@ private:
     void updateDOMTimerAlignmentInterval();
     void domTimerAlignmentIntervalIncreaseTimerFired();
 
+    bool canUpdateThrottlingReason(ThrottlingReason reason) const { return !m_throttlingReasonsOverridenForTesting.contains(reason); }
+
     void doAfterUpdateRendering();
 
     WheelEventTestMonitor& ensureWheelEventTestMonitor();
@@ -1000,7 +1006,8 @@ private:
 
     std::unique_ptr<PerformanceMonitor> m_performanceMonitor;
     std::unique_ptr<LowPowerModeNotifier> m_lowPowerModeNotifier;
-    Optional<bool> m_lowPowerModeEnabledOverrideForTesting;
+    OptionSet<ThrottlingReason> m_throttlingReasons;
+    OptionSet<ThrottlingReason> m_throttlingReasonsOverridenForTesting;
 
     Optional<Navigation> m_navigationToLogWhenVisible;
 
index 6f33702..74632f0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 Apple Inc. All rights reserved.
+ * Copyright (C) 2019-2020 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -42,6 +42,45 @@ RenderingUpdateScheduler::RenderingUpdateScheduler(Page& page)
 #endif
 }
 
+
+#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
+void RenderingUpdateScheduler::setPreferredFramesPerSecond(FramesPerSecond preferredFramesPerSecond)
+{
+    if (m_preferredFramesPerSecond == preferredFramesPerSecond)
+        return;
+
+    m_preferredFramesPerSecond = preferredFramesPerSecond;
+    DisplayRefreshMonitorManager::sharedManager().setPreferredFramesPerSecond(*this, m_preferredFramesPerSecond);
+}
+
+bool RenderingUpdateScheduler::scheduleAnimation(FramesPerSecond preferredFramesPerSecond)
+{
+#if !PLATFORM(IOS_FAMILY)
+    // PreferredFramesPerSecond can only be changed for iOS DisplayRefreshMonitor.
+    // The caller has to fall back to using the timer.
+    if (preferredFramesPerSecond != FullSpeedFramesPerSecond)
+        return false;
+#endif
+    setPreferredFramesPerSecond(preferredFramesPerSecond);
+    return DisplayRefreshMonitorManager::sharedManager().scheduleAnimation(*this);
+}
+#endif
+
+void RenderingUpdateScheduler::adjustRenderingUpdateFrequency()
+{
+#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
+    Seconds interval = m_page.preferredRenderingUpdateInterval();
+
+    // PreferredFramesPerSecond is an integer and should be > 0.
+    if (interval <= 1_s)
+        setPreferredFramesPerSecond(preferredFramesPerSecond(interval));
+#endif
+    if (isScheduled()) {
+        clearScheduled();
+        scheduleTimedRenderingUpdate();
+    }
+}
+
 void RenderingUpdateScheduler::scheduleTimedRenderingUpdate()
 {
     if (isScheduled())
@@ -55,12 +94,16 @@ void RenderingUpdateScheduler::scheduleTimedRenderingUpdate()
 
     tracePoint(ScheduleRenderingUpdate);
 
+    Seconds interval = m_page.preferredRenderingUpdateInterval();
+
 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
-    if (!DisplayRefreshMonitorManager::sharedManager().scheduleAnimation(*this))
+    // PreferredFramesPerSecond is an integer and should be > 0.
+    if (interval <= 1_s)
+        m_scheduled = scheduleAnimation(preferredFramesPerSecond(interval));
 #endif
-        startTimer(Seconds(1.0 / 60));
 
-    m_scheduled = true;
+    if (!isScheduled())
+        startTimer(interval);
 }
 
 bool RenderingUpdateScheduler::isScheduled() const
@@ -74,6 +117,7 @@ void RenderingUpdateScheduler::startTimer(Seconds delay)
     ASSERT(!isScheduled());
     m_refreshTimer = makeUnique<Timer>(*this, &RenderingUpdateScheduler::displayRefreshFired);
     m_refreshTimer->startOneShot(delay);
+    m_scheduled = true;
 }
 
 void RenderingUpdateScheduler::clearScheduled()
index da3d0cb..12d6118 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 Apple Inc. All rights reserved.
+ * Copyright (C) 2019-2020 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -25,6 +25,7 @@
 
 #pragma once
 
+#include "AnimationFrameRate.h"
 #include "DisplayRefreshMonitorClient.h"
 #include <wtf/Seconds.h>
 
@@ -46,6 +47,8 @@ public:
     }
 
     RenderingUpdateScheduler(Page&);
+    
+    void adjustRenderingUpdateFrequency();
     void scheduleTimedRenderingUpdate();
     void scheduleImmediateRenderingUpdate();
     void scheduleRenderingUpdate();
@@ -56,6 +59,8 @@ public:
 
 private:
 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
+    void setPreferredFramesPerSecond(FramesPerSecond);
+    bool scheduleAnimation(FramesPerSecond);
     RefPtr<DisplayRefreshMonitor> createDisplayRefreshMonitor(PlatformDisplayID) const final;
     void displayRefreshFired() final;
 #else
@@ -69,6 +74,9 @@ private:
     Page& m_page;
     bool m_scheduled { false };
     std::unique_ptr<Timer> m_refreshTimer;
+#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
+    FramesPerSecond m_preferredFramesPerSecond { FullSpeedFramesPerSecond };
+#endif
 };
 
 }
diff --git a/Source/WebCore/platform/graphics/AnimationFrameRate.h b/Source/WebCore/platform/graphics/AnimationFrameRate.h
new file mode 100644 (file)
index 0000000..6d8f4d5
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <wtf/OptionSet.h>
+#include <wtf/Seconds.h>
+#include <wtf/text/TextStream.h>
+
+namespace WebCore {
+
+using FramesPerSecond = unsigned;
+
+enum class ThrottlingReason {
+    VisuallyIdle                    = 1 << 0,
+    OutsideViewport                 = 1 << 1,
+    LowPowerMode                    = 1 << 2,
+    NonInteractedCrossOriginFrame   = 1 << 3,
+};
+
+// Allow a little more than 60fps to make sure we can at least hit that frame rate.
+constexpr const Seconds FullSpeedAnimationInterval { 15_ms };
+// Allow a little more than 30fps to make sure we can at least hit that frame rate.
+constexpr const Seconds HalfSpeedThrottlingAnimationInterval { 30_ms };
+constexpr const Seconds AggressiveThrottlingAnimationInterval { 10_s };
+
+constexpr const FramesPerSecond FullSpeedFramesPerSecond = 60;
+constexpr const FramesPerSecond HalfSpeedThrottlingFramesPerSecond = 30;
+constexpr const FramesPerSecond ZeroFramesPerSecond = 0;
+
+inline Seconds preferredFrameInterval(const OptionSet<ThrottlingReason>& reasons)
+{
+    // FIXME: handle ThrottlingReason::VisuallyIdle, ThrottlingReason::OutsideViewport
+    if (reasons.containsAny({ ThrottlingReason::LowPowerMode, ThrottlingReason::NonInteractedCrossOriginFrame }))
+        return HalfSpeedThrottlingAnimationInterval;
+
+    return FullSpeedAnimationInterval;
+}
+
+inline FramesPerSecond preferredFramesPerSecond(Seconds preferredFrameInterval)
+{
+    if (preferredFrameInterval == FullSpeedAnimationInterval)
+        return FullSpeedFramesPerSecond;
+
+    if (preferredFrameInterval == HalfSpeedThrottlingAnimationInterval)
+        return HalfSpeedThrottlingFramesPerSecond;
+
+    ASSERT_NOT_REACHED();
+    return ZeroFramesPerSecond;
+}
+
+inline TextStream& operator<<(TextStream& ts, const OptionSet<ThrottlingReason>& reasons)
+{
+    StringBuilder builder;
+    for (auto reason : reasons) {
+        if (!builder.isEmpty())
+            ts << "|";
+
+        switch (reason) {
+        case ThrottlingReason::VisuallyIdle:
+            ts << "VisuallyIdle";
+            break;
+        case ThrottlingReason::OutsideViewport:
+            ts << "OutsideViewport";
+            break;
+        case ThrottlingReason::LowPowerMode:
+            ts << "LowPowerMode";
+            break;
+        case ThrottlingReason::NonInteractedCrossOriginFrame:
+            ts << "NonInteractiveCrossOriginFrame";
+            break;
+        }
+    }
+    if (reasons.isEmpty())
+        ts << "[Unthrottled]";
+    return ts;
+}
+
+}
index d3542b0..3ee7b51 100644 (file)
@@ -27,6 +27,7 @@
 
 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
 
+#include "AnimationFrameRate.h"
 #include "PlatformScreen.h"
 #include <wtf/HashSet.h>
 #include <wtf/Lock.h>
@@ -45,6 +46,8 @@ public:
 
     virtual void displayLinkFired() { }
 
+    virtual void setPreferredFramesPerSecond(FramesPerSecond) { }
+
     // Return true if callback request was scheduled, false if it couldn't be
     // (e.g., hardware refresh is not available)
     virtual bool requestRefreshCallback() = 0;
index ce04729..8fcc176 100644 (file)
@@ -42,8 +42,11 @@ DisplayRefreshMonitorManager& DisplayRefreshMonitorManager::sharedManager()
     return manager.get();
 }
 
-DisplayRefreshMonitor* DisplayRefreshMonitorManager::createMonitorForClient(DisplayRefreshMonitorClient& client)
+DisplayRefreshMonitor* DisplayRefreshMonitorManager::monitorForClient(DisplayRefreshMonitorClient& client)
 {
+    if (!client.hasDisplayID())
+        return nullptr;
+
     PlatformDisplayID clientDisplayID = client.displayID();
     if (auto* existingMonitor = monitorForDisplayID(clientDisplayID)) {
         existingMonitor->addClient(client);
@@ -54,21 +57,13 @@ DisplayRefreshMonitor* DisplayRefreshMonitorManager::createMonitorForClient(Disp
     if (!monitor)
         return nullptr;
 
-    LOG(RequestAnimationFrame, "DisplayRefreshMonitorManager::createMonitorForClient() - created monitor %p", monitor.get());
+    LOG(RequestAnimationFrame, "DisplayRefreshMonitorManager::monitorForClient() - created monitor %p", monitor.get());
     monitor->addClient(client);
     DisplayRefreshMonitor* result = monitor.get();
     m_monitors.append({ WTFMove(monitor) });
     return result;
 }
 
-void DisplayRefreshMonitorManager::registerClient(DisplayRefreshMonitorClient& client)
-{
-    if (!client.hasDisplayID())
-        return;
-
-    createMonitorForClient(client);
-}
-
 void DisplayRefreshMonitorManager::unregisterClient(DisplayRefreshMonitorClient& client)
 {
     if (!client.hasDisplayID())
@@ -85,17 +80,19 @@ void DisplayRefreshMonitorManager::unregisterClient(DisplayRefreshMonitorClient&
     }
 }
 
-bool DisplayRefreshMonitorManager::scheduleAnimation(DisplayRefreshMonitorClient& client)
+void DisplayRefreshMonitorManager::setPreferredFramesPerSecond(DisplayRefreshMonitorClient& client, FramesPerSecond preferredFramesPerSecond)
 {
-    if (!client.hasDisplayID())
-        return false;
-
-    DisplayRefreshMonitor* monitor = createMonitorForClient(client);
-    if (!monitor)
-        return false;
+    if (auto* monitor = monitorForClient(client))
+        monitor->setPreferredFramesPerSecond(preferredFramesPerSecond);
+}
 
-    client.setIsScheduled(true);
-    return monitor->requestRefreshCallback();
+bool DisplayRefreshMonitorManager::scheduleAnimation(DisplayRefreshMonitorClient& client)
+{
+    if (auto* monitor = monitorForClient(client)) {
+        client.setIsScheduled(true);
+        return monitor->requestRefreshCallback();
+    }
+    return false;
 }
 
 void DisplayRefreshMonitorManager::displayDidRefresh(DisplayRefreshMonitor& monitor)
@@ -116,7 +113,6 @@ void DisplayRefreshMonitorManager::windowScreenDidChange(PlatformDisplayID displ
     
     unregisterClient(client);
     client.setDisplayID(displayID);
-    registerClient(client);
     if (client.isScheduled())
         scheduleAnimation(client);
 }
index 4e0d4ea..bd1d86b 100644 (file)
@@ -27,6 +27,7 @@
 
 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
 
+#include "AnimationFrameRate.h"
 #include "DisplayRefreshMonitor.h"
 #include "PlatformScreen.h"
 #include <wtf/NeverDestroyed.h>
@@ -37,28 +38,27 @@ namespace WebCore {
 
 class DisplayRefreshMonitorManager {
     friend class NeverDestroyed<DisplayRefreshMonitorManager>;
+    friend class DisplayRefreshMonitor;
 public:
     WEBCORE_EXPORT static DisplayRefreshMonitorManager& sharedManager();
-    
-    void registerClient(DisplayRefreshMonitorClient&);
+
     void unregisterClient(DisplayRefreshMonitorClient&);
 
+    void setPreferredFramesPerSecond(DisplayRefreshMonitorClient&, FramesPerSecond);
     bool scheduleAnimation(DisplayRefreshMonitorClient&);
     void windowScreenDidChange(PlatformDisplayID, DisplayRefreshMonitorClient&);
 
     WEBCORE_EXPORT void displayWasUpdated(PlatformDisplayID);
-    
+
 private:
-    friend class DisplayRefreshMonitor;
-    void displayDidRefresh(DisplayRefreshMonitor&);
-    
-    DisplayRefreshMonitorManager() { }
+    DisplayRefreshMonitorManager() = default;
     virtual ~DisplayRefreshMonitorManager();
 
+    void displayDidRefresh(DisplayRefreshMonitor&);
+
     size_t findMonitorForDisplayID(PlatformDisplayID) const;
     DisplayRefreshMonitor* monitorForDisplayID(PlatformDisplayID) const;
-
-    DisplayRefreshMonitor* createMonitorForClient(DisplayRefreshMonitorClient&);
+    DisplayRefreshMonitor* monitorForClient(DisplayRefreshMonitorClient&);
 
     struct DisplayRefreshMonitorWrapper {
         DisplayRefreshMonitorWrapper(DisplayRefreshMonitorWrapper&&) = default;
index a911bdd..9bcd23f 100644 (file)
@@ -35,7 +35,6 @@ namespace WebCore {
 GraphicsLayerUpdater::GraphicsLayerUpdater(GraphicsLayerUpdaterClient& client, PlatformDisplayID displayID)
     : m_client(client)
 {
-    DisplayRefreshMonitorManager::sharedManager().registerClient(*this);
     DisplayRefreshMonitorManager::sharedManager().windowScreenDidChange(displayID, *this);
     DisplayRefreshMonitorManager::sharedManager().scheduleAnimation(*this);
 }
index ee94779..18e809d 100644 (file)
@@ -40,6 +40,7 @@ using WebCore::DisplayRefreshMonitorIOS;
 }
 
 - (id)initWithMonitor:(DisplayRefreshMonitorIOS*)monitor;
+- (void)setPreferredFramesPerSecond:(NSInteger)preferredFramesPerSecond;
 - (void)handleDisplayLink:(CADisplayLink *)sender;
 - (void)invalidate;
 
@@ -65,6 +66,11 @@ using WebCore::DisplayRefreshMonitorIOS;
     [super dealloc];
 }
 
+- (void)setPreferredFramesPerSecond:(NSInteger)preferredFramesPerSecond
+{
+    m_displayLink.preferredFramesPerSecond = preferredFramesPerSecond;
+}
+
 - (void)handleDisplayLink:(CADisplayLink *)sender
 {
     UNUSED_PARAM(sender);
index 99c6500..f529df7 100644 (file)
@@ -1387,12 +1387,15 @@ ExceptionOr<bool> Internals::isTimerThrottled(int timeoutId)
     return !!timer->alignedFireTime(MonotonicTime { });
 }
 
-bool Internals::isRequestAnimationFrameThrottled() const
+String Internals::requestAnimationFrameThrottlingReasons() const
 {
     auto* scriptedAnimationController = contextDocument()->scriptedAnimationController();
     if (!scriptedAnimationController)
-        return false;
-    return scriptedAnimationController->isThrottled();
+        return String();
+        
+    TextStream ts;
+    ts << scriptedAnimationController->throttlingReasons();
+    return ts.release();
 }
 
 double Internals::requestAnimationFrameInterval() const
index c42b6c7..ce00358 100644 (file)
@@ -205,7 +205,7 @@ public:
 
     // DOMTimers throttling testing.
     ExceptionOr<bool> isTimerThrottled(int timeoutId);
-    bool isRequestAnimationFrameThrottled() const;
+    String requestAnimationFrameThrottlingReasons() const;
     double requestAnimationFrameInterval() const;
     bool scriptedAnimationsAreSuspended() const;
     bool areTimersThrottled() const;
index 93168f7..042d90d 100644 (file)
@@ -557,7 +557,7 @@ enum CompositingPolicy {
     // Query if a timer is currently throttled, to debug timer throttling.
     [MayThrowException] boolean isTimerThrottled(long timerHandle);
 
-    boolean isRequestAnimationFrameThrottled();
+    DOMString requestAnimationFrameThrottlingReasons();
     boolean areTimersThrottled();
 
     [MayThrowException] void setLowPowerModeEnabled(boolean enabled);
index 06e0ebd..19f7c22 100644 (file)
@@ -1,3 +1,31 @@
+2020-05-04  Said Abou-Hallawa  <sabouhallawa@apple.com>
+
+        Throttling requestAnimationFrame should be controlled by RenderingUpdateScheduler
+        https://bugs.webkit.org/show_bug.cgi?id=204713
+
+        Reviewed by Simon Fraser.
+
+        Create an IPC message on the DrawingArea to send a message from the
+        WebProcess to the UIProcess to setPreferredFramesPerSecond of the
+        DisplayRefreshMonitor.
+
+        * UIProcess/RemoteLayerTree/RemoteLayerTreeDrawingAreaProxy.h:
+        * UIProcess/RemoteLayerTree/RemoteLayerTreeDrawingAreaProxy.messages.in:
+        * UIProcess/RemoteLayerTree/RemoteLayerTreeDrawingAreaProxy.mm:
+        (-[WKOneShotDisplayLinkHandler setPreferredFramesPerSecond:]):
+        (WebKit::RemoteLayerTreeDrawingAreaProxy::setPreferredFramesPerSecond):
+        Set the preferredFramesPerSecond of the CADisplayLink.
+
+        * WebProcess/WebPage/RemoteLayerTree/RemoteLayerTreeDisplayRefreshMonitor.h:
+        * WebProcess/WebPage/RemoteLayerTree/RemoteLayerTreeDisplayRefreshMonitor.mm:
+        (WebKit::RemoteLayerTreeDisplayRefreshMonitor::setPreferredFramesPerSecond):
+        Forward the call to RemoteLayerTreeDrawingArea.
+
+        * WebProcess/WebPage/RemoteLayerTree/RemoteLayerTreeDrawingArea.h:
+        * WebProcess/WebPage/RemoteLayerTree/RemoteLayerTreeDrawingArea.mm:
+        (WebKit::RemoteLayerTreeDrawingArea::setPreferredFramesPerSecond):
+        Send the IPC message from the WebProcess to the UIProcess.
+
 2020-05-04  Alex Christensen  <achristensen@webkit.org>
 
         TestWebKitAPI.WebKit.CustomDisplayName is a flaky timeout
index 81c1a12..b6ec52d 100644 (file)
@@ -28,6 +28,7 @@
 #include "DrawingAreaProxy.h"
 #include "RemoteLayerTreeHost.h"
 #include "TransactionID.h"
+#include <WebCore/AnimationFrameRate.h>
 #include <WebCore/FloatPoint.h>
 #include <WebCore/IntPoint.h>
 #include <WebCore/IntSize.h>
@@ -97,6 +98,7 @@ private:
     void didReceiveMessage(IPC::Connection&, IPC::Decoder&) override;
 
     // Message handlers
+    void setPreferredFramesPerSecond(WebCore::FramesPerSecond);
     void willCommitLayerTree(TransactionID);
     void commitLayerTree(const RemoteLayerTreeTransaction&, const RemoteScrollingCoordinatorTransaction&);
     
index d1047a9..ed0328d 100644 (file)
@@ -21,6 +21,7 @@
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 messages -> RemoteLayerTreeDrawingAreaProxy : DrawingAreaProxy NotRefCounted {
+    void SetPreferredFramesPerSecond(unsigned preferredFramesPerSecond)
     void WillCommitLayerTree(WebKit::TransactionID transactionID)
     void CommitLayerTree(WebKit::RemoteLayerTreeTransaction layerTreeTransaction, WebKit::RemoteScrollingCoordinatorTransaction scrollingTreeTransaction)
 }
index d543a4b..eb49e66 100644 (file)
@@ -49,6 +49,7 @@
 }
 
 - (id)initWithDrawingAreaProxy:(WebKit::RemoteLayerTreeDrawingAreaProxy*)drawingAreaProxy;
+- (void)setPreferredFramesPerSecond:(NSInteger)preferredFramesPerSecond;
 - (void)displayLinkFired:(CADisplayLink *)sender;
 - (void)invalidate;
 - (void)schedule;
     [super dealloc];
 }
 
+- (void)setPreferredFramesPerSecond:(NSInteger)preferredFramesPerSecond
+{
+    _displayLink.preferredFramesPerSecond = preferredFramesPerSecond;
+}
+
 - (void)displayLinkFired:(CADisplayLink *)sender
 {
     ASSERT(isUIThread());
@@ -183,6 +189,15 @@ void RemoteLayerTreeDrawingAreaProxy::sendUpdateGeometry()
     m_isWaitingForDidUpdateGeometry = true;
 }
 
+void RemoteLayerTreeDrawingAreaProxy::setPreferredFramesPerSecond(FramesPerSecond preferredFramesPerSecond)
+{
+#if PLATFORM(IOS_FAMILY)
+    [displayLinkHandler() setPreferredFramesPerSecond:preferredFramesPerSecond];
+#else
+    UNUSED_PARAM(preferredFramesPerSecond);
+#endif
+}
+
 void RemoteLayerTreeDrawingAreaProxy::willCommitLayerTree(TransactionID transactionID)
 {
     m_pendingLayerTreeTransactionID = transactionID;
index d1f7747..b97f1f4 100644 (file)
@@ -28,6 +28,7 @@
 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
 
 #include "RemoteLayerTreeDrawingArea.h"
+#include <WebCore/AnimationFrameRate.h>
 #include <WebCore/DisplayRefreshMonitor.h>
 
 namespace WebKit {
@@ -41,6 +42,7 @@ public:
     
     virtual ~RemoteLayerTreeDisplayRefreshMonitor();
 
+    void setPreferredFramesPerSecond(WebCore::FramesPerSecond) override;
     bool requestRefreshCallback() override;
 
     void didUpdateLayers();
index a2e6d40..6b295bd 100644 (file)
@@ -43,6 +43,12 @@ RemoteLayerTreeDisplayRefreshMonitor::~RemoteLayerTreeDisplayRefreshMonitor()
         m_drawingArea->willDestroyDisplayRefreshMonitor(this);
 }
 
+void RemoteLayerTreeDisplayRefreshMonitor::setPreferredFramesPerSecond(FramesPerSecond preferredFramesPerSecond)
+{
+    if (m_drawingArea)
+        m_drawingArea->setPreferredFramesPerSecond(preferredFramesPerSecond);
+}
+
 bool RemoteLayerTreeDisplayRefreshMonitor::requestRefreshCallback()
 {
     if (!m_drawingArea || !isActive())
index 1ac3944..b70eee2 100644 (file)
@@ -29,6 +29,7 @@
 #include "DrawingArea.h"
 #include "GraphicsLayerCARemote.h"
 #include "RemoteLayerTreeTransaction.h"
+#include <WebCore/AnimationFrameRate.h>
 #include <WebCore/GraphicsLayerClient.h>
 #include <WebCore/Timer.h>
 #include <atomic>
@@ -72,6 +73,7 @@ private:
 
     RefPtr<WebCore::DisplayRefreshMonitor> createDisplayRefreshMonitor(WebCore::PlatformDisplayID) override;
     void willDestroyDisplayRefreshMonitor(WebCore::DisplayRefreshMonitor*);
+    void setPreferredFramesPerSecond(WebCore::FramesPerSecond);
 
     bool shouldUseTiledBackingForFrameView(const WebCore::FrameView&) const override;
 
index b779827..d72b593 100644 (file)
@@ -126,6 +126,11 @@ void RemoteLayerTreeDrawingArea::adoptDisplayRefreshMonitorsFromDrawingArea(Draw
     }
 }
 
+void RemoteLayerTreeDrawingArea::setPreferredFramesPerSecond(FramesPerSecond preferredFramesPerSecond)
+{
+    send(Messages::RemoteLayerTreeDrawingAreaProxy::SetPreferredFramesPerSecond(preferredFramesPerSecond));
+}
+
 void RemoteLayerTreeDrawingArea::updateRootLayers()
 {
     Vector<Ref<GraphicsLayer>> children;