Home | History | Annotate | Download | only in page
      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 
     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 
     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 
     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 
     82 DOMTimer::~DOMTimer()
     83 {
     84     if (scriptExecutionContext())
     85         scriptExecutionContext()->removeTimeout(m_timeoutId);
     86 }
     87 
     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 
    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 
    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 
    155 void DOMTimer::contextDestroyed()
    156 {
    157     SuspendableTimer::contextDestroyed();
    158     delete this;
    159 }
    160 
    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 
    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 
    187 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