Source/WebCore/ChangeLog

 12020-01-23 Said Abou-Hallawa <sabouhallawa@apple.com>
 2
 3 Throttling requestAnimationFrame should be controlled by RenderingUpdateScheduler
 4 https://bugs.webkit.org/show_bug.cgi?id=204713
 5
 6 Reviewed by Simon Fraser.
 7
 8 Test: fast/animation/request-animation-frame-throttling-outside-viewport.html
 9
 10 requestAnimationFrame is throttled by a timer although its callback are
 11 serviced by the page RenderingUpdate. This led to excessive rAF firing
 12 which makes it more than the preferred frame per seconds.
 13
 14 The solution is to have two throttling types:
 15
 16 1) Page throttling (or full throttling) which slows down all the steps of
 17 RenderingUpdate for the main document and all the sub-documents.
 18 2) Document throttling (or partial throttling) which only slows down the
 19 rAF of a certain document.
 20
 21 * Headers.cmake:
 22 * WebCore.xcodeproj/project.pbxproj:
 23
 24 * animation/DocumentTimeline.cpp:
 25 (WebCore::DocumentTimeline::animationInterval const):
 26 (WebCore::DocumentTimeline::updateThrottlingState): Deleted.
 27 * animation/DocumentTimeline.h:
 28 There is no need to have DocumentTimeline throttling. It is throttled
 29 when the page RenderingUpdate is throttled.
 30
 31 * dom/Document.cpp:
 32 (WebCore::Document::requestAnimationFrame):
 33 (WebCore::Document::updateLastHandledUserGestureTimestamp):
 34 LowPowerMode throttling is now handled by the page. So remove its handling
 35 in the Document side.
 36
 37 * dom/ScriptedAnimationController.cpp:
 38 (WebCore::ScriptedAnimationController::ScriptedAnimationController):
 39 (WebCore::ScriptedAnimationController::page const):
 40 (WebCore::ScriptedAnimationController::interval const):
 41 (WebCore::ScriptedAnimationController::isThrottled const):
 42 (WebCore::ScriptedAnimationController::registerCallback):
 43 (WebCore::ScriptedAnimationController::cancelCallback):
 44 (WebCore::ScriptedAnimationController::serviceRequestAnimationFrameCallbacks):
 45 (WebCore::ScriptedAnimationController::scheduleAnimation):
 46 (WebCore::throttlingReasonToString): Deleted.
 47 (WebCore::throttlingReasonsToString): Deleted.
 48 (WebCore::ScriptedAnimationController::addThrottlingReason): Deleted.
 49 (WebCore::ScriptedAnimationController::removeThrottlingReason): Deleted.
 50 (WebCore::ScriptedAnimationController::animationTimerFired): Deleted.
 51 * dom/ScriptedAnimationController.h:
 52 (WebCore::ScriptedAnimationController::addThrottlingReason):
 53 (WebCore::ScriptedAnimationController::removeThrottlingReason):
 54 Get rid of the rAF throttling timer. Service the rAF callback only when
 55 the period from the current time stamp till the last service time stamp
 56 is greater than the preferred rAF interval .
 57
 58 * page/FrameView.cpp:
 59 (WebCore::FrameView::updateScriptedAnimationsAndTimersThrottlingState):
 60 ThrottlingReason is now defined outside ScriptedAnimationController.
 61
 62 * page/Page.cpp:
 63 (WebCore::Page::suspendScriptedAnimations):
 64 (WebCore::Page::resumeScriptedAnimations):
 65 Use forEachDocument().
 66
 67 (WebCore::Page::preferredRenderingUpdateInterval const):
 68 Calculate the preferred RenderingUpdate interval from the throttling
 69 reasons.
 70
 71 (WebCore::Page::setIsVisuallyIdleInternal):
 72 (WebCore::Page::handleLowModePowerChange):
 73 Call adjustRenderingUpdateFrequency() when isLowPowerModeEnabled or
 74 IsVisuallyIdle is toggled.
 75
 76 (WebCore::updateScriptedAnimationsThrottlingReason): Deleted.
 77 * page/Page.h:
 78 (WebCore::Page::isRenderingUpdateThrottled const):
 79
 80 * page/RenderingUpdateScheduler.cpp:
 81 (WebCore::RenderingUpdateScheduler::adjustFramesPerSecond):
 82 (WebCore::RenderingUpdateScheduler::adjustRenderingUpdateFrequency):
 83 Change the preferredFramesPerSecond of the DisplayRefreshMonitor if the
 84 throttling is not aggressive e.g. 10_s. Otherwise use the timer.
 85
 86 (WebCore::RenderingUpdateScheduler::scheduleTimedRenderingUpdate):
 87 Call adjustFramesPerSecond() when DisplayRefreshMonitor is created.
 88
 89 (WebCore::RenderingUpdateScheduler::startTimer):
 90 * page/RenderingUpdateScheduler.h:
 91
 92 * platform/graphics/AnimationFrameRate.h: Added.
 93 (WebCore::preferredFrameInterval):
 94 (WebCore::preferredFramesPerSecond):
 95 * platform/graphics/DisplayRefreshMonitor.h:
 96 (WebCore::DisplayRefreshMonitor::setPreferredFramesPerSecond):
 97
 98 * platform/graphics/DisplayRefreshMonitorManager.cpp:
 99 (WebCore::DisplayRefreshMonitorManager::monitorForClient):
 100 Rename createMonitorForClient() to monitorForClient() since it may return
 101 a cached DisplayRefreshMonitor.
 102
 103 (WebCore::DisplayRefreshMonitorManager::setPreferredFramesPerSecond):
 104 (WebCore::DisplayRefreshMonitorManager::scheduleAnimation):
 105 (WebCore::DisplayRefreshMonitorManager::displayDidRefresh):
 106 No need to call registerClient(). This function was just ensuring the
 107 DisplayRefreshMonitor is created. scheduleAnimation() does the same thing.
 108
 109 (WebCore::DisplayRefreshMonitorManager::createMonitorForClient): Deleted.
 110 (WebCore::DisplayRefreshMonitorManager::registerClient): Deleted.
 111 * platform/graphics/DisplayRefreshMonitorManager.h:
 112 (WebCore::DisplayRefreshMonitorManager::DisplayRefreshMonitorManager): Deleted.
 113
 114 * platform/graphics/GraphicsLayerUpdater.cpp:
 115 (WebCore::GraphicsLayerUpdater::GraphicsLayerUpdater):
 116 * platform/graphics/ios/DisplayRefreshMonitorIOS.mm:
 117 (-[WebDisplayLinkHandler setPreferredFramesPerSecond:]):
 118 Set the preferredFramesPerSecond of the CADisplayLink.
 119
11202020-01-23 Andres Gonzalez <andresg_22@apple.com>
2121
3122 Use the same AccessibilityObjectWrapper for associated AXObject and AXIsolatedObject.

Source/WebKit/ChangeLog

 12020-01-23 Said Abou-Hallawa <sabouhallawa@apple.com>
 2
 3 Throttling requestAnimationFrame should be controlled by RenderingUpdateScheduler
 4 https://bugs.webkit.org/show_bug.cgi?id=204713
 5
 6 Reviewed by Simon Fraser.
 7
 8 Create an IPC message on the DrawingArea to send a message from the
 9 WebProcess to the UIProcess to setPreferredFramesPerSecond of the
 10 DisplayRefreshMonitor.
 11
 12 * UIProcess/RemoteLayerTree/RemoteLayerTreeDrawingAreaProxy.h:
 13 * UIProcess/RemoteLayerTree/RemoteLayerTreeDrawingAreaProxy.messages.in:
 14
 15 * UIProcess/RemoteLayerTree/RemoteLayerTreeDrawingAreaProxy.mm:
 16 (-[WKOneShotDisplayLinkHandler setPreferredFramesPerSecond:]):
 17 (WebKit::RemoteLayerTreeDrawingAreaProxy::setPreferredFramesPerSecond):
 18 Set the preferredFramesPerSecond of the CADisplayLink.
 19
 20 * WebProcess/WebPage/RemoteLayerTree/RemoteLayerTreeDisplayRefreshMonitor.h:
 21 * WebProcess/WebPage/RemoteLayerTree/RemoteLayerTreeDisplayRefreshMonitor.mm:
 22 (WebKit::RemoteLayerTreeDisplayRefreshMonitor::setPreferredFramesPerSecond):
 23 Delegate the call to RemoteLayerTreeDrawingArea.
 24
 25 * WebProcess/WebPage/RemoteLayerTree/RemoteLayerTreeDrawingArea.h:
 26 * WebProcess/WebPage/RemoteLayerTree/RemoteLayerTreeDrawingArea.mm:
 27 (WebKit::RemoteLayerTreeDrawingArea::setPreferredFramesPerSecond):
 28 Send the IPC message from the WebProcess to the UIProcess.
 29
