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 COMPUTER, 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 COMPUTER, 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/HashSet.h>
35 #include <wtf/StdLibExtras.h>
36
37 using namespace std;
38
39 namespace WebCore {
40
41 static const int maxIntervalForUserGestureForwarding = 1000; // One second matches Gecko.
42 static const int maxTimerNestingLevel = 5;
43 static const double oneMillisecond = 0.001;
44 double DOMTimer::s_minDefaultTimerInterval = 0.010; // 10 milliseconds
45
46 static int timerNestingLevel = 0;
47
timeoutId()48 static int timeoutId()
49 {
50 static int lastUsedTimeoutId = 0;
51 ++lastUsedTimeoutId;
52 // Avoid wraparound going negative on us.
53 if (lastUsedTimeoutId <= 0)
54 lastUsedTimeoutId = 1;
55 return lastUsedTimeoutId;
56 }
57
shouldForwardUserGesture(int interval,int nestingLevel)58 static inline bool shouldForwardUserGesture(int interval, int nestingLevel)
59 {
60 return UserGestureIndicator::processingUserGesture()
61 && interval <= maxIntervalForUserGestureForwarding
62 && nestingLevel == 1; // Gestures should not be forwarded to nested timers.
63 }
64
DOMTimer(ScriptExecutionContext * context,PassOwnPtr<ScheduledAction> action,int interval,bool singleShot)65 DOMTimer::DOMTimer(ScriptExecutionContext* context, PassOwnPtr<ScheduledAction> action, int interval, bool singleShot)
66 : SuspendableTimer(context)
67 , m_timeoutId(timeoutId())
68 , m_nestingLevel(timerNestingLevel + 1)
69 , m_action(action)
70 , m_originalInterval(interval)
71 , m_shouldForwardUserGesture(shouldForwardUserGesture(interval, m_nestingLevel))
72 {
73 scriptExecutionContext()->addTimeout(m_timeoutId, this);
74
75 double intervalMilliseconds = intervalClampedToMinimum(interval, context->minimumTimerInterval());
76 if (singleShot)
77 startOneShot(intervalMilliseconds);
78 else
79 startRepeating(intervalMilliseconds);
80 }
81
~DOMTimer()82 DOMTimer::~DOMTimer()
83 {
84 if (scriptExecutionContext())
85 scriptExecutionContext()->removeTimeout(m_timeoutId);
86 }
87
install(ScriptExecutionContext * context,PassOwnPtr<ScheduledAction> action,int timeout,bool singleShot)88 int DOMTimer::install(ScriptExecutionContext* context, PassOwnPtr<ScheduledAction> action, int timeout, bool singleShot)
89 {
90 // DOMTimer constructor links the new timer into a list of ActiveDOMObjects held by the 'context'.
91 // The timer is deleted when context is deleted (DOMTimer::contextDestroyed) or explicitly via DOMTimer::removeById(),
92 // or if it is a one-time timer and it has fired (DOMTimer::fired).
93 DOMTimer* timer = new DOMTimer(context, action, timeout, singleShot);
94
95 InspectorInstrumentation::didInstallTimer(context, timer->m_timeoutId, timeout, singleShot);
96
97 return timer->m_timeoutId;
98 }
99
removeById(ScriptExecutionContext * context,int timeoutId)100 void DOMTimer::removeById(ScriptExecutionContext* context, int timeoutId)
101 {
102 // timeout IDs have to be positive, and 0 and -1 are unsafe to
103 // even look up since they are the empty and deleted value
104 // respectively
105 if (timeoutId <= 0)
106 return;
107
108 InspectorInstrumentation::didRemoveTimer(context, timeoutId);
109
110 delete context->findTimeout(timeoutId);
111 }
112
fired()113 void DOMTimer::fired()
114 {
115 ScriptExecutionContext* context = scriptExecutionContext();
116 timerNestingLevel = m_nestingLevel;
117
118 UserGestureIndicator gestureIndicator(m_shouldForwardUserGesture ? DefinitelyProcessingUserGesture : PossiblyProcessingUserGesture);
119
120 // Only the first execution of a multi-shot timer should get an affirmative user gesture indicator.
121 m_shouldForwardUserGesture = false;
122
123 InspectorInstrumentationCookie cookie = InspectorInstrumentation::willFireTimer(context, m_timeoutId);
124
125 // Simple case for non-one-shot timers.
126 if (isActive()) {
127 double minimumInterval = context->minimumTimerInterval();
128 if (repeatInterval() && repeatInterval() < minimumInterval) {
129 m_nestingLevel++;
130 if (m_nestingLevel >= maxTimerNestingLevel)
131 augmentRepeatInterval(minimumInterval - repeatInterval());
132 }
133
134 // No access to member variables after this point, it can delete the timer.
135 m_action->execute(context);
136
137 InspectorInstrumentation::didFireTimer(cookie);
138
139 return;
140 }
141
142 // Delete timer before executing the action for one-shot timers.
143 OwnPtr<ScheduledAction> action = m_action.release();
144
145 // No access to member variables after this point.
146 delete this;
147
148 action->execute(context);
149
150 InspectorInstrumentation::didFireTimer(cookie);
151
152 timerNestingLevel = 0;
153 }
154
contextDestroyed()155 void DOMTimer::contextDestroyed()
156 {
157 SuspendableTimer::contextDestroyed();
158 delete this;
159 }
160
stop()161 void DOMTimer::stop()
162 {
163 SuspendableTimer::stop();
164 // Need to release JS objects potentially protected by ScheduledAction
165 // because they can form circular references back to the ScriptExecutionContext
166 // which will cause a memory leak.
167 m_action.clear();
168 }
169
adjustMinimumTimerInterval(double oldMinimumTimerInterval)170 void DOMTimer::adjustMinimumTimerInterval(double oldMinimumTimerInterval)
171 {
172 if (m_nestingLevel < maxTimerNestingLevel)
173 return;
174
175 double newMinimumInterval = scriptExecutionContext()->minimumTimerInterval();
176 double newClampedInterval = intervalClampedToMinimum(m_originalInterval, newMinimumInterval);
177
178 if (repeatInterval()) {
179 augmentRepeatInterval(newClampedInterval - repeatInterval());
180 return;
181 }
182
183 double previousClampedInterval = intervalClampedToMinimum(m_originalInterval, oldMinimumTimerInterval);
184 augmentFireInterval(newClampedInterval - previousClampedInterval);
185 }
186
intervalClampedToMinimum(int timeout,double minimumTimerInterval) const187 double DOMTimer::intervalClampedToMinimum(int timeout, double minimumTimerInterval) const
188 {
189 double intervalMilliseconds = max(oneMillisecond, timeout * oneMillisecond);
190
191 if (intervalMilliseconds < minimumTimerInterval && m_nestingLevel >= maxTimerNestingLevel)
192 intervalMilliseconds = minimumTimerInterval;
193 return intervalMilliseconds;
194 }
195
196 } // namespace WebCore
197