2 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
30 #include "HTMLPlugInElement.h"
31 #include "InspectorInstrumentation.h"
32 #include "PluginViewBase.h"
33 #include "ScheduledAction.h"
34 #include "ScriptExecutionContext.h"
35 #include "UserGestureIndicator.h"
36 #include <wtf/CurrentTime.h>
37 #include <wtf/HashSet.h>
38 #include <wtf/StdLibExtras.h>
42 #include "ChromeClient.h"
45 #include "WKContentObservation.h"
50 static const int maxIntervalForUserGestureForwarding = 1000; // One second matches Gecko.
51 static const int minIntervalForNonUserObservablePluginScriptTimers = 1000; // Empirically determined to maximize battery life.
52 static const int maxTimerNestingLevel = 5;
53 static const double oneMillisecond = 0.001;
55 struct DOMTimerFireState {
56 DOMTimerFireState(ScriptExecutionContext* context)
57 : scriptDidInteractWithNonUserObservablePlugin(false)
58 , scriptDidInteractWithUserObservablePlugin(false)
59 , shouldSetCurrent(context->isDocument())
61 // For worker threads, don't update the current DOMTimerFireState.
62 // Setting this from workers would not be thread-safe, and its not relevant to current uses.
63 if (shouldSetCurrent) {
75 static DOMTimerFireState* current;
77 bool scriptDidInteractWithNonUserObservablePlugin;
78 bool scriptDidInteractWithUserObservablePlugin;
81 bool shouldSetCurrent;
82 DOMTimerFireState* previous;
85 DOMTimerFireState* DOMTimerFireState::current = nullptr;
87 static inline bool shouldForwardUserGesture(int interval, int nestingLevel)
89 return UserGestureIndicator::processingUserGesture()
90 && interval <= maxIntervalForUserGestureForwarding
91 && !nestingLevel; // Gestures should not be forwarded to nested timers.
94 DOMTimer::DOMTimer(ScriptExecutionContext* context, std::unique_ptr<ScheduledAction> action, int interval, bool singleShot)
95 : SuspendableTimer(context)
96 , m_nestingLevel(context->timerNestingLevel())
97 , m_action(WTF::move(action))
98 , m_originalInterval(interval)
99 , m_throttleState(Undetermined)
100 , m_currentTimerInterval(intervalClampedToMinimum())
101 , m_shouldForwardUserGesture(shouldForwardUserGesture(interval, m_nestingLevel))
103 RefPtr<DOMTimer> reference = adoptRef(this);
105 // Keep asking for the next id until we're given one that we don't already have.
107 m_timeoutId = context->circularSequentialID();
108 } while (!context->addTimeout(m_timeoutId, reference));
111 startOneShot(m_currentTimerInterval);
113 startRepeating(m_currentTimerInterval);
116 int DOMTimer::install(ScriptExecutionContext* context, std::unique_ptr<ScheduledAction> action, int timeout, bool singleShot)
118 // DOMTimer constructor passes ownership of the initial ref on the object to the constructor.
119 // This reference will be released automatically when a one-shot timer fires, when the context
120 // is destroyed, or if explicitly cancelled by removeById.
121 DOMTimer* timer = new DOMTimer(context, WTF::move(action), timeout, singleShot);
123 if (context->isDocument()) {
124 Document& document = toDocument(*context);
125 bool didDeferTimeout = document.frame() && document.frame()->timersPaused();
126 if (!didDeferTimeout && timeout <= 100 && singleShot) {
127 WKSetObservedContentChange(WKContentIndeterminateChange);
128 WebThreadAddObservedContentModifier(timer); // Will only take affect if not already visibility change.
133 timer->suspendIfNeeded();
134 InspectorInstrumentation::didInstallTimer(context, timer->m_timeoutId, timeout, singleShot);
136 return timer->m_timeoutId;
139 void DOMTimer::removeById(ScriptExecutionContext* context, int timeoutId)
141 // timeout IDs have to be positive, and 0 and -1 are unsafe to
142 // even look up since they are the empty and deleted value
147 InspectorInstrumentation::didRemoveTimer(context, timeoutId);
148 context->removeTimeout(timeoutId);
151 void DOMTimer::scriptDidInteractWithPlugin(HTMLPlugInElement& pluginElement)
153 if (!DOMTimerFireState::current)
156 if (pluginElement.isUserObservable())
157 DOMTimerFireState::current->scriptDidInteractWithUserObservablePlugin = true;
159 DOMTimerFireState::current->scriptDidInteractWithNonUserObservablePlugin = true;
162 void DOMTimer::fired()
164 // Retain this - if the timer is cancelled while this function is on the stack (implicitly and always
165 // for one-shot timers, or if removeById is called on itself from within an interval timer fire) then
166 // wait unit the end of this function to delete DOMTimer.
167 RefPtr<DOMTimer> reference = this;
169 ScriptExecutionContext* context = scriptExecutionContext();
172 DOMTimerFireState fireState(context);
175 Document* document = nullptr;
176 if (context->isDocument()) {
177 document = toDocument(context);
178 ASSERT(!document->frame()->timersPaused());
181 context->setTimerNestingLevel(std::min(m_nestingLevel + 1, maxTimerNestingLevel));
183 ASSERT(!isSuspended());
184 ASSERT(!context->activeDOMObjectsAreSuspended());
185 UserGestureIndicator gestureIndicator(m_shouldForwardUserGesture ? DefinitelyProcessingUserGesture : PossiblyProcessingUserGesture);
186 // Only the first execution of a multi-shot timer should get an affirmative user gesture indicator.
187 m_shouldForwardUserGesture = false;
189 InspectorInstrumentationCookie cookie = InspectorInstrumentation::willFireTimer(context, m_timeoutId);
191 // Simple case for non-one-shot timers.
193 if (m_nestingLevel < maxTimerNestingLevel) {
195 updateTimerIntervalIfNecessary();
198 m_action->execute(context);
200 InspectorInstrumentation::didFireTimer(cookie);
202 if (fireState.scriptDidInteractWithUserObservablePlugin && m_throttleState != ShouldNotThrottle) {
203 m_throttleState = ShouldNotThrottle;
204 updateTimerIntervalIfNecessary();
205 } else if (fireState.scriptDidInteractWithNonUserObservablePlugin && m_throttleState == Undetermined) {
206 m_throttleState = ShouldThrottle;
207 updateTimerIntervalIfNecessary();
213 context->removeTimeout(m_timeoutId);
216 bool shouldReportLackOfChanges;
217 bool shouldBeginObservingChanges;
219 shouldReportLackOfChanges = WebThreadCountOfObservedContentModifiers() == 1;
220 shouldBeginObservingChanges = WebThreadContainsObservedContentModifier(this);
222 shouldReportLackOfChanges = false;
223 shouldBeginObservingChanges = false;
226 if (shouldBeginObservingChanges) {
227 WKBeginObservingContentChanges(false);
228 WebThreadRemoveObservedContentModifier(this);
232 m_action->execute(context);
235 if (shouldBeginObservingChanges) {
236 WKStopObservingContentChanges();
238 if (WKObservedContentChange() == WKContentVisibilityChange || shouldReportLackOfChanges)
239 if (document && document->page())
240 document->page()->chrome().client().observedContentChange(document->frame());
244 InspectorInstrumentation::didFireTimer(cookie);
246 context->setTimerNestingLevel(0);
249 void DOMTimer::didStop()
251 // Need to release JS objects potentially protected by ScheduledAction
252 // because they can form circular references back to the ScriptExecutionContext
253 // which will cause a memory leak.
257 void DOMTimer::updateTimerIntervalIfNecessary()
259 ASSERT(m_nestingLevel <= maxTimerNestingLevel);
261 double previousInterval = m_currentTimerInterval;
262 m_currentTimerInterval = intervalClampedToMinimum();
264 if (previousInterval == m_currentTimerInterval)
267 if (repeatInterval()) {
268 ASSERT(repeatInterval() == previousInterval);
269 augmentRepeatInterval(m_currentTimerInterval - previousInterval);
271 augmentFireInterval(m_currentTimerInterval - previousInterval);
274 double DOMTimer::intervalClampedToMinimum() const
276 ASSERT(scriptExecutionContext());
277 ASSERT(m_nestingLevel <= maxTimerNestingLevel);
279 double intervalInSeconds = std::max(oneMillisecond, m_originalInterval * oneMillisecond);
281 // Only apply throttling to repeating timers.
282 if (m_nestingLevel < maxTimerNestingLevel)
283 return intervalInSeconds;
285 // Apply two throttles - the global (per Page) minimum, and also a per-timer throttle.
286 intervalInSeconds = std::max(intervalInSeconds, scriptExecutionContext()->minimumTimerInterval());
287 if (m_throttleState == ShouldThrottle)
288 intervalInSeconds = std::max(intervalInSeconds, minIntervalForNonUserObservablePluginScriptTimers * oneMillisecond);
289 return intervalInSeconds;
292 double DOMTimer::alignedFireTime(double fireTime) const
294 double alignmentInterval = scriptExecutionContext()->timerAlignmentInterval();
295 if (alignmentInterval) {
296 double currentTime = monotonicallyIncreasingTime();
297 if (fireTime <= currentTime)
300 double alignedTime = ceil(fireTime / alignmentInterval) * alignmentInterval;
307 } // namespace WebCore