1302020-01-23 Daniel Bates <dabates@apple.com>
231
332 Add a variant of -selectPositionAtPoint that can select an arbitrary point

Source/WebCore/Headers.cmake

@@set(WebCore_PRIVATE_FRAMEWORK_HEADERS
10341034
10351035 platform/graphics/AlphaPremultiplication.h
10361036 platform/graphics/ANGLEWebKitBridge.h
 1037 platform/graphics/AnimationFrameRate.h
10371038 platform/graphics/AudioTrackPrivate.h
10381039 platform/graphics/BitmapImage.h
10391040 platform/graphics/Color.h

Source/WebCore/WebCore.xcodeproj/project.pbxproj

21412141 72144333223EC8B000F12FF7 /* SVGProperty.h in Headers */ = {isa = PBXBuildFile; fileRef = 55EE5363223B2A2400FBA944 /* SVGProperty.h */; settings = {ATTRIBUTES = (Private, ); }; };
21422142 72144334223EC91600F12FF7 /* SVGPropertyOwner.h in Headers */ = {isa = PBXBuildFile; fileRef = 55EE5360223B2A2100FBA944 /* SVGPropertyOwner.h */; settings = {ATTRIBUTES = (Private, ); }; };
21432143 72283F0E230B268C00F5D828 /* ImagePaintingOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 72C18A3F230B04B7006847C7 /* ImagePaintingOptions.h */; settings = {ATTRIBUTES = (Private, ); }; };
 2144 722A815D238FDAF000C00583 /* AnimationFrameRate.h in Headers */ = {isa = PBXBuildFile; fileRef = 722A815C238FD50500C00583 /* AnimationFrameRate.h */; settings = {ATTRIBUTES = (Private, ); }; };
21442145 724ED3321A3A8B2300F5F13C /* JSEXTBlendMinMax.h in Headers */ = {isa = PBXBuildFile; fileRef = 724ED3301A3A8B2300F5F13C /* JSEXTBlendMinMax.h */; };
21452146 724EE5501DC80D7F00A91FFB /* ActivityState.h in Headers */ = {isa = PBXBuildFile; fileRef = 724EE54E1DC7F25B00A91FFB /* ActivityState.h */; settings = {ATTRIBUTES = (Private, ); }; };
21462147 724EE5511DC80D8400A91FFB /* ActivityStateChangeObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = 724EE54F1DC7F25B00A91FFB /* ActivityStateChangeObserver.h */; settings = {ATTRIBUTES = (Private, ); }; };

96319632 71FF851822A3F81F005D5959 /* NavigatorMaxTouchPoints.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = NavigatorMaxTouchPoints.idl; sourceTree = "<group>"; };
96329633 721443452240C8BA00F12FF7 /* SVGAnimatedValueProperty.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SVGAnimatedValueProperty.h; sourceTree = "<group>"; };
96339634 721443462240CAD200F12FF7 /* SVGValueProperty.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SVGValueProperty.h; sourceTree = "<group>"; };
 9635 722A815C238FD50500C00583 /* AnimationFrameRate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AnimationFrameRate.h; sourceTree = "<group>"; };
96349636 724ED3291A3A7E5400F5F13C /* EXTBlendMinMax.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EXTBlendMinMax.cpp; sourceTree = "<group>"; };
96359637 724ED32A1A3A7E5400F5F13C /* EXTBlendMinMax.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EXTBlendMinMax.h; sourceTree = "<group>"; };
96369638 724ED32B1A3A7E5400F5F13C /* EXTBlendMinMax.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = EXTBlendMinMax.idl; sourceTree = "<group>"; };

2504025042 7299BC6423D686A600CC6883 /* AlphaPremultiplication.h */,
2504125043 490707E41219C04300D90E51 /* ANGLEWebKitBridge.cpp */,
2504225044 490707E51219C04300D90E51 /* ANGLEWebKitBridge.h */,
 25045 722A815C238FD50500C00583 /* AnimationFrameRate.h */,
2504325046 BEF29EE91715DD0900C4B4C9 /* AudioTrackPrivate.h */,
2504425047 A89943270B42338700D7C802 /* BitmapImage.cpp */,
2504525048 A89943260B42338700D7C802 /* BitmapImage.h */,

2908629089 71EFCEDC202B38A900D7C411 /* AnimationEffect.h in Headers */,
2908729090 71E2C42621C935280024F8C8 /* AnimationEffectPhase.h in Headers */,
2908829091 319848011A1D817B00A13318 /* AnimationEvent.h in Headers */,
 29092 722A815D238FDAF000C00583 /* AnimationFrameRate.h in Headers */,
2908929093 49E912AD0EFAC906009D0CAF /* AnimationList.h in Headers */,
2909029094 714C7C661FDAD2A100F2BEE1 /* AnimationPlaybackEvent.h in Headers */,
2909129095 714C7C671FDAD2A900F2BEE1 /* AnimationPlaybackEventInit.h in Headers */,

Source/WebCore/animation/DocumentTimeline.cpp

4444#include "RenderLayerBacking.h"
4545#include <JavaScriptCore/VM.h>
4646
47 static const Seconds defaultAnimationInterval { 15_ms };
48 static const Seconds throttledAnimationInterval { 30_ms };
49 
5047namespace WebCore {
5148
5249Ref<DocumentTimeline> DocumentTimeline::create(Document& document)

@@Vector<RefPtr<WebAnimation>> DocumentTimeline::getAnimations() const
193190 return animations;
194191}
195192
196 void DocumentTimeline::updateThrottlingState()
197 {
198  scheduleAnimationResolution();
199 }
200 
201193Seconds DocumentTimeline::animationInterval() const
202194{
203195 if (!m_document || !m_document->page())
204196 return Seconds::infinity();
205  return m_document->page()->isLowPowerModeEnabled() ? throttledAnimationInterval : defaultAnimationInterval;
 197 return m_document->page()->preferredRenderingUpdateInterval();
206198}
207199
208200void DocumentTimeline::suspendAnimations()

Source/WebCore/animation/DocumentTimeline.h

@@public:
7373
7474 void updateAnimationsAndSendEvents(DOMHighResTimeStamp timestamp);
7575
76  void updateThrottlingState();
7776 WEBCORE_EXPORT Seconds animationInterval() const;
7877 WEBCORE_EXPORT void suspendAnimations();
7978 WEBCORE_EXPORT void resumeAnimations();

Source/WebCore/dom/Document.cpp

@@int Document::requestAnimationFrame(Ref<RequestAnimationFrameCallback>&& callbac
64996499 if (!page() || page()->scriptedAnimationsSuspended())
65006500 m_scriptedAnimationController->suspend();
65016501
6502  if (page() && page()->isLowPowerModeEnabled())
6503  m_scriptedAnimationController->addThrottlingReason(ScriptedAnimationController::ThrottlingReason::LowPowerMode);
6504 
65056502 if (!topOrigin().canAccess(securityOrigin()) && !hasHadUserInteraction())
6506  m_scriptedAnimationController->addThrottlingReason(ScriptedAnimationController::ThrottlingReason::NonInteractedCrossOriginFrame);
 6503 m_scriptedAnimationController->addThrottlingReason(ThrottlingReason::NonInteractedCrossOriginFrame);
65076504 }
65086505
65096506 return m_scriptedAnimationController->registerCallback(WTFMove(callback));

