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 46namespace WebCore { 47 48static const int maxIntervalForUserGestureForwarding = 1000; // One second matches Gecko. 49static const int maxTimerNestingLevel = 5; 50static const double oneMillisecond = 0.001; 51 52static int timerNestingLevel = 0; 53 54static 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 61DOMTimer::DOMTimer(ScriptExecutionContext* context, std::unique_ptr<ScheduledAction> action, int interval, bool singleShot) 62 : SuspendableTimer(context) 63 , m_nestingLevel(timerNestingLevel + 1) 64 , m_action(WTF::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 80DOMTimer::~DOMTimer() 81{ 82 if (scriptExecutionContext()) 83 scriptExecutionContext()->removeTimeout(m_timeoutId); 84} 85 86int 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, WTF::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 109void 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 122void 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 = WTF::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 199void DOMTimer::contextDestroyed() 200{ 201 SuspendableTimer::contextDestroyed(); 202 delete this; 203} 204 205void 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 213void 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 230double 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 239double 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 255