/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef mozilla_dom_OrderedTimeoutIterator_h__ #define mozilla_dom_OrderedTimeoutIterator_h__ #include "mozilla/RefPtr.h" #include "mozilla/dom/Timeout.h" #include "mozilla/dom/TimeoutManager.h" namespace mozilla { namespace dom { // This class implements and iterator which iterates the normal and tracking // timeouts lists simultaneously in the mWhen order. class MOZ_STACK_CLASS OrderedTimeoutIterator final { public: typedef TimeoutManager::Timeouts Timeouts; typedef Timeouts::TimeoutList TimeoutList; OrderedTimeoutIterator(Timeouts& aNormalTimeouts, Timeouts& aTrackingTimeouts) : mNormalTimeouts(aNormalTimeouts.mTimeoutList), mTrackingTimeouts(aTrackingTimeouts.mTimeoutList), mNormalIter(mNormalTimeouts.getFirst()), mTrackingIter(mTrackingTimeouts.getFirst()), mKind(Kind::None), mUpdateIteratorCalled(true) { } // Return the current timeout and move to the next one. // Unless this is the first time calling Next(), you must call // UpdateIterator() before calling this method. Timeout* Next() { MOZ_ASSERT(mUpdateIteratorCalled); MOZ_ASSERT_IF(mNormalIter, mNormalIter->isInList()); MOZ_ASSERT_IF(mTrackingIter, mTrackingIter->isInList()); mUpdateIteratorCalled = false; mKind = Kind::None; Timeout* timeout = nullptr; if (!mNormalIter) { if (!mTrackingIter) { // We have reached the end of both lists. Bail out! return nullptr; } else { // We have reached the end of the normal timeout list, select the next // tracking timeout. timeout = mTrackingIter; mKind = Kind::Tracking; } } else if (!mTrackingIter) { // We have reached the end of the tracking timeout list, select the next // normal timeout. timeout = mNormalIter; mKind = Kind::Normal; } else { // If we have a normal and a tracking timer, return the one with the // smaller mWhen (and prefer the timeout with a lower ID in case they are // equal.) Otherwise, return whichever iterator has an item left, // preferring a non-tracking timeout again. Note that in practice, even // if a web page calls setTimeout() twice in a row, it should get // different mWhen values, so in practice we shouldn't fall back to // comparing timeout IDs. if (mNormalIter && mTrackingIter && (mTrackingIter->When() < mNormalIter->When() || (mTrackingIter->When() == mNormalIter->When() && mTrackingIter->mTimeoutId < mNormalIter->mTimeoutId))) { timeout = mTrackingIter; mKind = Kind::Tracking; } else if (mNormalIter) { timeout = mNormalIter; mKind = Kind::Normal; } else if (mTrackingIter) { timeout = mTrackingIter; mKind = Kind::Tracking; } } if (!timeout) { // We didn't find any suitable iterator. This can happen for example // when getNext() in UpdateIterator() returns nullptr and then Next() // gets called. Bail out! return nullptr; } MOZ_ASSERT(mKind != Kind::None); // Record the current timeout we just found. mCurrent = timeout; MOZ_ASSERT(mCurrent); return mCurrent; } // Prepare the iterator for the next call to Next(). // This method can be called as many times as needed. Calling this more than // once is helpful in cases where we expect the timeouts list has been // modified before we got a chance to call Next(). void UpdateIterator() { MOZ_ASSERT(mKind != Kind::None); // Update the winning iterator to point to the next element. Also check to // see if the other iterator is still valid, otherwise reset it to the // beginning of the list. This is needed in case a timeout handler removes // the timeout pointed to from one of our iterators. if (mKind == Kind::Normal) { mNormalIter = mCurrent->getNext(); if (mTrackingIter && !mTrackingIter->isInList()) { mTrackingIter = mTrackingTimeouts.getFirst(); } } else { mTrackingIter = mCurrent->getNext(); if (mNormalIter && !mNormalIter->isInList()) { mNormalIter = mNormalTimeouts.getFirst(); } } mUpdateIteratorCalled = true; } // This function resets the iterator to a defunct state. It should only be // used when we want to forcefully sever all of the strong references this // class holds. void Clear() { // Release all strong references. mNormalIter = nullptr; mTrackingIter = nullptr; mCurrent = nullptr; mKind = Kind::None; mUpdateIteratorCalled = true; } // Returns true if the previous call to Next() picked a normal timeout. // Cannot be called before Next() has been called. Note that the result of // this method is only affected by Next() and not UpdateIterator(), so calling // UpdateIterator() before calling this is allowed. bool PickedNormalIter() const { MOZ_ASSERT(mKind != Kind::None); return mKind == Kind::Normal; } // Returns true if the previous call to Next() picked a tracking timeout. // Cannot be called before Next() has been called. Note that the result of // this method is only affected by Next() and not UpdateIterator(), so calling // UpdateIterator() before calling this is allowed. bool PickedTrackingIter() const { MOZ_ASSERT(mKind != Kind::None); return mKind == Kind::Tracking; } private: TimeoutList& mNormalTimeouts; // The list of normal timeouts. TimeoutList& mTrackingTimeouts; // The list of tracking timeouts. RefPtr mNormalIter; // The iterator over the normal timeout list. RefPtr mTrackingIter; // The iterator over the tracking timeout list. RefPtr mCurrent; // The current timeout that Next() just found. enum class Kind { Normal, Tracking, None }; Kind mKind; // The kind of iterator picked the last time. DebugOnly mUpdateIteratorCalled; // Whether we have called UpdateIterator() before calling Next(). }; } } #endif