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