/* * Copyright (C) 2008 Apple Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include "sky/engine/config.h" #include "sky/engine/core/frame/DOMTimer.h" #include "sky/engine/core/dom/ExecutionContext.h" #include "sky/engine/platform/Logging.h" #include "sky/engine/platform/TraceEvent.h" #include "sky/engine/wtf/CurrentTime.h" namespace blink { static const int maxTimerNestingLevel = 5; static const double oneMillisecond = 0.001; // Chromium uses a minimum timer interval of 4ms. We'd like to go // lower; however, there are poorly coded websites out there which do // create CPU-spinning loops. Using 4ms prevents the CPU from // spinning too busily and provides a balance between CPU spinning and // the smallest possible interval timer. static const double minimumInterval = 0.004; static int timerNestingLevel = 0; double DOMTimer::hiddenPageAlignmentInterval() { // Timers on hidden pages are aligned so that they fire once per // second at most. return 1.0; } double DOMTimer::visiblePageAlignmentInterval() { // Alignment does not apply to timers on visible pages. return 0; } int DOMTimer::install(ExecutionContext* context, PassOwnPtr action, int timeout, bool singleShot) { int timeoutID = context->installNewTimeout(action, timeout, singleShot); WTF_LOG(Timers, "DOMTimer::install: timeoutID = %d, timeout = %d, singleShot = %d", timeoutID, timeout, singleShot ? 1 : 0); return timeoutID; } void DOMTimer::removeByID(ExecutionContext* context, int timeoutID) { WTF_LOG(Timers, "DOMTimer::removeByID: timeoutID = %d", timeoutID); context->removeTimeoutByID(timeoutID); } DOMTimer::DOMTimer(ExecutionContext* context, PassOwnPtr action, int interval, bool singleShot, int timeoutID) : SuspendableTimer(context) , m_timeoutID(timeoutID) , m_nestingLevel(timerNestingLevel + 1) , m_action(action) { ASSERT(timeoutID > 0); double intervalMilliseconds = std::max(oneMillisecond, interval * oneMillisecond); if (intervalMilliseconds < minimumInterval && m_nestingLevel >= maxTimerNestingLevel) intervalMilliseconds = minimumInterval; if (singleShot) startOneShot(intervalMilliseconds, FROM_HERE); else startRepeating(intervalMilliseconds, FROM_HERE); } DOMTimer::~DOMTimer() { } int DOMTimer::timeoutID() const { return m_timeoutID; } void DOMTimer::fired() { ExecutionContext* context = executionContext(); timerNestingLevel = m_nestingLevel; ASSERT(!context->activeDOMObjectsAreSuspended()); // Simple case for non-one-shot timers. if (isActive()) { if (repeatInterval() && repeatInterval() < minimumInterval) { m_nestingLevel++; if (m_nestingLevel >= maxTimerNestingLevel) augmentRepeatInterval(minimumInterval - repeatInterval()); } WTF_LOG(Timers, "DOMTimer::fired: m_timeoutID = %d, repeatInterval = %f, m_action = %p", m_timeoutID, repeatInterval(), m_action.get()); // No access to member variables after this point, it can delete the timer. m_action->Execute(context); return; } WTF_LOG(Timers, "DOMTimer::fired: m_timeoutID = %d, one-shot, m_action = %p", m_timeoutID, m_action.get()); // Delete timer before executing the action for one-shot timers. OwnPtr action = m_action.release(); // This timer is being deleted; no access to member variables allowed after this point. context->removeTimeoutByID(m_timeoutID); action->Execute(context); timerNestingLevel = 0; } void DOMTimer::contextDestroyed() { SuspendableTimer::contextDestroyed(); } void DOMTimer::stop() { SuspendableTimer::stop(); // Need to release JS objects potentially protected by ScheduledAction // because they can form circular references back to the ExecutionContext // which will cause a memory leak. m_action.clear(); } double DOMTimer::alignedFireTime(double fireTime) const { double alignmentInterval = executionContext()->timerAlignmentInterval(); if (alignmentInterval) { double currentTime = monotonicallyIncreasingTime(); if (fireTime <= currentTime) return fireTime; // When a repeating timer is scheduled for exactly the // background page alignment interval, because it's impossible // for the timer to be rescheduled instantaneously, it misses // every other fire time. Avoid this by looking at the next // fire time rounded both down and up. double alignedTimeRoundedDown = floor(fireTime / alignmentInterval) * alignmentInterval; double alignedTimeRoundedUp = ceil(fireTime / alignmentInterval) * alignmentInterval; // If the version rounded down is in the past, discard it // immediately. if (alignedTimeRoundedDown <= currentTime) return alignedTimeRoundedUp; // Only use the rounded-down time if it's within a certain // tolerance of the fire time. This avoids speeding up timers // on background pages in the common case. if (fireTime - alignedTimeRoundedDown < minimumInterval) return alignedTimeRoundedDown; return alignedTimeRoundedUp; } return fireTime; } } // namespace blink