Remove rAf suspension logging
[WebKit-https.git] / Source / WebCore / dom / ScriptedAnimationController.cpp
1 /*
2  * Copyright (C) 2011 Google 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. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
14  *  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15  *  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16  *  DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
17  *  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18  *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19  *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20  *  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21  *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22  *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  *
24  */
25
26 #include "config.h"
27 #include "ScriptedAnimationController.h"
28
29 #include "Chrome.h"
30 #include "ChromeClient.h"
31 #include "DOMWindow.h"
32 #include "DisplayRefreshMonitor.h"
33 #include "DisplayRefreshMonitorManager.h"
34 #include "Document.h"
35 #include "DocumentLoader.h"
36 #include "FrameView.h"
37 #include "InspectorInstrumentation.h"
38 #include "Logging.h"
39 #include "MainFrame.h"
40 #include "Page.h"
41 #include "RequestAnimationFrameCallback.h"
42 #include "Settings.h"
43 #include <algorithm>
44 #include <wtf/CurrentTime.h>
45 #include <wtf/Ref.h>
46 #include <wtf/SystemTracing.h>
47 #include <wtf/text/StringBuilder.h>
48
49 // Allow a little more than 60fps to make sure we can at least hit that frame rate.
50 static const Seconds fullSpeedAnimationInterval { 15_ms };
51 // Allow a little more than 30fps to make sure we can at least hit that frame rate.
52 static const Seconds halfSpeedThrottlingAnimationInterval { 30_ms };
53 static const Seconds aggressiveThrottlingAnimationInterval { 10_s };
54
55 #define RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(page() && page()->isAlwaysOnLoggingAllowed(), PerformanceLogging, "%p - ScriptedAnimationController::" fmt, this, ##__VA_ARGS__)
56
57 namespace WebCore {
58
59 ScriptedAnimationController::ScriptedAnimationController(Document& document, PlatformDisplayID displayID)
60     : m_document(&document)
61     , m_animationTimer(*this, &ScriptedAnimationController::animationTimerFired)
62 {
63     windowScreenDidChange(displayID);
64 }
65
66 ScriptedAnimationController::~ScriptedAnimationController()
67 {
68 }
69
70 bool ScriptedAnimationController::requestAnimationFrameEnabled() const
71 {
72     return m_document && m_document->settings().requestAnimationFrameEnabled();
73 }
74
75 void ScriptedAnimationController::suspend()
76 {
77     ++m_suspendCount;
78 }
79
80 void ScriptedAnimationController::resume()
81 {
82     // It would be nice to put an ASSERT(m_suspendCount > 0) here, but in WK1 resume() can be called
83     // even when suspend hasn't (if a tab was created in the background).
84     if (m_suspendCount > 0)
85         --m_suspendCount;
86
87     if (!m_suspendCount && m_callbacks.size())
88         scheduleAnimation();
89 }
90
91 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR) && !RELEASE_LOG_DISABLED
92
93 static const char* throttlingReasonToString(ScriptedAnimationController::ThrottlingReason reason)
94 {
95     switch (reason) {
96     case ScriptedAnimationController::ThrottlingReason::VisuallyIdle:
97         return "VisuallyIdle";
98     case ScriptedAnimationController::ThrottlingReason::OutsideViewport:
99         return "OutsideViewport";
100     case ScriptedAnimationController::ThrottlingReason::LowPowerMode:
101         return "LowPowerMode";
102     case ScriptedAnimationController::ThrottlingReason::NonInteractedCrossOriginFrame:
103         return "NonInteractiveCrossOriginFrame";
104     }
105 }
106
107 static String throttlingReasonsToString(OptionSet<ScriptedAnimationController::ThrottlingReason> reasons)
108 {
109     if (reasons.isEmpty())
110         return ASCIILiteral("[Unthrottled]");
111
112     StringBuilder builder;
113     for (auto reason : reasons) {
114         if (!builder.isEmpty())
115             builder.append('|');
116         builder.append(throttlingReasonToString(reason));
117     }
118     return builder.toString();
119 }
120
121 #endif
122
123 void ScriptedAnimationController::addThrottlingReason(ThrottlingReason reason)
124 {
125 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
126     if (m_throttlingReasons.contains(reason))
127         return;
128
129     m_throttlingReasons |= reason;
130
131     RELEASE_LOG_IF_ALLOWED("addThrottlingReason(%s) -> %s", throttlingReasonToString(reason), throttlingReasonsToString(m_throttlingReasons).utf8().data());
132
133     if (m_animationTimer.isActive()) {
134         m_animationTimer.stop();
135         scheduleAnimation();
136     }
137 #else
138     UNUSED_PARAM(reason);
139 #endif
140 }
141
142 void ScriptedAnimationController::removeThrottlingReason(ThrottlingReason reason)
143 {
144 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
145     if (!m_throttlingReasons.contains(reason))
146         return;
147
148     m_throttlingReasons -= reason;
149
150     RELEASE_LOG_IF_ALLOWED("removeThrottlingReason(%s) -> %s", throttlingReasonToString(reason), throttlingReasonsToString(m_throttlingReasons).utf8().data());
151
152     if (m_animationTimer.isActive()) {
153         m_animationTimer.stop();
154         scheduleAnimation();
155     }
156 #else
157     UNUSED_PARAM(reason);
158 #endif
159 }
160
161 bool ScriptedAnimationController::isThrottled() const
162 {
163 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
164     return !m_throttlingReasons.isEmpty();
165 #else
166     return false;
167 #endif
168 }
169
170 ScriptedAnimationController::CallbackId ScriptedAnimationController::registerCallback(Ref<RequestAnimationFrameCallback>&& callback)
171 {
172     ScriptedAnimationController::CallbackId id = ++m_nextCallbackId;
173     callback->m_firedOrCancelled = false;
174     callback->m_id = id;
175     m_callbacks.append(WTFMove(callback));
176
177     InspectorInstrumentation::didRequestAnimationFrame(m_document, id);
178
179     if (!m_suspendCount)
180         scheduleAnimation();
181     return id;
182 }
183
184 void ScriptedAnimationController::cancelCallback(CallbackId id)
185 {
186     for (size_t i = 0; i < m_callbacks.size(); ++i) {
187         if (m_callbacks[i]->m_id == id) {
188             m_callbacks[i]->m_firedOrCancelled = true;
189             InspectorInstrumentation::didCancelAnimationFrame(m_document, id);
190             m_callbacks.remove(i);
191             return;
192         }
193     }
194 }
195
196 void ScriptedAnimationController::serviceScriptedAnimations(double timestamp)
197 {
198     if (!m_callbacks.size() || m_suspendCount || !requestAnimationFrameEnabled())
199         return;
200
201     TraceScope tracingScope(RAFCallbackStart, RAFCallbackEnd);
202
203     double highResNowMs = 1000 * timestamp;
204     double legacyHighResNowMs = 1000 * (timestamp + m_document->loader()->timing().referenceWallTime().secondsSinceEpoch().seconds());
205
206     // First, generate a list of callbacks to consider.  Callbacks registered from this point
207     // on are considered only for the "next" frame, not this one.
208     CallbackList callbacks(m_callbacks);
209
210     // Invoking callbacks may detach elements from our document, which clears the document's
211     // reference to us, so take a defensive reference.
212     Ref<ScriptedAnimationController> protectedThis(*this);
213
214     for (auto& callback : callbacks) {
215         if (!callback->m_firedOrCancelled) {
216             callback->m_firedOrCancelled = true;
217             InspectorInstrumentationCookie cookie = InspectorInstrumentation::willFireAnimationFrame(m_document, callback->m_id);
218             if (callback->m_useLegacyTimeBase)
219                 callback->handleEvent(legacyHighResNowMs);
220             else
221                 callback->handleEvent(highResNowMs);
222             InspectorInstrumentation::didFireAnimationFrame(cookie);
223         }
224     }
225
226     // Remove any callbacks we fired from the list of pending callbacks.
227     for (size_t i = 0; i < m_callbacks.size();) {
228         if (m_callbacks[i]->m_firedOrCancelled)
229             m_callbacks.remove(i);
230         else
231             ++i;
232     }
233
234     if (m_callbacks.size())
235         scheduleAnimation();
236 }
237
238 void ScriptedAnimationController::windowScreenDidChange(PlatformDisplayID displayID)
239 {
240     if (!requestAnimationFrameEnabled())
241         return;
242 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
243     DisplayRefreshMonitorManager::sharedManager().windowScreenDidChange(displayID, *this);
244 #else
245     UNUSED_PARAM(displayID);
246 #endif
247 }
248
249 Seconds ScriptedAnimationController::interval() const
250 {
251 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
252     if (m_throttlingReasons.contains(ThrottlingReason::VisuallyIdle) || m_throttlingReasons.contains(ThrottlingReason::OutsideViewport))
253         return aggressiveThrottlingAnimationInterval;
254
255     if (m_throttlingReasons.contains(ThrottlingReason::LowPowerMode))
256         return halfSpeedThrottlingAnimationInterval;
257
258     if (m_throttlingReasons.contains(ThrottlingReason::NonInteractedCrossOriginFrame))
259         return halfSpeedThrottlingAnimationInterval;
260
261     ASSERT(m_throttlingReasons.isEmpty());
262 #endif
263     return fullSpeedAnimationInterval;
264 }
265
266 Page* ScriptedAnimationController::page() const
267 {
268     return m_document ? m_document->page() : nullptr;
269 }
270
271 void ScriptedAnimationController::scheduleAnimation()
272 {
273     if (!requestAnimationFrameEnabled())
274         return;
275
276 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
277     if (!m_isUsingTimer && !isThrottled()) {
278         if (DisplayRefreshMonitorManager::sharedManager().scheduleAnimation(*this))
279             return;
280
281         m_isUsingTimer = true;
282     }
283 #endif
284     if (m_animationTimer.isActive())
285         return;
286
287     Seconds animationInterval = interval();
288     Seconds scheduleDelay = std::max(animationInterval - Seconds(m_document->domWindow()->nowTimestamp() - m_lastAnimationFrameTimestamp), 0_s);
289
290     if (isThrottled()) {
291         // FIXME: not ideal to snapshot time both in now() and nowTimestamp(), the latter of which also has reduced resolution.
292         MonotonicTime now = MonotonicTime::now();
293
294         MonotonicTime fireTime = now + scheduleDelay;
295         Seconds alignmentInterval = 10_ms;
296         // Snap to the nearest alignmentInterval.
297         Seconds alignment = (fireTime + alignmentInterval / 2) % alignmentInterval;
298         MonotonicTime alignedFireTime = fireTime - alignment;
299         scheduleDelay = alignedFireTime - now;
300     }
301
302     m_animationTimer.startOneShot(scheduleDelay);
303 }
304
305 void ScriptedAnimationController::animationTimerFired()
306 {
307     m_lastAnimationFrameTimestamp = m_document->domWindow()->nowTimestamp();
308     serviceScriptedAnimations(m_lastAnimationFrameTimestamp);
309 }
310
311 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
312 void ScriptedAnimationController::displayRefreshFired()
313 {
314     serviceScriptedAnimations(m_document->domWindow()->nowTimestamp());
315 }
316
317 RefPtr<DisplayRefreshMonitor> ScriptedAnimationController::createDisplayRefreshMonitor(PlatformDisplayID displayID) const
318 {
319     if (!m_document->page())
320         return nullptr;
321
322     if (auto monitor = m_document->page()->chrome().client().createDisplayRefreshMonitor(displayID))
323         return monitor;
324
325     return DisplayRefreshMonitor::createDefaultDisplayRefreshMonitor(displayID);
326 }
327 #endif
328
329 }