@@void Document::updateLastHandledUserGestureTimestamp(MonotonicTime time)
67426739
67436740 if (static_cast<bool>(time) && m_scriptedAnimationController) {
67446741 // It's OK to always remove NonInteractedCrossOriginFrame even if this frame isn't cross-origin.
6745  m_scriptedAnimationController->removeThrottlingReason(ScriptedAnimationController::ThrottlingReason::NonInteractedCrossOriginFrame);
 6742 m_scriptedAnimationController->removeThrottlingReason(ThrottlingReason::NonInteractedCrossOriginFrame);
67466743 }
67476744
67486745 // DOM Timer alignment may depend on the user having interacted with the document.

Source/WebCore/dom/ScriptedAnimationController.cpp

11/*
22 * Copyright (C) 2011 Google Inc. All Rights Reserved.
 3 * Copyright (C) 2019 Apple Inc. All rights reserved.
34 *
45 * Redistribution and use in source and binary forms, with or without
56 * modification, are permitted provided that the following conditions

2627#include "config.h"
2728#include "ScriptedAnimationController.h"
2829
29 #include "Chrome.h"
30 #include "ChromeClient.h"
31 #include "CustomHeaderFields.h"
32 #include "DOMWindow.h"
33 #include "Document.h"
34 #include "DocumentLoader.h"
35 #include "Frame.h"
36 #include "FrameView.h"
3730#include "InspectorInstrumentation.h"
38 #include "Logging.h"
3931#include "Page.h"
4032#include "RequestAnimationFrameCallback.h"
4133#include "Settings.h"
42 #include <algorithm>
43 #include <wtf/Ref.h>
4434#include <wtf/SystemTracing.h>
45 #include <wtf/text/StringBuilder.h>
46 
47 // Allow a little more than 60fps to make sure we can at least hit that frame rate.
48 static const Seconds fullSpeedAnimationInterval { 15_ms };
49 // Allow a little more than 30fps to make sure we can at least hit that frame rate.
50 static const Seconds halfSpeedThrottlingAnimationInterval { 30_ms };
51 static const Seconds aggressiveThrottlingAnimationInterval { 10_s };
52 
53 #define RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(page() && page()->isAlwaysOnLoggingAllowed(), PerformanceLogging, "%p - ScriptedAnimationController::" fmt, this, ##__VA_ARGS__)
5435
5536namespace WebCore {
5637
5738ScriptedAnimationController::ScriptedAnimationController(Document& document)
5839 : m_document(makeWeakPtr(document))
59  , m_animationTimer(*this, &ScriptedAnimationController::animationTimerFired)
6040{
6141}
6242

@@bool ScriptedAnimationController::requestAnimationFrameEnabled() const
6747 return m_document && m_document->settings().requestAnimationFrameEnabled();
6848}
6949
 50Page* ScriptedAnimationController::page() const
 51{
 52 return m_document ? m_document->page() : nullptr;
 53}
 54
 55Seconds ScriptedAnimationController::interval() const
 56{
 57 if (auto* page = this->page())
 58 return std::max(preferredFrameInterval(m_throttlingReasons), page->preferredRenderingUpdateInterval());
 59 return preferredFrameInterval(m_throttlingReasons);
 60}
 61
7062void ScriptedAnimationController::suspend()
7163{
7264 ++m_suspendCount;

@@void ScriptedAnimationController::resume()
8375 scheduleAnimation();
8476}
8577
86 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR) && !RELEASE_LOG_DISABLED
87 
88 static const char* throttlingReasonToString(ScriptedAnimationController::ThrottlingReason reason)
89 {
90  switch (reason) {
91  case ScriptedAnimationController::ThrottlingReason::VisuallyIdle:
92  return "VisuallyIdle";
93  case ScriptedAnimationController::ThrottlingReason::OutsideViewport:
94  return "OutsideViewport";
95  case ScriptedAnimationController::ThrottlingReason::LowPowerMode:
96  return "LowPowerMode";
97  case ScriptedAnimationController::ThrottlingReason::NonInteractedCrossOriginFrame:
98  return "NonInteractiveCrossOriginFrame";
99  }
100 }
101 
102 static String throttlingReasonsToString(OptionSet<ScriptedAnimationController::ThrottlingReason> reasons)
103 {
104  if (reasons.isEmpty())
105  return "[Unthrottled]"_s;
106 
107  StringBuilder builder;
108  for (auto reason : reasons) {
109  if (!builder.isEmpty())
110  builder.append('|');
111  builder.append(throttlingReasonToString(reason));
112  }
113  return builder.toString();
114 }
115 
116 #endif
117 
118 void ScriptedAnimationController::addThrottlingReason(ThrottlingReason reason)
119 {
120 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
121  if (m_throttlingReasons.contains(reason))
122  return;
123 
124  m_throttlingReasons.add(reason);
125 
126  RELEASE_LOG_IF_ALLOWED("addThrottlingReason(%s) -> %s", throttlingReasonToString(reason), throttlingReasonsToString(m_throttlingReasons).utf8().data());
127 
128  if (m_animationTimer.isActive()) {
129  m_animationTimer.stop();
130  scheduleAnimation();
131  }
132 #else
133  UNUSED_PARAM(reason);
134 #endif
135 }
136 
137 void ScriptedAnimationController::removeThrottlingReason(ThrottlingReason reason)
138 {
139 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
140  if (!m_throttlingReasons.contains(reason))
141  return;
142 
143  m_throttlingReasons.remove(reason);
144 
145  RELEASE_LOG_IF_ALLOWED("removeThrottlingReason(%s) -> %s", throttlingReasonToString(reason), throttlingReasonsToString(m_throttlingReasons).utf8().data());
146 
147  if (m_animationTimer.isActive()) {
148  m_animationTimer.stop();
149  scheduleAnimation();
150  }
151 #else
152  UNUSED_PARAM(reason);
153 #endif
154 }
155 
15678bool ScriptedAnimationController::isThrottled() const
15779{
158 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
159  return !m_throttlingReasons.isEmpty();
160 #else
 80 auto* page = this->page();
 81 if (!m_throttlingReasons.isEmpty() || (page && page->isRenderingUpdateThrottled())) {
 82 ASSERT(interval() > FullSpeedAnimationInterval);
 83 return true;
 84 }
16185 return false;
162 #endif
16386}
16487
16588ScriptedAnimationController::CallbackId ScriptedAnimationController::registerCallback(Ref<RequestAnimationFrameCallback>&& callback)
16689{
167  ScriptedAnimationController::CallbackId id = ++m_nextCallbackId;
 90 CallbackId callbackId = ++m_nextCallbackId;
16891 callback->m_firedOrCancelled = false;
169  callback->m_id = id;
 92 callback->m_id = callbackId;
17093 m_callbacks.append(WTFMove(callback));
17194
17295 if (m_document)
173  InspectorInstrumentation::didRequestAnimationFrame(*m_document, id);
 96 InspectorInstrumentation::didRequestAnimationFrame(*m_document, callbackId);
17497
17598 if (!m_suspendCount)
17699 scheduleAnimation();
177  return id;
 100 return callbackId;
178101}
179102
180 void ScriptedAnimationController::cancelCallback(CallbackId id)
 103void ScriptedAnimationController::cancelCallback(CallbackId callbackId)
181104{
182  for (size_t i = 0; i < m_callbacks.size(); ++i) {
183  if (m_callbacks[i]->m_id == id) {
184  m_callbacks[i]->m_firedOrCancelled = true;
185  InspectorInstrumentation::didCancelAnimationFrame(*m_document, id);
186  m_callbacks.remove(i);
187  return;
188  }
189  }
 105 bool cancelled = m_callbacks.removeFirstMatching([&](auto& callback) {
 106 if (callback->m_id != callbackId)
 107 return false;
 108 callback->m_firedOrCancelled = true;
 109 return true;
 110 });
 111
 112 if (cancelled && m_document)
 113 InspectorInstrumentation::didCancelAnimationFrame(*m_document, callbackId);
190114}
191115
192116void ScriptedAnimationController::serviceRequestAnimationFrameCallbacks(DOMHighResTimeStamp timestamp)

@@void ScriptedAnimationController::serviceRequestAnimationFrameCallbacks(DOMHighR
194118 if (!m_callbacks.size() || m_suspendCount || !requestAnimationFrameEnabled())
195119 return;
196120
 121 bool isThrottlingRelativeToPage = page() && page()->preferredRenderingUpdateInterval() < preferredFrameInterval(m_throttlingReasons);
 122 bool canSkipFrame = Seconds(timestamp - m_lastAnimationFrameTimestamp) < preferredFrameInterval(m_throttlingReasons);
 123 if (isThrottlingRelativeToPage && canSkipFrame) {
 124 WTFLogAlways("Skipped serviceRequestAnimationFrameCallbacks(). preferredFrameInterval() = %f6.2", preferredFrameInterval(m_throttlingReasons).seconds());
 125 scheduleAnimation();
 126 return;
 127 }
 128
197129 TraceScope tracingScope(RAFCallbackStart, RAFCallbackEnd);
198130
199131 // We round this to the nearest microsecond so that we can return a time that matches what is returned by document.timeline.currentTime.

@@void ScriptedAnimationController::serviceRequestAnimationFrameCallbacks(DOMHighR
223155 return callback->m_firedOrCancelled;
224156 });
225157
 158 m_lastAnimationFrameTimestamp = timestamp;
 159
226160 if (m_callbacks.size())
227161 scheduleAnimation();
228162}
229163
230 Seconds ScriptedAnimationController::interval() const
231 {
232 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
233  if (m_throttlingReasons.contains(ThrottlingReason::VisuallyIdle) || m_throttlingReasons.contains(ThrottlingReason::OutsideViewport))
234  return aggressiveThrottlingAnimationInterval;
235 
236  if (m_throttlingReasons.contains(ThrottlingReason::LowPowerMode))
237  return halfSpeedThrottlingAnimationInterval;
238 
239  if (m_throttlingReasons.contains(ThrottlingReason::NonInteractedCrossOriginFrame))
240  return halfSpeedThrottlingAnimationInterval;
241 
242  ASSERT(m_throttlingReasons.isEmpty());
243 #endif
244  return fullSpeedAnimationInterval;
245 }
246 
247 Page* ScriptedAnimationController::page() const
248 {
249  return m_document ? m_document->page() : nullptr;
250 }
251 
252164void ScriptedAnimationController::scheduleAnimation()
253165{
254166 if (!requestAnimationFrameEnabled())
255167 return;
256168
257 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
258  if (!m_isUsingTimer && !isThrottled()) {
259  if (auto* page = this->page()) {
260  page->renderingUpdateScheduler().scheduleTimedRenderingUpdate();
261  return;
262  }
263 
264  m_isUsingTimer = true;
265  }
266 #endif
267  if (m_animationTimer.isActive())
268  return;
269 
270  Seconds animationInterval = interval();
271  Seconds scheduleDelay = std::max(animationInterval - Seconds(m_document->domWindow()->nowTimestamp() - m_lastAnimationFrameTimestamp), 0_s);
272 
273  if (isThrottled()) {
274  // FIXME: not ideal to snapshot time both in now() and nowTimestamp(), the latter of which also has reduced resolution.
275  MonotonicTime now = MonotonicTime::now();
276 
277  MonotonicTime fireTime = now + scheduleDelay;
278  Seconds alignmentInterval = 10_ms;
279  // Snap to the nearest alignmentInterval.
280  Seconds alignment = (fireTime + alignmentInterval / 2) % alignmentInterval;
281  MonotonicTime alignedFireTime = fireTime - alignment;
282  scheduleDelay = alignedFireTime - now;
283  }
284 
285  m_animationTimer.startOneShot(scheduleDelay);
286 }
287 
288 void ScriptedAnimationController::animationTimerFired()
289 {
290  m_lastAnimationFrameTimestamp = m_document->domWindow()->nowTimestamp();
291  serviceRequestAnimationFrameCallbacks(m_lastAnimationFrameTimestamp);
 169 if (auto* page = this->page())
 170 page->renderingUpdateScheduler().scheduleTimedRenderingUpdate();
292171}
293172
294173}

Source/WebCore/dom/ScriptedAnimationController.h

11/*
22 * Copyright (C) 2011 Google Inc. All Rights Reserved.
 3 * Copyright (C) 2019 Apple Inc. All rights reserved.
34 *
45 * Redistribution and use in source and binary forms, with or without
56 * modification, are permitted provided that the following conditions

2526
2627#pragma once
2728
 29#include "AnimationFrameRate.h"
2830#include "DOMHighResTimeStamp.h"
29 #include "Timer.h"
3031#include <wtf/OptionSet.h>
3132#include <wtf/RefCounted.h>
3233#include <wtf/RefPtr.h>
3334#include <wtf/Vector.h>
 35#include <wtf/WeakPtr.h>
3436
3537namespace WebCore {
3638

@@public:
4850 ~ScriptedAnimationController();
4951 void clearDocumentPointer() { m_document = nullptr; }
5052 bool requestAnimationFrameEnabled() const;
 53 WEBCORE_EXPORT Seconds interval() const;
5154
5255 typedef int CallbackId;
5356

@@public:
5760
5861 void suspend();
5962 void resume();
60 
61  enum class ThrottlingReason {
62  VisuallyIdle = 1 << 0,
63  OutsideViewport = 1 << 1,
64  LowPowerMode = 1 << 2,
65  NonInteractedCrossOriginFrame = 1 << 3,
66  };
67  void addThrottlingReason(ThrottlingReason);
68  void removeThrottlingReason(ThrottlingReason);
 63
 64 void addThrottlingReason(ThrottlingReason reason)
 65 {
 66 m_throttlingReasons.add(reason);
 67 WTFLogAlways("ScriptedAnimationController::addThrottlingReason() adding ThrottlingReason = %d", reason);
 68 }
 69
 70 void removeThrottlingReason(ThrottlingReason reason)
 71 {
 72 m_throttlingReasons.remove(reason);
 73 WTFLogAlways("ScriptedAnimationController::removeThrottlingReason() removing ThrottlingReason = %d", reason);
 74 }
6975
7076 WEBCORE_EXPORT bool isThrottled() const;
71  WEBCORE_EXPORT Seconds interval() const;
7277
7378private:
7479 ScriptedAnimationController(Document&);
7580
76  void scheduleAnimation();
77  void animationTimerFired();
78 
7981 Page* page() const;
 82 void scheduleAnimation();
8083
81  typedef Vector<RefPtr<RequestAnimationFrameCallback>> CallbackList;
 84 using CallbackList = Vector<RefPtr<RequestAnimationFrameCallback>>;
8285 CallbackList m_callbacks;
 86 DOMHighResTimeStamp m_lastAnimationFrameTimestamp { 0 };
8387
8488 WeakPtr<Document> m_document;
8589 CallbackId m_nextCallbackId { 0 };
8690 int m_suspendCount { 0 };
8791
88  Timer m_animationTimer;
89  double m_lastAnimationFrameTimestamp { 0 };
90 
91 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
9292 OptionSet<ThrottlingReason> m_throttlingReasons;
93  bool m_isUsingTimer { false };
94 #endif
9593};
9694
9795} // namespace WebCore

Source/WebCore/page/FrameView.cpp

@@void FrameView::updateScriptedAnimationsAndTimersThrottlingState(const IntRect&
25242524
25252525 if (auto* scriptedAnimationController = document->scriptedAnimationController()) {
25262526 if (shouldThrottle)
2527  scriptedAnimationController->addThrottlingReason(ScriptedAnimationController::ThrottlingReason::OutsideViewport);
 2527 scriptedAnimationController->addThrottlingReason(ThrottlingReason::OutsideViewport);
25282528 else
2529  scriptedAnimationController->removeThrottlingReason(ScriptedAnimationController::ThrottlingReason::OutsideViewport);
 2529 scriptedAnimationController->removeThrottlingReason(ThrottlingReason::OutsideViewport);
25302530 }
25312531
25322532 document->setTimerThrottlingEnabled(shouldThrottle);

Source/WebCore/page/Page.cpp

2222
2323#include "ActivityStateChangeObserver.h"
2424#include "AlternativeTextClient.h"
 25#include "AnimationFrameRate.h"
2526#include "ApplicationCacheStorage.h"
2627#include "AuthenticatorCoordinator.h"
2728#include "BackForwardCache.h"

@@void Page::resumeScriptedAnimations()
13751376 });
13761377}
13771378
1378 enum class ThrottlingReasonOperation { Add, Remove };
1379 static void updateScriptedAnimationsThrottlingReason(Page& page, ThrottlingReasonOperation operation, ScriptedAnimationController::ThrottlingReason reason)
 1379Seconds Page::preferredRenderingUpdateInterval() const
13801380{
1381  page.forEachDocument([&] (Document& document) {
1382  if (auto* controller = document.scriptedAnimationController()) {
1383  if (operation == ThrottlingReasonOperation::Add)
1384  controller->addThrottlingReason(reason);
1385  else
1386  controller->removeThrottlingReason(reason);
1387  }
1388  });
 1381 return preferredFrameInterval(m_throttlingReasons);
13891382}
13901383
13911384void Page::setIsVisuallyIdleInternal(bool isVisuallyIdle)
13921385{
1393  updateScriptedAnimationsThrottlingReason(*this, isVisuallyIdle ? ThrottlingReasonOperation::Add : ThrottlingReasonOperation::Remove, ScriptedAnimationController::ThrottlingReason::VisuallyIdle);
 1386 if (isVisuallyIdle == m_throttlingReasons.contains(ThrottlingReason::VisuallyIdle))
 1387 return;
 1388
 1389 m_throttlingReasons = m_throttlingReasons ^ ThrottlingReason::VisuallyIdle;
 1390 renderingUpdateScheduler().adjustRenderingUpdateFrequency();
 1391
 1392 if (m_throttlingReasons.contains(ThrottlingReason::VisuallyIdle))
 1393 WTFLogAlways("Page::setIsVisuallyIdleInternal() adding ThrottlingReason = ThrottlingReason::VisuallyIdle");
 1394 else
 1395 WTFLogAlways("Page::setIsVisuallyIdleInternal() removing ThrottlingReason = ThrottlingReason::VisuallyIdle");
13941396}
13951397
13961398void Page::handleLowModePowerChange(bool isLowPowerModeEnabled)
13971399{
1398  updateScriptedAnimationsThrottlingReason(*this, isLowPowerModeEnabled ? ThrottlingReasonOperation::Add : ThrottlingReasonOperation::Remove, ScriptedAnimationController::ThrottlingReason::LowPowerMode);
1399  if (RuntimeEnabledFeatures::sharedFeatures().webAnimationsCSSIntegrationEnabled()) {
1400  forEachDocument([] (Document& document) {
1401  if (auto timeline = document.existingTimeline())
1402  timeline->updateThrottlingState();
1403  });
1404  } else
 1400 if (isLowPowerModeEnabled == m_throttlingReasons.contains(ThrottlingReason::LowPowerMode))
 1401 return;
 1402
 1403 m_throttlingReasons = m_throttlingReasons ^ ThrottlingReason::LowPowerMode;
 1404 renderingUpdateScheduler().adjustRenderingUpdateFrequency();
 1405
 1406 if (m_throttlingReasons.contains(ThrottlingReason::LowPowerMode))
 1407 WTFLogAlways("Page::handleLowModePowerChange() adding ThrottlingReason = ThrottlingReason::LowPowerMode");
 1408 else
 1409 WTFLogAlways("Page::handleLowModePowerChange() removing ThrottlingReason = ThrottlingReason::LowPowerMode");
 1410
 1411 if (!RuntimeEnabledFeatures::sharedFeatures().webAnimationsCSSIntegrationEnabled())
14051412 mainFrame().animation().updateThrottlingState();
 1413
14061414 updateDOMTimerAlignmentInterval();
14071415}
14081416

Source/WebCore/page/Page.h

2121#pragma once
2222
2323#include "ActivityState.h"
 24#include "AnimationFrameRate.h"
2425#include "DisabledAdaptations.h"
2526#include "Document.h"
2627#include "FindOptions.h"

@@public:
258259 PerformanceMonitor* performanceMonitor() { return m_performanceMonitor.get(); }
259260
260261 RenderingUpdateScheduler& renderingUpdateScheduler();
 262 bool isRenderingUpdateThrottled() const { return !m_throttlingReasons.isEmpty(); }
261263
262264 ValidationMessageClient* validationMessageClient() const { return m_validationMessageClient.get(); }
263265 void updateValidationBubbleStateIfNeeded();

@@public:
701703 bool isLowPowerModeEnabled() const;
702704 WEBCORE_EXPORT void setLowPowerModeEnabledOverrideForTesting(Optional<bool>);
703705
 706 Seconds preferredRenderingUpdateInterval() const;
 707
704708 WEBCORE_EXPORT void applicationWillResignActive();
705709 WEBCORE_EXPORT void applicationDidEnterBackground();
706710 WEBCORE_EXPORT void applicationWillEnterForeground();

@@private:
9961000 bool m_inUpdateRendering { false };
9971001 bool m_hasResourceLoadClient { false };
9981002 Vector<UserContentURLPattern> m_corsDisablingPatterns;
 1003 OptionSet<ThrottlingReason> m_throttlingReasons;
9991004};
10001005
10011006inline PageGroup& Page::group()

Source/WebCore/page/RenderingUpdateScheduler.cpp

@@RenderingUpdateScheduler::RenderingUpdateScheduler(Page& page)
4242#endif
4343}
4444
 45#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR) && PLATFORM(IOS_FAMILY)
 46void RenderingUpdateScheduler::adjustFramesPerSecond()
 47{
 48 Seconds interval = m_page.preferredRenderingUpdateInterval();
 49 // CADisplayLink.preferredFramesPerSecond is an integer. So a fraction PreferredFramesPerSecond can't be set.
 50 if (interval < 1_s)
 51 DisplayRefreshMonitorManager::sharedManager().setPreferredFramesPerSecond(*this, preferredFramesPerSecond(interval));
 52}
 53#endif
 54
 55void RenderingUpdateScheduler::adjustRenderingUpdateFrequency()
 56{
 57#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR) && PLATFORM(IOS_FAMILY)
 58 adjustFramesPerSecond();
 59#endif
 60 if (isScheduled()) {
 61 clearScheduled();
 62 scheduleTimedRenderingUpdate();
 63 }
 64}
 65
4566void RenderingUpdateScheduler::scheduleTimedRenderingUpdate()
4667{
4768 if (isScheduled())

@@void RenderingUpdateScheduler::scheduleTimedRenderingUpdate()
5576
5677 tracePoint(ScheduleRenderingUpdate);
5778
 79 Seconds interval = m_page.preferredRenderingUpdateInterval();
 80
5881#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
59  if (!DisplayRefreshMonitorManager::sharedManager().scheduleAnimation(*this))
 82 // CADisplayLink.preferredFramesPerSecond is an integer. Fall back to timer if the PreferredFramesPerSecond is a fraction.
 83 if (interval < 1_s) {
 84#if PLATFORM(IOS_FAMILY)
 85 if (!m_isMonitorCreated) {
 86 adjustFramesPerSecond();
 87 m_isMonitorCreated = true;
 88 }
 89#else
 90 if (interval == FullSpeedAnimationInterval)
 91#endif
 92 m_scheduled = DisplayRefreshMonitorManager::sharedManager().scheduleAnimation(*this);
 93 }
6094#endif
61  startTimer(Seconds(1.0 / 60));
6295
63  m_scheduled = true;
 96 if (!isScheduled())
 97 startTimer(interval);
6498}
6599
66100bool RenderingUpdateScheduler::isScheduled() const

@@void RenderingUpdateScheduler::startTimer(Seconds delay)
74108 ASSERT(!isScheduled());
75109 m_refreshTimer = makeUnique<Timer>(*this, &RenderingUpdateScheduler::displayRefreshFired);
76110 m_refreshTimer->startOneShot(delay);
 111 m_scheduled = true;
77112}
78113
79114void RenderingUpdateScheduler::clearScheduled()

Source/WebCore/page/RenderingUpdateScheduler.h

@@public:
4646 }
4747
4848 RenderingUpdateScheduler(Page&);
 49
 50 void adjustRenderingUpdateFrequency();
4951 void scheduleTimedRenderingUpdate();
5052 void scheduleImmediateRenderingUpdate();
5153 void scheduleRenderingUpdate();

@@public:
5658
5759private:
5860#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
 61#if PLATFORM(IOS_FAMILY)
 62 void adjustFramesPerSecond();
 63#endif
5964 RefPtr<DisplayRefreshMonitor> createDisplayRefreshMonitor(PlatformDisplayID) const final;
6065 void displayRefreshFired() final;
6166#else

@@private:
6772 void clearScheduled();
6873
6974 Page& m_page;
 75#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR) && PLATFORM(IOS_FAMILY)
 76 bool m_isMonitorCreated;
 77#endif
7078 bool m_scheduled { false };
7179 std::unique_ptr<Timer> m_refreshTimer;
7280};

Source/WebCore/platform/graphics/AnimationFrameRate.h

 1/*
 2 * Copyright (C) 2019 Apple Inc. All rights reserved.
 3 *
 4 * Redistribution and use in source and binary forms, with or without
 5 * modification, are permitted provided that the following conditions
 6 * are met:
 7 * 1. Redistributions of source code must retain the above copyright
 8 * notice, this list of conditions and the following disclaimer.
 9 * 2. Redistributions in binary form must reproduce the above copyright
 10 * notice, this list of conditions and the following disclaimer in the
 11 * documentation and/or other materials provided with the distribution.
 12 *
 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 24 */
 25
 26#pragma once
 27
 28#include <wtf/OptionSet.h>
 29#include <wtf/Seconds.h>
 30
 31namespace WebCore {
 32
 33using FramesPerSecond = unsigned;
 34
 35enum class ThrottlingReason {
 36 VisuallyIdle = 1 << 0,
 37 OutsideViewport = 1 << 1,
 38 LowPowerMode = 1 << 2,
 39 NonInteractedCrossOriginFrame = 1 << 3,
 40};
 41
 42// Allow a little more than 60fps to make sure we can at least hit that frame rate.
 43constexpr const Seconds FullSpeedAnimationInterval { 15_ms };
 44// Allow a little more than 30fps to make sure we can at least hit that frame rate.
 45constexpr const Seconds HalfSpeedThrottlingAnimationInterval { 30_ms };
 46constexpr const Seconds AggressiveThrottlingAnimationInterval { 10_s };
 47
 48constexpr const FramesPerSecond FullSpeedFramesPerSecond = 60;
 49constexpr const FramesPerSecond HalfSpeedThrottlingFramesPerSecond = 30;
 50constexpr const FramesPerSecond ZeroFramesPerSecond = 0;
 51
 52inline Seconds preferredFrameInterval(const OptionSet<ThrottlingReason>& throttlingReasons)
 53{
 54 if (throttlingReasons.containsAny({ ThrottlingReason::VisuallyIdle, ThrottlingReason::OutsideViewport }))
 55 return AggressiveThrottlingAnimationInterval;
 56
 57 if (throttlingReasons.containsAny({ ThrottlingReason::LowPowerMode, ThrottlingReason::NonInteractedCrossOriginFrame }))
 58 return HalfSpeedThrottlingAnimationInterval;
 59
 60 ASSERT(throttlingReasons.isEmpty());
 61 return FullSpeedAnimationInterval;
 62}
 63
 64inline FramesPerSecond preferredFramesPerSecond(Seconds preferredFrameInterval)
 65{
 66 if (preferredFrameInterval == FullSpeedAnimationInterval)
 67 return FullSpeedFramesPerSecond;
 68
 69 if (preferredFrameInterval == HalfSpeedThrottlingAnimationInterval)
 70 return HalfSpeedThrottlingFramesPerSecond;
 71
 72 ASSERT_NOT_REACHED();
 73 return ZeroFramesPerSecond;
 74}
 75
 76}

