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