73f7207b09fd0664814279aac0ce4ed9a13ee016
[WebKit-https.git] / Source / WebCore / page / DOMTimer.cpp
1 /*
2  * Copyright (C) 2008 Apple 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. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  *
25  */
26
27 #include "config.h"
28 #include "DOMTimer.h"
29
30 #include "InspectorInstrumentation.h"
31 #include "ScheduledAction.h"
32 #include "ScriptExecutionContext.h"
33 #include "UserGestureIndicator.h"
34 #include <wtf/CurrentTime.h>
35 #include <wtf/HashSet.h>
36 #include <wtf/StdLibExtras.h>
37
38 #if PLATFORM(IOS)
39 #include "Chrome.h"
40 #include "ChromeClient.h"
41 #include "Frame.h"
42 #include "Page.h"
43 #include "WKContentObservation.h"
44 #endif
45
46 namespace WebCore {
47
48 static const int maxIntervalForUserGestureForwarding = 1000; // One second matches Gecko.
49 static const int maxTimerNestingLevel = 5;
50 static const double oneMillisecond = 0.001;
51
52 static int timerNestingLevel = 0;
53     
54 static inline bool shouldForwardUserGesture(int interval, int nestingLevel)
55 {
56     return UserGestureIndicator::processingUserGesture()
57         && interval <= maxIntervalForUserGestureForwarding
58         && nestingLevel == 1; // Gestures should not be forwarded to nested timers.
59 }
60
61 DOMTimer::DOMTimer(ScriptExecutionContext* context, std::unique_ptr<ScheduledAction> action, int interval, bool singleShot)
62     : SuspendableTimer(context)
63     , m_nestingLevel(timerNestingLevel + 1)
64     , m_action(std::move(action))
65     , m_originalInterval(interval)
66     , m_shouldForwardUserGesture(shouldForwardUserGesture(interval, m_nestingLevel))
67 {
68     // Keep asking for the next id until we're given one that we don't already have.
69     do {
70         m_timeoutId = context->circularSequentialID();
71     } while (!context->addTimeout(m_timeoutId, this));
72
73     double intervalMilliseconds = intervalClampedToMinimum(interval, context->minimumTimerInterval());
74     if (singleShot)
75         startOneShot(intervalMilliseconds);
76     else
77         startRepeating(intervalMilliseconds);
78 }
79
80 DOMTimer::~DOMTimer()
81 {
82     if (scriptExecutionContext())
83         scriptExecutionContext()->removeTimeout(m_timeoutId);
84 }
85
86 int DOMTimer::install(ScriptExecutionContext* context, std::unique_ptr<ScheduledAction> action, int timeout, bool singleShot)
87 {
88     // DOMTimer constructor links the new timer into a list of ActiveDOMObjects held by the 'context'.
89     // The timer is deleted when context is deleted (DOMTimer::contextDestroyed) or explicitly via DOMTimer::removeById(),
90     // or if it is a one-time timer and it has fired (DOMTimer::fired).
91     DOMTimer* timer = new DOMTimer(context, std::move(action), timeout, singleShot);
92 #if PLATFORM(IOS)
93     if (context->isDocument()) {
94         Document& document = toDocument(*context);
95         bool didDeferTimeout = document.frame() && document.frame()->timersPaused();
96         if (!didDeferTimeout && timeout <= 100 && singleShot) {
97             WKSetObservedContentChange(WKContentIndeterminateChange);
98             WebThreadAddObservedContentModifier(timer); // Will only take affect if not already visibility change.
99         }
100     }
101 #endif
102
103     timer->suspendIfNeeded();
104     InspectorInstrumentation::didInstallTimer(context, timer->m_timeoutId, timeout, singleShot);
105
106     return timer->m_timeoutId;
107 }
108
109 void DOMTimer::removeById(ScriptExecutionContext* context, int timeoutId)
110 {
111     // timeout IDs have to be positive, and 0 and -1 are unsafe to
112     // even look up since they are the empty and deleted value
113     // respectively
114     if (timeoutId <= 0)
115         return;
116
117     InspectorInstrumentation::didRemoveTimer(context, timeoutId);
118
119     delete context->findTimeout(timeoutId);
120 }
121
122 void DOMTimer::fired()
123 {
124     ScriptExecutionContext* context = scriptExecutionContext();
125     ASSERT(context);
126 #if PLATFORM(IOS)
127     Document* document = nullptr;
128     if (context->isDocument()) {
129         document = toDocument(context);
130         ASSERT(!document->frame()->timersPaused());
131     }
132 #endif
133     timerNestingLevel = m_nestingLevel;
134     ASSERT(!isSuspended());
135     ASSERT(!context->activeDOMObjectsAreSuspended());
136     UserGestureIndicator gestureIndicator(m_shouldForwardUserGesture ? DefinitelyProcessingUserGesture : PossiblyProcessingUserGesture);
137     // Only the first execution of a multi-shot timer should get an affirmative user gesture indicator.
138     m_shouldForwardUserGesture = false;
139
140     InspectorInstrumentationCookie cookie = InspectorInstrumentation::willFireTimer(context, m_timeoutId);
141
142     // Simple case for non-one-shot timers.
143     if (isActive()) {
144         double minimumInterval = context->minimumTimerInterval();
145         if (repeatInterval() && repeatInterval() < minimumInterval) {
146             m_nestingLevel++;
147             if (m_nestingLevel >= maxTimerNestingLevel)
148                 augmentRepeatInterval(minimumInterval - repeatInterval());
149         }
150
151         // No access to member variables after this point, it can delete the timer.
152         m_action->execute(context);
153
154         InspectorInstrumentation::didFireTimer(cookie);
155
156         return;
157     }
158
159     // Delete timer before executing the action for one-shot timers.
160     std::unique_ptr<ScheduledAction> action = std::move(m_action);
161
162     // No access to member variables after this point.
163     delete this;
164
165 #if PLATFORM(IOS)
166     bool shouldReportLackOfChanges;
167     bool shouldBeginObservingChanges;
168     if (document) {
169         shouldReportLackOfChanges = WebThreadCountOfObservedContentModifiers() == 1;
170         shouldBeginObservingChanges = WebThreadContainsObservedContentModifier(this);
171     } else {
172         shouldReportLackOfChanges = false;
173         shouldBeginObservingChanges = false;
174     }
175
176     if (shouldBeginObservingChanges) {
177         WKBeginObservingContentChanges(false);
178         WebThreadRemoveObservedContentModifier(this);
179     }
180 #endif
181
182     action->execute(context);
183
184 #if PLATFORM(IOS)
185     if (shouldBeginObservingChanges) {
186         WKStopObservingContentChanges();
187
188         if (WKObservedContentChange() == WKContentVisibilityChange || shouldReportLackOfChanges)
189             if (document && document->page())
190                 document->page()->chrome().client().observedContentChange(document->frame());
191     }
192 #endif
193
194     InspectorInstrumentation::didFireTimer(cookie);
195
196     timerNestingLevel = 0;
197 }
198
199 void DOMTimer::contextDestroyed()
200 {
201     SuspendableTimer::contextDestroyed();
202     delete this;
203 }
204
205 void DOMTimer::didStop()
206 {
207     // Need to release JS objects potentially protected by ScheduledAction
208     // because they can form circular references back to the ScriptExecutionContext
209     // which will cause a memory leak.
210     m_action = nullptr;
211 }
212
213 void DOMTimer::adjustMinimumTimerInterval(double oldMinimumTimerInterval)
214 {
215     if (m_nestingLevel < maxTimerNestingLevel)
216         return;
217
218     double newMinimumInterval = scriptExecutionContext()->minimumTimerInterval();
219     double newClampedInterval = intervalClampedToMinimum(m_originalInterval, newMinimumInterval);
220
221     if (repeatInterval()) {
222         augmentRepeatInterval(newClampedInterval - repeatInterval());
223         return;
224     }
225
226     double previousClampedInterval = intervalClampedToMinimum(m_originalInterval, oldMinimumTimerInterval);
227     augmentFireInterval(newClampedInterval - previousClampedInterval);
228 }
229
230 double DOMTimer::intervalClampedToMinimum(int timeout, double minimumTimerInterval) const
231 {
232     double intervalMilliseconds = std::max(oneMillisecond, timeout * oneMillisecond);
233
234     if (intervalMilliseconds < minimumTimerInterval && m_nestingLevel >= maxTimerNestingLevel)
235         intervalMilliseconds = minimumTimerInterval;
236     return intervalMilliseconds;
237 }
238
239 double DOMTimer::alignedFireTime(double fireTime) const
240 {
241     double alignmentInterval = scriptExecutionContext()->timerAlignmentInterval();
242     if (alignmentInterval) {
243         double currentTime = monotonicallyIncreasingTime();
244         if (fireTime <= currentTime)
245             return fireTime;
246
247         double alignedTime = ceil(fireTime / alignmentInterval) * alignmentInterval;
248         return alignedTime;
249     }
250
251     return fireTime;
252 }
253
254 } // namespace WebCore