02881305d8245a927ebe968862c9db201dd60ee8
[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 inline bool shouldForwardUserGesture(int interval, int nestingLevel)
53 {
54     return UserGestureIndicator::processingUserGesture()
55         && interval <= maxIntervalForUserGestureForwarding
56         && !nestingLevel; // Gestures should not be forwarded to nested timers.
57 }
58
59 DOMTimer::DOMTimer(ScriptExecutionContext* context, std::unique_ptr<ScheduledAction> action, int interval, bool singleShot)
60     : SuspendableTimer(context)
61     , m_nestingLevel(context->timerNestingLevel())
62     , m_action(WTF::move(action))
63     , m_originalInterval(interval)
64     , m_currentTimerInterval(intervalClampedToMinimum())
65     , m_shouldForwardUserGesture(shouldForwardUserGesture(interval, m_nestingLevel))
66 {
67     RefPtr<DOMTimer> reference = adoptRef(this);
68
69     // Keep asking for the next id until we're given one that we don't already have.
70     do {
71         m_timeoutId = context->circularSequentialID();
72     } while (!context->addTimeout(m_timeoutId, reference));
73
74     if (singleShot)
75         startOneShot(m_currentTimerInterval);
76     else
77         startRepeating(m_currentTimerInterval);
78 }
79
80 int DOMTimer::install(ScriptExecutionContext* context, std::unique_ptr<ScheduledAction> action, int timeout, bool singleShot)
81 {
82     // DOMTimer constructor passes ownership of the initial ref on the object to the constructor.
83     // This reference will be released automatically when a one-shot timer fires, when the context
84     // is destroyed, or if explicitly cancelled by removeById. 
85     DOMTimer* timer = new DOMTimer(context, WTF::move(action), timeout, singleShot);
86 #if PLATFORM(IOS)
87     if (context->isDocument()) {
88         Document& document = toDocument(*context);
89         bool didDeferTimeout = document.frame() && document.frame()->timersPaused();
90         if (!didDeferTimeout && timeout <= 100 && singleShot) {
91             WKSetObservedContentChange(WKContentIndeterminateChange);
92             WebThreadAddObservedContentModifier(timer); // Will only take affect if not already visibility change.
93         }
94     }
95 #endif
96
97     timer->suspendIfNeeded();
98     InspectorInstrumentation::didInstallTimer(context, timer->m_timeoutId, timeout, singleShot);
99
100     return timer->m_timeoutId;
101 }
102
103 void DOMTimer::removeById(ScriptExecutionContext* context, int timeoutId)
104 {
105     // timeout IDs have to be positive, and 0 and -1 are unsafe to
106     // even look up since they are the empty and deleted value
107     // respectively
108     if (timeoutId <= 0)
109         return;
110
111     InspectorInstrumentation::didRemoveTimer(context, timeoutId);
112     context->removeTimeout(timeoutId);
113 }
114
115 void DOMTimer::fired()
116 {
117     // Retain this - if the timer is cancelled while this function is on the stack (implicitly and always
118     // for one-shot timers, or if removeById is called on itself from within an interval timer fire) then
119     // wait unit the end of this function to delete DOMTimer.
120     RefPtr<DOMTimer> reference = this;
121
122     ScriptExecutionContext* context = scriptExecutionContext();
123     ASSERT(context);
124 #if PLATFORM(IOS)
125     Document* document = nullptr;
126     if (context->isDocument()) {
127         document = toDocument(context);
128         ASSERT(!document->frame()->timersPaused());
129     }
130 #endif
131     context->setTimerNestingLevel(std::min(m_nestingLevel + 1, maxTimerNestingLevel));
132
133     ASSERT(!isSuspended());
134     ASSERT(!context->activeDOMObjectsAreSuspended());
135     UserGestureIndicator gestureIndicator(m_shouldForwardUserGesture ? DefinitelyProcessingUserGesture : PossiblyProcessingUserGesture);
136     // Only the first execution of a multi-shot timer should get an affirmative user gesture indicator.
137     m_shouldForwardUserGesture = false;
138
139     InspectorInstrumentationCookie cookie = InspectorInstrumentation::willFireTimer(context, m_timeoutId);
140
141     // Simple case for non-one-shot timers.
142     if (isActive()) {
143         if (m_nestingLevel < maxTimerNestingLevel) {
144             m_nestingLevel++;
145             updateTimerIntervalIfNecessary();
146         }
147
148         m_action->execute(context);
149
150         InspectorInstrumentation::didFireTimer(cookie);
151
152         return;
153     }
154
155     context->removeTimeout(m_timeoutId);
156
157 #if PLATFORM(IOS)
158     bool shouldReportLackOfChanges;
159     bool shouldBeginObservingChanges;
160     if (document) {
161         shouldReportLackOfChanges = WebThreadCountOfObservedContentModifiers() == 1;
162         shouldBeginObservingChanges = WebThreadContainsObservedContentModifier(this);
163     } else {
164         shouldReportLackOfChanges = false;
165         shouldBeginObservingChanges = false;
166     }
167
168     if (shouldBeginObservingChanges) {
169         WKBeginObservingContentChanges(false);
170         WebThreadRemoveObservedContentModifier(this);
171     }
172 #endif
173
174     m_action->execute(context);
175
176 #if PLATFORM(IOS)
177     if (shouldBeginObservingChanges) {
178         WKStopObservingContentChanges();
179
180         if (WKObservedContentChange() == WKContentVisibilityChange || shouldReportLackOfChanges)
181             if (document && document->page())
182                 document->page()->chrome().client().observedContentChange(document->frame());
183     }
184 #endif
185
186     InspectorInstrumentation::didFireTimer(cookie);
187
188     context->setTimerNestingLevel(0);
189 }
190
191 void DOMTimer::didStop()
192 {
193     // Need to release JS objects potentially protected by ScheduledAction
194     // because they can form circular references back to the ScriptExecutionContext
195     // which will cause a memory leak.
196     m_action = nullptr;
197 }
198
199 void DOMTimer::updateTimerIntervalIfNecessary()
200 {
201     ASSERT(m_nestingLevel <= maxTimerNestingLevel);
202     if (m_nestingLevel < maxTimerNestingLevel)
203         return;
204
205     double previousInterval = m_currentTimerInterval;
206     m_currentTimerInterval = intervalClampedToMinimum();
207
208     if (previousInterval == m_currentTimerInterval)
209         return;
210
211     if (repeatInterval()) {
212         ASSERT(repeatInterval() == previousInterval);
213         augmentRepeatInterval(m_currentTimerInterval - previousInterval);
214     } else
215         augmentFireInterval(m_currentTimerInterval - previousInterval);
216 }
217
218 double DOMTimer::intervalClampedToMinimum() const
219 {
220     ASSERT(scriptExecutionContext());
221     ASSERT(m_nestingLevel <= maxTimerNestingLevel);
222
223     double minimumTimerInterval = scriptExecutionContext()->minimumTimerInterval();
224     double intervalMilliseconds = std::max(oneMillisecond, m_originalInterval * oneMillisecond);
225
226     if (intervalMilliseconds < minimumTimerInterval && m_nestingLevel == maxTimerNestingLevel)
227         intervalMilliseconds = minimumTimerInterval;
228     return intervalMilliseconds;
229 }
230
231 double DOMTimer::alignedFireTime(double fireTime) const
232 {
233     double alignmentInterval = scriptExecutionContext()->timerAlignmentInterval();
234     if (alignmentInterval) {
235         double currentTime = monotonicallyIncreasingTime();
236         if (fireTime <= currentTime)
237             return fireTime;
238
239         double alignedTime = ceil(fireTime / alignmentInterval) * alignmentInterval;
240         return alignedTime;
241     }
242
243     return fireTime;
244 }
245
246 } // namespace WebCore