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#if ENABLE(REQUEST_ANIMATION_FRAME)
30
31#include "DisplayRefreshMonitor.h"
32#include "DisplayRefreshMonitorManager.h"
33#include "Document.h"
34#include "DocumentLoader.h"
35#include "FrameView.h"
36#include "InspectorInstrumentation.h"
37#include "RequestAnimationFrameCallback.h"
38#include "Settings.h"
39#include <wtf/Ref.h>
40
41#if USE(REQUEST_ANIMATION_FRAME_TIMER)
42#include <algorithm>
43#include <wtf/CurrentTime.h>
44
45// Allow a little more than 60fps to make sure we can at least hit that frame rate.
46#define MinimumAnimationInterval 0.015
47#define MinimumThrottledAnimationInterval 10
48#endif
49
50namespace WebCore {
51
52ScriptedAnimationController::ScriptedAnimationController(Document* document, PlatformDisplayID displayID)
53    : m_document(document)
54    , m_nextCallbackId(0)
55    , m_suspendCount(0)
56#if USE(REQUEST_ANIMATION_FRAME_TIMER)
57    , m_animationTimer(this, &ScriptedAnimationController::animationTimerFired)
58    , m_lastAnimationFrameTimeMonotonic(0)
59#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
60    , m_isUsingTimer(false)
61    , m_isThrottled(false)
62#endif
63#endif
64{
65    windowScreenDidChange(displayID);
66}
67
68ScriptedAnimationController::~ScriptedAnimationController()
69{
70}
71
72void ScriptedAnimationController::suspend()
73{
74    ++m_suspendCount;
75}
76
77void 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
88void ScriptedAnimationController::setThrottled(bool isThrottled)
89{
90#if USE(REQUEST_ANIMATION_FRAME_TIMER) && USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
91    if (m_isThrottled == isThrottled)
92        return;
93
94    m_isThrottled = isThrottled;
95    if (m_animationTimer.isActive()) {
96        m_animationTimer.stop();
97        scheduleAnimation();
98    }
99#else
100    UNUSED_PARAM(isThrottled);
101#endif
102}
103
104ScriptedAnimationController::CallbackId ScriptedAnimationController::registerCallback(PassRefPtr<RequestAnimationFrameCallback> callback)
105{
106    ScriptedAnimationController::CallbackId id = ++m_nextCallbackId;
107    callback->m_firedOrCancelled = false;
108    callback->m_id = id;
109    m_callbacks.append(callback);
110
111    InspectorInstrumentation::didRequestAnimationFrame(m_document, id);
112
113    if (!m_suspendCount)
114        scheduleAnimation();
115    return id;
116}
117
118void ScriptedAnimationController::cancelCallback(CallbackId id)
119{
120    for (size_t i = 0; i < m_callbacks.size(); ++i) {
121        if (m_callbacks[i]->m_id == id) {
122            m_callbacks[i]->m_firedOrCancelled = true;
123            InspectorInstrumentation::didCancelAnimationFrame(m_document, id);
124            m_callbacks.remove(i);
125            return;
126        }
127    }
128}
129
130void ScriptedAnimationController::serviceScriptedAnimations(double monotonicTimeNow)
131{
132    if (!m_callbacks.size() || m_suspendCount || (m_document->settings() && !m_document->settings()->requestAnimationFrameEnabled()))
133        return;
134
135    double highResNowMs = 1000.0 * m_document->loader()->timing()->monotonicTimeToZeroBasedDocumentTime(monotonicTimeNow);
136    double legacyHighResNowMs = 1000.0 * m_document->loader()->timing()->monotonicTimeToPseudoWallTime(monotonicTimeNow);
137
138    // First, generate a list of callbacks to consider.  Callbacks registered from this point
139    // on are considered only for the "next" frame, not this one.
140    CallbackList callbacks(m_callbacks);
141
142    // Invoking callbacks may detach elements from our document, which clears the document's
143    // reference to us, so take a defensive reference.
144    Ref<ScriptedAnimationController> protect(*this);
145
146    for (size_t i = 0; i < callbacks.size(); ++i) {
147        RequestAnimationFrameCallback* callback = callbacks[i].get();
148        if (!callback->m_firedOrCancelled) {
149            callback->m_firedOrCancelled = true;
150            InspectorInstrumentationCookie cookie = InspectorInstrumentation::willFireAnimationFrame(m_document, callback->m_id);
151            if (callback->m_useLegacyTimeBase)
152                callback->handleEvent(legacyHighResNowMs);
153            else
154                callback->handleEvent(highResNowMs);
155            InspectorInstrumentation::didFireAnimationFrame(cookie);
156        }
157    }
158
159    // Remove any callbacks we fired from the list of pending callbacks.
160    for (size_t i = 0; i < m_callbacks.size();) {
161        if (m_callbacks[i]->m_firedOrCancelled)
162            m_callbacks.remove(i);
163        else
164            ++i;
165    }
166
167    if (m_callbacks.size())
168        scheduleAnimation();
169}
170
171void ScriptedAnimationController::windowScreenDidChange(PlatformDisplayID displayID)
172{
173    if (m_document->settings() && !m_document->settings()->requestAnimationFrameEnabled())
174        return;
175#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
176    DisplayRefreshMonitorManager::sharedManager().windowScreenDidChange(displayID, this);
177#else
178    UNUSED_PARAM(displayID);
179#endif
180}
181
182void ScriptedAnimationController::scheduleAnimation()
183{
184    if (!m_document || (m_document->settings() && !m_document->settings()->requestAnimationFrameEnabled()))
185        return;
186
187#if USE(REQUEST_ANIMATION_FRAME_TIMER)
188#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
189    if (!m_isUsingTimer && !m_isThrottled) {
190        if (DisplayRefreshMonitorManager::sharedManager().scheduleAnimation(this))
191            return;
192
193        m_isUsingTimer = true;
194    }
195#endif
196    if (m_animationTimer.isActive())
197        return;
198
199    double animationInterval = MinimumAnimationInterval;
200#if USE(REQUEST_ANIMATION_FRAME_TIMER) && USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
201    if (m_isThrottled)
202        animationInterval = MinimumThrottledAnimationInterval;
203#endif
204
205    double scheduleDelay = std::max<double>(animationInterval - (monotonicallyIncreasingTime() - m_lastAnimationFrameTimeMonotonic), 0);
206    m_animationTimer.startOneShot(scheduleDelay);
207#else
208    if (FrameView* frameView = m_document->view())
209        frameView->scheduleAnimation();
210#endif
211}
212
213#if USE(REQUEST_ANIMATION_FRAME_TIMER)
214void ScriptedAnimationController::animationTimerFired(Timer<ScriptedAnimationController>&)
215{
216    m_lastAnimationFrameTimeMonotonic = monotonicallyIncreasingTime();
217    serviceScriptedAnimations(m_lastAnimationFrameTimeMonotonic);
218}
219#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
220void ScriptedAnimationController::displayRefreshFired(double monotonicTimeNow)
221{
222    serviceScriptedAnimations(monotonicTimeNow);
223}
224#endif
225#endif
226
227
228#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
229PassRefPtr<DisplayRefreshMonitor> ScriptedAnimationController::createDisplayRefreshMonitor(PlatformDisplayID displayID) const
230{
231    return m_document->page()->chrome().client().createDisplayRefreshMonitor(displayID);
232}
233#endif
234
235
236}
237
238#endif
239