Source/WebCore/platform/graphics/DisplayRefreshMonitor.h

2727
2828#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
2929
 30#include "AnimationFrameRate.h"
3031#include "PlatformScreen.h"
3132#include <wtf/HashSet.h>
3233#include <wtf/Lock.h>

@@public:
4546
4647 virtual void displayLinkFired() { }
4748
 49 virtual void setPreferredFramesPerSecond(FramesPerSecond) { }
 50
4851 // Return true if callback request was scheduled, false if it couldn't be
4952 // (e.g., hardware refresh is not available)
5053 virtual bool requestRefreshCallback() = 0;

Source/WebCore/platform/graphics/DisplayRefreshMonitorManager.cpp

@@DisplayRefreshMonitorManager& DisplayRefreshMonitorManager::sharedManager()
4242 return manager.get();
4343}
4444
45 DisplayRefreshMonitor* DisplayRefreshMonitorManager::createMonitorForClient(DisplayRefreshMonitorClient& client)
 45DisplayRefreshMonitor* DisplayRefreshMonitorManager::monitorForClient(DisplayRefreshMonitorClient& client)
4646{
 47 if (!client.hasDisplayID())
 48 return nullptr;
 49
4750 PlatformDisplayID clientDisplayID = client.displayID();
4851 if (auto* existingMonitor = monitorForDisplayID(clientDisplayID)) {
4952 existingMonitor->addClient(client);

@@DisplayRefreshMonitor* DisplayRefreshMonitorManager::createMonitorForClient(Disp
5457 if (!monitor)
5558 return nullptr;
5659
57  LOG(RequestAnimationFrame, "DisplayRefreshMonitorManager::createMonitorForClient() - created monitor %p", monitor.get());
 60 LOG(RequestAnimationFrame, "DisplayRefreshMonitorManager::monitorForClient() - created monitor %p", monitor.get());
5861 monitor->addClient(client);
5962 DisplayRefreshMonitor* result = monitor.get();
6063 m_monitors.append({ WTFMove(monitor) });
6164 return result;
6265}
6366
64 void DisplayRefreshMonitorManager::registerClient(DisplayRefreshMonitorClient& client)
65 {
66  if (!client.hasDisplayID())
67  return;
68 
69  createMonitorForClient(client);
70 }
71 
7267void DisplayRefreshMonitorManager::unregisterClient(DisplayRefreshMonitorClient& client)
7368{
7469 if (!client.hasDisplayID())

@@void DisplayRefreshMonitorManager::unregisterClient(DisplayRefreshMonitorClient&
8580 }
8681}
8782
88 bool DisplayRefreshMonitorManager::scheduleAnimation(DisplayRefreshMonitorClient& client)
 83void DisplayRefreshMonitorManager::setPreferredFramesPerSecond(DisplayRefreshMonitorClient& client, FramesPerSecond preferredFramesPerSecond)
8984{
90  if (!client.hasDisplayID())
91  return false;
92 
93  DisplayRefreshMonitor* monitor = createMonitorForClient(client);
94  if (!monitor)
95  return false;
 85 if (auto* monitor = monitorForClient(client))
 86 monitor->setPreferredFramesPerSecond(preferredFramesPerSecond);
 87}
9688
97  client.setIsScheduled(true);
98  return monitor->requestRefreshCallback();
 89bool DisplayRefreshMonitorManager::scheduleAnimation(DisplayRefreshMonitorClient& client)
 90{
 91 if (auto* monitor = monitorForClient(client)) {
 92 client.setIsScheduled(true);
 93 return monitor->requestRefreshCallback();
 94 }
 95 return false;
9996}
10097
10198void DisplayRefreshMonitorManager::displayDidRefresh(DisplayRefreshMonitor& monitor)

@@void DisplayRefreshMonitorManager::windowScreenDidChange(PlatformDisplayID displ
116113
117114 unregisterClient(client);
118115 client.setDisplayID(displayID);
119  registerClient(client);
120116 if (client.isScheduled())
121117 scheduleAnimation(client);
122118}

Source/WebCore/platform/graphics/DisplayRefreshMonitorManager.h

2727
2828#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
2929
 30#include "AnimationFrameRate.h"
3031#include "DisplayRefreshMonitor.h"
3132#include "PlatformScreen.h"
3233#include <wtf/NeverDestroyed.h>

@@namespace WebCore {
3738
3839class DisplayRefreshMonitorManager {
3940 friend class NeverDestroyed<DisplayRefreshMonitorManager>;
 41 friend class DisplayRefreshMonitor;
4042public:
4143 WEBCORE_EXPORT static DisplayRefreshMonitorManager& sharedManager();
42 
43  void registerClient(DisplayRefreshMonitorClient&);
 44
4445 void unregisterClient(DisplayRefreshMonitorClient&);
4546
 47 void setPreferredFramesPerSecond(DisplayRefreshMonitorClient&, FramesPerSecond);
4648 bool scheduleAnimation(DisplayRefreshMonitorClient&);
4749 void windowScreenDidChange(PlatformDisplayID, DisplayRefreshMonitorClient&);
4850
4951 WEBCORE_EXPORT void displayWasUpdated(PlatformDisplayID);
50 
 52
5153private:
52  friend class DisplayRefreshMonitor;
53  void displayDidRefresh(DisplayRefreshMonitor&);
54 
55  DisplayRefreshMonitorManager() { }
 54 DisplayRefreshMonitorManager() = default;
5655 virtual ~DisplayRefreshMonitorManager();
5756
 57 void displayDidRefresh(DisplayRefreshMonitor&);
 58
5859 size_t findMonitorForDisplayID(PlatformDisplayID) const;
5960 DisplayRefreshMonitor* monitorForDisplayID(PlatformDisplayID) const;
60 
61  DisplayRefreshMonitor* createMonitorForClient(DisplayRefreshMonitorClient&);
 61 DisplayRefreshMonitor* monitorForClient(DisplayRefreshMonitorClient&);
6262
6363 struct DisplayRefreshMonitorWrapper {
6464 DisplayRefreshMonitorWrapper(DisplayRefreshMonitorWrapper&&) = default;

Source/WebCore/platform/graphics/GraphicsLayerUpdater.cpp

@@namespace WebCore {
3535GraphicsLayerUpdater::GraphicsLayerUpdater(GraphicsLayerUpdaterClient& client, PlatformDisplayID displayID)
3636 : m_client(client)
3737{
38  DisplayRefreshMonitorManager::sharedManager().registerClient(*this);
3938 DisplayRefreshMonitorManager::sharedManager().windowScreenDidChange(displayID, *this);
4039 DisplayRefreshMonitorManager::sharedManager().scheduleAnimation(*this);
4140}

Source/WebCore/platform/graphics/ios/DisplayRefreshMonitorIOS.mm

@@using WebCore::DisplayRefreshMonitorIOS;
4040}
4141
4242- (id)initWithMonitor:(DisplayRefreshMonitorIOS*)monitor;
 43- (void)setPreferredFramesPerSecond:(NSInteger)preferredFramesPerSecond;
4344- (void)handleDisplayLink:(CADisplayLink *)sender;
4445- (void)invalidate;
4546

@@using WebCore::DisplayRefreshMonitorIOS;
6566 [super dealloc];
6667}
6768
 69- (void)setPreferredFramesPerSecond:(NSInteger)preferredFramesPerSecond
 70{
 71 m_displayLink.preferredFramesPerSecond = preferredFramesPerSecond;
 72}
 73
6874- (void)handleDisplayLink:(CADisplayLink *)sender
6975{
7076 UNUSED_PARAM(sender);

Source/WebKit/UIProcess/RemoteLayerTree/RemoteLayerTreeDrawingAreaProxy.h

2828#include "DrawingAreaProxy.h"
2929#include "RemoteLayerTreeHost.h"
3030#include "TransactionID.h"
 31#include <WebCore/AnimationFrameRate.h>
3132#include <WebCore/FloatPoint.h>
3233#include <WebCore/IntPoint.h>
3334#include <WebCore/IntSize.h>

@@private:
9798 void didReceiveMessage(IPC::Connection&, IPC::Decoder&) override;
9899
99100 // Message handlers
 101 void setPreferredFramesPerSecond(WebCore::FramesPerSecond);
100102 void willCommitLayerTree(TransactionID);
101103 void commitLayerTree(const RemoteLayerTreeTransaction&, const RemoteScrollingCoordinatorTransaction&);
102104

Source/WebKit/UIProcess/RemoteLayerTree/RemoteLayerTreeDrawingAreaProxy.messages.in

2121# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2222
2323messages -> RemoteLayerTreeDrawingAreaProxy : DrawingAreaProxy NotRefCounted {
 24 void SetPreferredFramesPerSecond(unsigned preferredFramesPerSecond)
2425 void WillCommitLayerTree(WebKit::TransactionID transactionID)
2526 void CommitLayerTree(WebKit::RemoteLayerTreeTransaction layerTreeTransaction, WebKit::RemoteScrollingCoordinatorTransaction scrollingTreeTransaction)
2627}

Source/WebKit/UIProcess/RemoteLayerTree/RemoteLayerTreeDrawingAreaProxy.mm

4949}
5050
5151- (id)initWithDrawingAreaProxy:(WebKit::RemoteLayerTreeDrawingAreaProxy*)drawingAreaProxy;
 52- (void)setPreferredFramesPerSecond:(NSInteger)preferredFramesPerSecond;
5253- (void)displayLinkFired:(CADisplayLink *)sender;
5354- (void)invalidate;
5455- (void)schedule;

7677 [super dealloc];
7778}
7879
 80- (void)setPreferredFramesPerSecond:(NSInteger)preferredFramesPerSecond
 81{
 82 _displayLink.preferredFramesPerSecond = preferredFramesPerSecond;
 83}
 84
7985- (void)displayLinkFired:(CADisplayLink *)sender
8086{
8187 ASSERT(isUIThread());

@@void RemoteLayerTreeDrawingAreaProxy::sendUpdateGeometry()
183189 m_isWaitingForDidUpdateGeometry = true;
184190}
185191
 192void RemoteLayerTreeDrawingAreaProxy::setPreferredFramesPerSecond(FramesPerSecond preferredFramesPerSecond)
 193{
 194#if PLATFORM(IOS_FAMILY)
 195 [displayLinkHandler() setPreferredFramesPerSecond:preferredFramesPerSecond];
 196#else
 197 UNUSED_PARAM(preferredFramesPerSecond);
 198#endif
 199}
 200
186201void RemoteLayerTreeDrawingAreaProxy::willCommitLayerTree(TransactionID transactionID)
187202{
188203 m_pendingLayerTreeTransactionID = transactionID;

Source/WebKit/WebProcess/WebPage/RemoteLayerTree/RemoteLayerTreeDisplayRefreshMonitor.h

2828#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
2929
3030#include "RemoteLayerTreeDrawingArea.h"
 31#include <WebCore/AnimationFrameRate.h>
3132#include <WebCore/DisplayRefreshMonitor.h>
3233
3334namespace WebKit {

@@public:
4142
4243 virtual ~RemoteLayerTreeDisplayRefreshMonitor();
4344
 45 void setPreferredFramesPerSecond(WebCore::FramesPerSecond) override;
4446 bool requestRefreshCallback() override;
4547
4648 void didUpdateLayers();

Source/WebKit/WebProcess/WebPage/RemoteLayerTree/RemoteLayerTreeDisplayRefreshMonitor.mm

@@RemoteLayerTreeDisplayRefreshMonitor::~RemoteLayerTreeDisplayRefreshMonitor()
4343 m_drawingArea->willDestroyDisplayRefreshMonitor(this);
4444}
4545
 46void RemoteLayerTreeDisplayRefreshMonitor::setPreferredFramesPerSecond(FramesPerSecond preferredFramesPerSecond)
 47{
 48 if (m_drawingArea)
 49 m_drawingArea->setPreferredFramesPerSecond(preferredFramesPerSecond);
 50}
 51
4652bool RemoteLayerTreeDisplayRefreshMonitor::requestRefreshCallback()
4753{
4854 if (!m_drawingArea || !isActive())

Source/WebKit/WebProcess/WebPage/RemoteLayerTree/RemoteLayerTreeDrawingArea.h

2929#include "DrawingArea.h"
3030#include "GraphicsLayerCARemote.h"
3131#include "RemoteLayerTreeTransaction.h"
 32#include <WebCore/AnimationFrameRate.h>
3233#include <WebCore/GraphicsLayerClient.h>
3334#include <WebCore/Timer.h>
3435#include <atomic>

@@private:
7374
7475 RefPtr<WebCore::DisplayRefreshMonitor> createDisplayRefreshMonitor(WebCore::PlatformDisplayID) override;
7576 void willDestroyDisplayRefreshMonitor(WebCore::DisplayRefreshMonitor*);
 77 void setPreferredFramesPerSecond(WebCore::FramesPerSecond);
7678
7779 bool shouldUseTiledBackingForFrameView(const WebCore::FrameView&) const override;
7880

Source/WebKit/WebProcess/WebPage/RemoteLayerTree/RemoteLayerTreeDrawingArea.mm

@@void RemoteLayerTreeDrawingArea::adoptDisplayRefreshMonitorsFromDrawingArea(Draw
123123 }
124124}
125125
 126void RemoteLayerTreeDrawingArea::setPreferredFramesPerSecond(FramesPerSecond preferredFramesPerSecond)
 127{
 128 send(Messages::RemoteLayerTreeDrawingAreaProxy::SetPreferredFramesPerSecond(preferredFramesPerSecond));
 129}
 130
126131void RemoteLayerTreeDrawingArea::updateRootLayers()
127132{
128133 Vector<Ref<GraphicsLayer>> children;

LayoutTests/ChangeLog

 12020-01-23 Said Abou-Hallawa <sabouhallawa@apple.com>
 2
 3 Throttling requestAnimationFrame should be controlled by RenderingUpdateScheduler
 4 https://bugs.webkit.org/show_bug.cgi?id=204713
 5
 6 Reviewed by Simon Fraser.
 7
 8 * fast/animation/request-animation-frame-throttling-outside-viewport-expected.txt: Added.
 9 * fast/animation/request-animation-frame-throttling-outside-viewport.html: Added.
 10 * fast/animation/request-animation-frame-throttling-lowPowerMode-expected.txt:
 11 * fast/animation/request-animation-frame-throttling-lowPowerMode.html:
 12 * fast/animation/resources/frame-with-animation-2.html: Added.
 13
1142020-01-23 Diego Pino Garcia <dpino@igalia.com>
215
316 [GTK] Gardening, rebaselines and update TestExpectations

LayoutTests/fast/animation/request-animation-frame-throttling-lowPowerMode-expected.txt

@@Test that requestAnimationFrame gets throttled in low power mode.
33On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
44
55
6 PASS internals.isRequestAnimationFrameThrottled() is false
7 PASS internals.requestAnimationFrameInterval is Infinity
8 rAFHandle = requestAnimationFrame(doWork);
9 PASS internals.isRequestAnimationFrameThrottled() is false
10 PASS internals.requestAnimationFrameInterval is 0.015
11 internals.setLowPowerModeEnabled(true);
12 PASS internals.isRequestAnimationFrameThrottled() is true
13 PASS internals.requestAnimationFrameInterval is 0.030
14 cancelAnimationFrame(rAFHandle);
15 PASS internals.isRequestAnimationFrameThrottled() is true
16 PASS internals.requestAnimationFrameInterval is 0.030
17 rAFHandle = requestAnimationFrame(doWork);
18 PASS internals.isRequestAnimationFrameThrottled() is true
19 PASS internals.requestAnimationFrameInterval is 0.030
20 internals.setLowPowerModeEnabled(false);
21 PASS internals.isRequestAnimationFrameThrottled() is false
22 PASS internals.requestAnimationFrameInterval is 0.015
 6PASS farmesPerSecond < 35 is true
237PASS successfullyParsed is true
248
259TEST COMPLETE

LayoutTests/fast/animation/request-animation-frame-throttling-lowPowerMode.html

11<!DOCTYPE html>
22<html>
33<body>
4 <script src="../../resources/js-test-pre.js"></script>
5 <script>
6 description("Test that requestAnimationFrame gets throttled in low power mode.");
 4 <script src="../../resources/js-test-pre.js"></script>
 5 <script>
 6 description("Test that requestAnimationFrame gets throttled in low power mode.");
77
8 let rAFHandle;
9 let i = 0;
10 function doWork()
11 {
12  i++;
13  rAFHandle = requestAnimationFrame(doWork);
14 }
 8 window.jsTestIsAsync = true;
159
16 shouldBeFalse("internals.isRequestAnimationFrameThrottled()");
17 shouldBe("internals.requestAnimationFrameInterval", "Infinity");
18 evalAndLog("rAFHandle = requestAnimationFrame(doWork);");
19 shouldBeFalse("internals.isRequestAnimationFrameThrottled()");
20 shouldBe("internals.requestAnimationFrameInterval", "0.015");
21 evalAndLog("internals.setLowPowerModeEnabled(true);");
22 shouldBeTrue("internals.isRequestAnimationFrameThrottled()");
23 shouldBe("internals.requestAnimationFrameInterval", "0.030");
24 evalAndLog("cancelAnimationFrame(rAFHandle);");
25 shouldBeTrue("internals.isRequestAnimationFrameThrottled()");
26 shouldBe("internals.requestAnimationFrameInterval", "0.030");
27 evalAndLog("rAFHandle = requestAnimationFrame(doWork);");
28 shouldBeTrue("internals.isRequestAnimationFrameThrottled()");
29 shouldBe("internals.requestAnimationFrameInterval", "0.030");
30 evalAndLog("internals.setLowPowerModeEnabled(false);");
31 shouldBeFalse("internals.isRequestAnimationFrameThrottled()");
32 shouldBe("internals.requestAnimationFrameInterval", "0.015");
33 </script>
34 <script src="../../resources/js-test-post.js"></script>
 10 if (window.internals)
 11 internals.setLowPowerModeEnabled(true);
 12
 13 var start = null;
 14 var farmesPerSecond = 0;
 15 function doWork(timestamp) {
 16 if (!start)
 17 start = timestamp;
 18 if (timestamp - start < 1000) {
 19 ++farmesPerSecond;
 20 window.requestAnimationFrame(doWork);
 21 }
 22 else {
 23 // The LowPowerMode throttling interval = 30_ms. The frame rate ~= 33.3 fps.
 24 shouldBeTrue("farmesPerSecond < 35");
 25 finishJSTest();
 26 }
 27 }
 28 window.requestAnimationFrame(doWork);
 29 </script>
 30 <script src="../../resources/js-test-post.js"></script>
3531</body>
3632</html>

LayoutTests/fast/animation/request-animation-frame-throttling-outside-viewport-expected.txt

 1Test that requestAnimationFrame gets the right throttling in an iframe when when it's outside the viewport.
 2
 3On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 4
 5
 6PASS farmesPerSecond > 0 is true
 7PASS ifarmeFarmesPerSecond == 0 is true
 8PASS successfullyParsed is true
 9
 10TEST COMPLETE
 11

LayoutTests/fast/animation/request-animation-frame-throttling-outside-viewport.html

 1<!DOCTYPE html>
 2<html>
 3<body>
 4 <div style="height: 1000px;"></div>
 5 <script src="../../resources/js-test-pre.js"></script>
 6 <script>
 7 description("Test that requestAnimationFrame gets the right throttling in an iframe when when it's outside the viewport.");
 8 jsTestIsAsync = true;
 9
 10 var farmesPerSecond = 0;
 11 var ifarmeFarmesPerSecond = 0;
 12
 13 window.onmessage = function(e){
 14 if (e.data == 'subFrameRAFMessage') {
 15 ++ifarmeFarmesPerSecond;
 16 }
 17 };
 18
 19 const frame = document.createElement("iframe");
 20 frame.src = "resources/frame-with-animation-2.html";
 21 frame.onload = function() {
 22 var start = null;
 23 function doWork(timestamp) {
 24 if (!start)
 25 start = timestamp;
 26 if (timestamp - start < 1000) {
 27 ++farmesPerSecond;
 28 window.requestAnimationFrame(doWork);
 29 }
 30 else {
 31 shouldBeTrue("farmesPerSecond > 0");
 32
 33 // The OutsideViewport throttling = 10_s. subFrameRAFMessage
 34 // should not ever be received during the first second.
 35 shouldBeTrue("ifarmeFarmesPerSecond == 0");
 36 finishJSTest();
 37 }
 38 }
 39 window.requestAnimationFrame(doWork);
 40 }
 41 document.body.appendChild(frame);
 42 </script>
 43 <script src="../../resources/js-test-post.js"></script>
 44</body>
 45</html>

LayoutTests/fast/animation/resources/frame-with-animation-2.html

 1<script>
 2 function doWork(timestamp) {
 3 window.top.postMessage('subFrameRAFMessage', '*');
 4 window.requestAnimationFrame(doWork);
 5 }
 6 window.requestAnimationFrame(doWork);
 7</script>