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