Use "= default" to denote default constructor or destructor
[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() = default;
67
68 bool ScriptedAnimationController::requestAnimationFrameEnabled() const
69 {
70     return m_document && m_document->settings().requestAnimationFrameEnabled();
71 }
72
73 void ScriptedAnimationController::suspend()
74 {
75     ++m_suspendCount;
76 }
77
78 void ScriptedAnimationController::resume()
79 {
80     // It would be nice to put an ASSERT(m_suspendCount > 0) here, but in WK1 resume() can be called
81     // even when suspend hasn't (if a tab was created in the background).
82     if (m_suspendCount > 0)
83         --m_suspendCount;
84
85     if (!m_suspendCount && m_callbacks.size())
86         scheduleAnimation();
87 }
88
89 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR) && !RELEASE_LOG_DISABLED
90
91 static const char* throttlingReasonToString(ScriptedAnimationController::ThrottlingReason reason)
92 {
93     switch (reason) {
94     case ScriptedAnimationController::ThrottlingReason::VisuallyIdle:
95         return "VisuallyIdle";
96     case ScriptedAnimationController::ThrottlingReason::OutsideViewport:
97         return "OutsideViewport";
98     case ScriptedAnimationController::ThrottlingReason::LowPowerMode:
99         return "LowPowerMode";
100     case ScriptedAnimationController::ThrottlingReason::NonInteractedCrossOriginFrame:
101         return "NonInteractiveCrossOriginFrame";
102     }
103 }
104
105 static String throttlingReasonsToString(OptionSet<ScriptedAnimationController::ThrottlingReason> reasons)
106 {
107     if (reasons.isEmpty())
108         return ASCIILiteral("[Unthrottled]");
109
110     StringBuilder builder;
111     for (auto reason : reasons) {
112         if (!builder.isEmpty())
113             builder.append('|');
114         builder.append(throttlingReasonToString(reason));
115     }
116     return builder.toString();
117 }
118
119 #endif
120
121 void ScriptedAnimationController::addThrottlingReason(ThrottlingReason reason)
122 {
123 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
124     if (m_throttlingReasons.contains(reason))
125         return;
126
127     m_throttlingReasons |= reason;
128
129     RELEASE_LOG_IF_ALLOWED("addThrottlingReason(%s) -> %s", throttlingReasonToString(reason), throttlingReasonsToString(m_throttlingReasons).utf8().data());
130
131     if (m_animationTimer.isActive()) {
132         m_animationTimer.stop();
133         scheduleAnimation();
134     }
135 #else
136     UNUSED_PARAM(reason);
137 #endif
138 }
139
140 void ScriptedAnimationController::removeThrottlingReason(ThrottlingReason reason)
141 {
142 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
143     if (!m_throttlingReasons.contains(reason))
144         return;
145
146     m_throttlingReasons -= reason;
147
148     RELEASE_LOG_IF_ALLOWED("removeThrottlingReason(%s) -> %s", throttlingReasonToString(reason), throttlingReasonsToString(m_throttlingReasons).utf8().data());
149
150     if (m_animationTimer.isActive()) {
151         m_animationTimer.stop();
152         scheduleAnimation();
153     }
154 #else
155     UNUSED_PARAM(reason);
156 #endif
157 }
158
159 bool ScriptedAnimationController::isThrottled() const
160 {
161 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
162     return !m_throttlingReasons.isEmpty();
163 #else
164     return false;
165 #endif
166 }
167
168 ScriptedAnimationController::CallbackId ScriptedAnimationController::registerCallback(Ref<RequestAnimationFrameCallback>&& callback)
169 {
170     ScriptedAnimationController::CallbackId id = ++m_nextCallbackId;
171     callback->m_firedOrCancelled = false;
172     callback->m_id = id;
173     m_callbacks.append(WTFMove(callback));
174
175     if (m_document)
176         InspectorInstrumentation::didRequestAnimationFrame(*m_document, id);
177
178     if (!m_suspendCount)
179         scheduleAnimation();
180     return id;
181 }
182
183 void ScriptedAnimationController::cancelCallback(CallbackId id)
184 {
185     for (size_t i = 0; i < m_callbacks.size(); ++i) {
186         if (m_callbacks[i]->m_id == id) {
187             m_callbacks[i]->m_firedOrCancelled = true;
188             InspectorInstrumentation::didCancelAnimationFrame(*m_document, id);
189             m_callbacks.remove(i);
190             return;
191         }
192     }
193 }
194
195 void ScriptedAnimationController::serviceScriptedAnimations(double timestamp)
196 {
197     if (!m_callbacks.size() || m_suspendCount || !requestAnimationFrameEnabled())
198         return;
199
200     TraceScope tracingScope(RAFCallbackStart, RAFCallbackEnd);
201
202     double highResNowMs = 1000 * timestamp;
203     double legacyHighResNowMs = 1000 * (timestamp + m_document->loader()->timing().referenceWallTime().secondsSinceEpoch().seconds());
204
205     // First, generate a list of callbacks to consider.  Callbacks registered from this point
206     // on are considered only for the "next" frame, not this one.
207     CallbackList callbacks(m_callbacks);
208
209     // Invoking callbacks may detach elements from our document, which clears the document's
210     // reference to us, so take a defensive reference.
211     Ref<ScriptedAnimationController> protectedThis(*this);
212
213     for (auto& callback : callbacks) {
214         if (!callback->m_firedOrCancelled) {
215             callback->m_firedOrCancelled = true;
216             InspectorInstrumentationCookie cookie = InspectorInstrumentation::willFireAnimationFrame(*m_document, 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 }