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