tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

TimeoutManager.cpp (51928B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "TimeoutManager.h"
      8 
      9 #include "TimeoutExecutor.h"
     10 #include "mozilla/Logging.h"
     11 #include "mozilla/MediaManager.h"
     12 #include "mozilla/ProfilerMarkers.h"
     13 #include "mozilla/ScopeExit.h"
     14 #include "mozilla/StaticPrefs_dom.h"
     15 #include "mozilla/StaticPrefs_privacy.h"
     16 #include "mozilla/ThrottledEventQueue.h"
     17 #include "mozilla/TimeStamp.h"
     18 #include "mozilla/dom/ContentChild.h"
     19 #include "mozilla/dom/DocGroup.h"
     20 #include "mozilla/dom/Document.h"
     21 #include "mozilla/dom/PopupBlocker.h"
     22 #include "mozilla/dom/TimeoutHandler.h"
     23 #include "mozilla/dom/WebTaskScheduler.h"
     24 #include "mozilla/dom/WorkerScope.h"
     25 #include "mozilla/net/WebSocketEventService.h"
     26 #include "nsGlobalWindowInner.h"
     27 #include "nsIGlobalObject.h"
     28 #include "nsINamed.h"
     29 
     30 using namespace mozilla;
     31 using namespace mozilla::dom;
     32 
     33 LazyLogModule gTimeoutLog("Timeout");
     34 
     35 TimeoutBudgetManager TimeoutManager::sBudgetManager{};
     36 
     37 // static
     38 const uint32_t TimeoutManager::InvalidFiringId = 0;
     39 
     40 namespace {
     41 static int32_t gRunningTimeoutDepth = 0;
     42 
     43 double GetRegenerationFactor(bool aIsBackground) {
     44  // Lookup function for "dom.timeout.{background,
     45  // foreground}_budget_regeneration_rate".
     46 
     47  // Returns the rate of regeneration of the execution budget as a
     48  // fraction. If the value is 1.0, the amount of time regenerated is
     49  // equal to time passed. At this rate we regenerate 1ms/ms. If it is
     50  // 0.01 the amount regenerated is 1% of time passed. At this rate we
     51  // regenerate 1ms/100ms, etc.
     52  double denominator = std::max(
     53      aIsBackground
     54          ? StaticPrefs::dom_timeout_background_budget_regeneration_rate()
     55          : StaticPrefs::dom_timeout_foreground_budget_regeneration_rate(),
     56      1);
     57  return 1.0 / denominator;
     58 }
     59 
     60 TimeDuration GetMaxBudget(bool aIsBackground) {
     61  // Lookup function for "dom.timeout.{background,
     62  // foreground}_throttling_max_budget".
     63 
     64  // Returns how high a budget can be regenerated before being
     65  // clamped. If this value is less or equal to zero,
     66  // TimeDuration::Forever() is implied.
     67  int32_t maxBudget =
     68      aIsBackground
     69          ? StaticPrefs::dom_timeout_background_throttling_max_budget()
     70          : StaticPrefs::dom_timeout_foreground_throttling_max_budget();
     71  return maxBudget > 0 ? TimeDuration::FromMilliseconds(maxBudget)
     72                       : TimeDuration::Forever();
     73 }
     74 
     75 TimeDuration GetMinBudget(bool aIsBackground) {
     76  // The minimum budget is computed by looking up the maximum allowed
     77  // delay and computing how long time it would take to regenerate
     78  // that budget using the regeneration factor. This number is
     79  // expected to be negative.
     80  return TimeDuration::FromMilliseconds(
     81      -StaticPrefs::dom_timeout_budget_throttling_max_delay() /
     82      std::max(
     83          aIsBackground
     84              ? StaticPrefs::dom_timeout_background_budget_regeneration_rate()
     85              : StaticPrefs::dom_timeout_foreground_budget_regeneration_rate(),
     86          1));
     87 }
     88 }  // namespace
     89 
     90 //
     91 nsGlobalWindowInner* TimeoutManager::GetInnerWindow() const {
     92  return nsGlobalWindowInner::Cast(mGlobalObject.GetAsInnerWindow());
     93 }
     94 
     95 bool TimeoutManager::IsBackground() const {
     96  return !IsActive() && mGlobalObject.IsBackgroundInternal();
     97 }
     98 
     99 bool TimeoutManager::IsActive() const {
    100  // A window/worker is considered active if:
    101  // * It is a chrome window/worker
    102  // * It is playing audio
    103  //
    104  // Note that a window/worker can be considered active if it is either in the
    105  // foreground or in the background.
    106 
    107  nsGlobalWindowInner* window = GetInnerWindow();
    108  if (window && window->IsChromeWindow()) {
    109    return true;
    110  }
    111 
    112  if (mIsChromeWorker) {
    113    return true;
    114  }
    115 
    116  // Check if we're playing audio
    117  if (mGlobalObject.IsPlayingAudio()) {
    118    return true;
    119  }
    120 
    121  return false;
    122 }
    123 
    124 void TimeoutManager::SetLoading(bool value) {
    125  // When moving from loading to non-loading, we may need to
    126  // reschedule any existing timeouts from the idle timeout queue
    127  // to the normal queue.
    128  MOZ_LOG(gTimeoutLog, LogLevel::Debug, ("%p: SetLoading(%d)", this, value));
    129  if (mIsLoading && !value) {
    130    MoveIdleToActive();
    131  }
    132  // We don't immediately move existing timeouts to the idle queue if we
    133  // move to loading.  When they would have fired, we'll see we're loading
    134  // and move them then.
    135  mIsLoading = value;
    136 }
    137 
    138 void TimeoutManager::MoveIdleToActive() {
    139  uint32_t num = 0;
    140  TimeStamp when;
    141  TimeStamp now;
    142  // Ensure we maintain the ordering of timeouts, so timeouts
    143  // never fire before a timeout set for an earlier time, or
    144  // before a timeout for the same time already submitted.
    145  // See https://html.spec.whatwg.org/#dom-settimeout #16 and #17
    146  while (RefPtr<Timeout> timeout = mIdleTimeouts.GetLast()) {
    147    if (num == 0) {
    148      when = timeout->When();
    149    }
    150    timeout->remove();
    151    mTimeouts.InsertFront(timeout);
    152    if (profiler_thread_is_being_profiled_for_markers()) {
    153      if (num == 0) {
    154        now = TimeStamp::Now();
    155      }
    156      TimeDuration elapsed = now - timeout->SubmitTime();
    157      TimeDuration target = timeout->When() - timeout->SubmitTime();
    158      TimeDuration delta = now - timeout->When();
    159      if (mIsWindow) {
    160        nsPrintfCString marker(
    161            "Releasing deferred setTimeout() for %dms (original target time "
    162            "was "
    163            "%dms (%dms delta))",
    164            int(elapsed.ToMilliseconds()), int(target.ToMilliseconds()),
    165            int(delta.ToMilliseconds()));
    166        // don't have end before start...
    167        PROFILER_MARKER_TEXT(
    168            "setTimeout deferred release", DOM,
    169            MarkerOptions(
    170                MarkerTiming::Interval(
    171                    delta.ToMilliseconds() >= 0 ? timeout->When() : now, now),
    172                MarkerInnerWindowId(
    173                    mGlobalObject.GetAsInnerWindow()->WindowID())),
    174            marker);
    175      }
    176      // TODO: add separate marker for Workers case
    177    }
    178    num++;
    179  }
    180  if (num > 0) {
    181    MOZ_ALWAYS_SUCCEEDS(MaybeSchedule(when));
    182    mIdleExecutor->Cancel();
    183  }
    184  MOZ_LOG(gTimeoutLog, LogLevel::Debug,
    185          ("%p: Moved %d timeouts from Idle to active", this, num));
    186 }
    187 
    188 uint32_t TimeoutManager::CreateFiringId() {
    189  uint32_t id = mNextFiringId;
    190  mNextFiringId += 1;
    191  if (mNextFiringId == InvalidFiringId) {
    192    mNextFiringId += 1;
    193  }
    194 
    195  mFiringIdStack.AppendElement(id);
    196 
    197  return id;
    198 }
    199 
    200 void TimeoutManager::DestroyFiringId(uint32_t aFiringId) {
    201  MOZ_DIAGNOSTIC_ASSERT(!mFiringIdStack.IsEmpty());
    202  MOZ_DIAGNOSTIC_ASSERT(mFiringIdStack.LastElement() == aFiringId);
    203  mFiringIdStack.RemoveLastElement();
    204 }
    205 
    206 bool TimeoutManager::IsValidFiringId(uint32_t aFiringId) const {
    207  return !IsInvalidFiringId(aFiringId);
    208 }
    209 
    210 TimeDuration TimeoutManager::MinSchedulingDelay() const {
    211  if (IsActive()) {
    212    return TimeDuration();
    213  }
    214 
    215  // do not throttle workers if dom_workers_throttling is disabled
    216  if (!mIsWindow && !StaticPrefs::dom_workers_throttling_enabled_AtStartup()) {
    217    return TimeDuration();
    218  }
    219 
    220  bool isBackground = mGlobalObject.IsBackgroundInternal();
    221 
    222  // If a window/worker isn't active as defined by TimeoutManager::IsActive()
    223  // and we're throttling timeouts using an execution budget, we
    224  // should adjust the minimum scheduling delay if we have used up all
    225  // of our execution budget. Note that a window/worker can be active or
    226  // inactive regardless of wether it is in the foreground or in the
    227  // background. Throttling using a budget depends largely on the
    228  // regeneration factor, which can be specified separately for
    229  // foreground and background windows.
    230  //
    231  // The value that we compute is the time in the future when we again
    232  // have a positive execution budget. We do this by taking the
    233  // execution budget into account, which if it positive implies that
    234  // we have time left to execute, and if it is negative implies that
    235  // we should throttle it until the budget again is positive. The
    236  // factor used is the rate of budget regeneration.
    237  //
    238  // We clamp the delay to be less than or equal to
    239  // "dom.timeout.budget_throttling_max_delay" to not entirely starve
    240  // the timeouts.
    241  //
    242  // Consider these examples assuming we should throttle using
    243  // budgets:
    244  //
    245  // mExecutionBudget is 20ms
    246  // factor is 1, which is 1 ms/ms
    247  // delay is 0ms
    248  // then we will compute the minimum delay:
    249  // max(0, - 20 * 1) = 0
    250  //
    251  // mExecutionBudget is -50ms
    252  // factor is 0.1, which is 1 ms/10ms
    253  // delay is 1000ms
    254  // then we will compute the minimum delay:
    255  // max(1000, - (- 50) * 1/0.1) = max(1000, 500) = 1000
    256  //
    257  // mExecutionBudget is -15ms
    258  // factor is 0.01, which is 1 ms/100ms
    259  // delay is 1000ms
    260  // then we will compute the minimum delay:
    261  // max(1000, - (- 15) * 1/0.01) = max(1000, 1500) = 1500
    262  TimeDuration unthrottled =
    263      isBackground ? TimeDuration::FromMilliseconds(
    264                         StaticPrefs::dom_min_background_timeout_value())
    265                   : TimeDuration();
    266  bool budgetThrottlingEnabled = BudgetThrottlingEnabled(isBackground);
    267  if (budgetThrottlingEnabled && mExecutionBudget < TimeDuration()) {
    268    // Only throttle if execution budget is less than 0
    269 
    270    double factor = 1.0 / GetRegenerationFactor(isBackground);
    271    return TimeDuration::Max(unthrottled, -mExecutionBudget.MultDouble(factor));
    272  }
    273  if (!budgetThrottlingEnabled && isBackground) {
    274    return TimeDuration::FromMilliseconds(
    275        StaticPrefs::
    276            dom_min_background_timeout_value_without_budget_throttling());
    277  }
    278 
    279  return unthrottled;
    280 }
    281 
    282 nsresult TimeoutManager::MaybeSchedule(const TimeStamp& aWhen,
    283                                       const TimeStamp& aNow) {
    284  MOZ_DIAGNOSTIC_ASSERT(mExecutor);
    285 
    286  // Before we can schedule the executor we need to make sure that we
    287  // have an updated execution budget.
    288  UpdateBudget(aNow);
    289  return mExecutor->MaybeSchedule(aWhen, MinSchedulingDelay());
    290 }
    291 
    292 bool TimeoutManager::IsInvalidFiringId(uint32_t aFiringId) const {
    293  // Check the most common ways to invalidate a firing id first.
    294  // These should be quite fast.
    295  if (aFiringId == InvalidFiringId || mFiringIdStack.IsEmpty()) {
    296    return true;
    297  }
    298 
    299  if (mFiringIdStack.Length() == 1) {
    300    return mFiringIdStack[0] != aFiringId;
    301  }
    302 
    303  // Next do a range check on the first and last items in the stack
    304  // of active firing ids.  This is a bit slower.
    305  uint32_t low = mFiringIdStack[0];
    306  uint32_t high = mFiringIdStack.LastElement();
    307  MOZ_DIAGNOSTIC_ASSERT(low != high);
    308  if (low > high) {
    309    // If the first element is bigger than the last element in the
    310    // stack, that means mNextFiringId wrapped around to zero at
    311    // some point.
    312    std::swap(low, high);
    313  }
    314  MOZ_DIAGNOSTIC_ASSERT(low < high);
    315 
    316  if (aFiringId < low || aFiringId > high) {
    317    return true;
    318  }
    319 
    320  // Finally, fall back to verifying the firing id is not anywhere
    321  // in the stack.  This could be slow for a large stack, but that
    322  // should be rare.  It can only happen with deeply nested event
    323  // loop spinning.  For example, a page that does a lot of timers
    324  // and a lot of sync XHRs within those timers could be slow here.
    325  return !mFiringIdStack.Contains(aFiringId);
    326 }
    327 
    328 TimeDuration TimeoutManager::CalculateDelay(Timeout* aTimeout) const {
    329  MOZ_DIAGNOSTIC_ASSERT(aTimeout);
    330  TimeDuration result = aTimeout->mInterval;
    331 
    332  if (aTimeout->mNestingLevel >=
    333          StaticPrefs::dom_clamp_timeout_nesting_level() &&
    334      !mIsChromeWorker) {
    335    uint32_t minTimeoutValue = StaticPrefs::dom_min_timeout_value();
    336    result = TimeDuration::Max(result,
    337                               TimeDuration::FromMilliseconds(minTimeoutValue));
    338  }
    339 
    340  return result;
    341 }
    342 
    343 void TimeoutManager::RecordExecution(Timeout* aRunningTimeout,
    344                                     Timeout* aTimeout) {
    345  TimeStamp now = TimeStamp::Now();
    346  TimeoutBudgetManager& budgetManager{mIsWindow ? sBudgetManager
    347                                                : mBudgetManager};
    348 
    349  if (aRunningTimeout) {
    350    // If we're running a timeout callback, record any execution until
    351    // now.
    352    TimeDuration duration = budgetManager.RecordExecution(now, aRunningTimeout);
    353 
    354    UpdateBudget(now, duration);
    355  }
    356 
    357  if (aTimeout) {
    358    // If we're starting a new timeout callback, start recording.
    359    budgetManager.StartRecording(now);
    360  } else {
    361    // Else stop by clearing the start timestamp.
    362    budgetManager.StopRecording();
    363  }
    364 }
    365 
    366 void TimeoutManager::UpdateBudget(const TimeStamp& aNow,
    367                                  const TimeDuration& aDuration) {
    368  nsGlobalWindowInner* window = GetInnerWindow();
    369  if (!window) {
    370    return;
    371  }
    372 
    373  if (window->IsChromeWindow()) {
    374    return;
    375  }
    376 
    377  // The budget is adjusted by increasing it with the time since the
    378  // last budget update factored with the regeneration rate. If a
    379  // runnable has executed, subtract that duration from the
    380  // budget. The budget updated without consideration of wether the
    381  // window/worker is active or not. If throttling is enabled and the
    382  // window/worker is active and then becomes inactive, an overdrawn budget will
    383  // still be counted against the minimum delay.
    384  bool isBackground = mGlobalObject.IsBackgroundInternal();
    385  if (BudgetThrottlingEnabled(isBackground)) {
    386    double factor = GetRegenerationFactor(isBackground);
    387    TimeDuration regenerated = (aNow - mLastBudgetUpdate).MultDouble(factor);
    388    // Clamp the budget to the range of minimum and maximum allowed budget.
    389    mExecutionBudget = TimeDuration::Max(
    390        GetMinBudget(isBackground),
    391        TimeDuration::Min(GetMaxBudget(isBackground),
    392                          mExecutionBudget - aDuration + regenerated));
    393  } else {
    394    // If budget throttling isn't enabled, reset the execution budget
    395    // to the max budget specified in preferences. Always doing this
    396    // will catch the case of BudgetThrottlingEnabled going from
    397    // returning true to returning false. This prevent us from looping
    398    // in RunTimeout, due to totalTimeLimit being set to zero and no
    399    // timeouts being executed, even though budget throttling isn't
    400    // active at the moment.
    401    mExecutionBudget = GetMaxBudget(isBackground);
    402  }
    403 
    404  mLastBudgetUpdate = aNow;
    405 }
    406 
    407 // The longest interval (as PRIntervalTime) we permit, or that our
    408 // timer code can handle, really. See DELAY_INTERVAL_LIMIT in
    409 // nsTimerImpl.h for details.
    410 #define DOM_MAX_TIMEOUT_VALUE DELAY_INTERVAL_LIMIT
    411 
    412 uint32_t TimeoutManager::sNestingLevel = 0;
    413 
    414 TimeoutManager::TimeoutManager(nsIGlobalObject& aHandle,
    415                               uint32_t aMaxIdleDeferMS,
    416                               nsISerialEventTarget* aEventTarget,
    417                               bool aIsChromeWorker)
    418    : mGlobalObject(aHandle),
    419      mExecutor(new TimeoutExecutor(this, false, 0)),
    420      mIdleExecutor(new TimeoutExecutor(this, true, aMaxIdleDeferMS)),
    421      mTimeouts(*this),
    422      mTimeoutIdCounter(1),
    423      mNextFiringId(InvalidFiringId + 1),
    424 #ifdef DEBUG
    425      mFiringIndex(0),
    426      mLastFiringIndex(-1),
    427 #endif
    428      mRunningTimeout(nullptr),
    429      mIdleTimeouts(*this),
    430      mIdleCallbackTimeoutCounter(1),
    431      mLastBudgetUpdate(TimeStamp::Now()),
    432      mExecutionBudget(GetMaxBudget(mGlobalObject.IsBackgroundInternal())),
    433      mThrottleTimeouts(false),
    434      mThrottleTrackingTimeouts(false),
    435      mBudgetThrottleTimeouts(false),
    436      mIsLoading(false),
    437      mEventTarget(aEventTarget),
    438      mIsWindow(aHandle.GetAsInnerWindow()),
    439      mIsChromeWorker(aIsChromeWorker) {
    440  MOZ_LOG(gTimeoutLog, LogLevel::Debug,
    441          ("TimeoutManager %p created, tracking bucketing %s\n", this,
    442           StaticPrefs::privacy_trackingprotection_annotate_channels()
    443               ? "enabled"
    444               : "disabled"));
    445 }
    446 
    447 TimeoutManager::~TimeoutManager() {
    448  if (mIsWindow) {
    449    MOZ_DIAGNOSTIC_ASSERT(mGlobalObject.IsDying());
    450  }
    451  MOZ_DIAGNOSTIC_ASSERT(!mThrottleTimeoutsTimer);
    452 
    453  mExecutor->Shutdown();
    454  mIdleExecutor->Shutdown();
    455 
    456  MOZ_LOG(gTimeoutLog, LogLevel::Debug,
    457          ("TimeoutManager %p destroyed\n", this));
    458 }
    459 
    460 int32_t TimeoutManager::GetTimeoutId(Timeout::Reason aReason) {
    461  int32_t timeoutId;
    462  do {
    463    switch (aReason) {
    464      case Timeout::Reason::eIdleCallbackTimeout:
    465        timeoutId = mIdleCallbackTimeoutCounter;
    466        if (mIdleCallbackTimeoutCounter ==
    467            std::numeric_limits<int32_t>::max()) {
    468          mIdleCallbackTimeoutCounter = 1;
    469        } else {
    470          ++mIdleCallbackTimeoutCounter;
    471        }
    472        break;
    473      case Timeout::Reason::eTimeoutOrInterval:
    474        timeoutId = mTimeoutIdCounter;
    475        if (mTimeoutIdCounter == std::numeric_limits<int32_t>::max()) {
    476          mTimeoutIdCounter = 1;
    477        } else {
    478          ++mTimeoutIdCounter;
    479        }
    480        break;
    481      case Timeout::Reason::eDelayedWebTaskTimeout:
    482      case Timeout::Reason::eJSTimeout:
    483      default:
    484        return -1;  // no cancellation support
    485    }
    486  } while (mTimeouts.GetTimeout(timeoutId, aReason));
    487 
    488  return timeoutId;
    489 }
    490 
    491 bool TimeoutManager::IsRunningTimeout() const { return mRunningTimeout; }
    492 
    493 nsresult TimeoutManager::SetTimeout(TimeoutHandler* aHandler, int32_t interval,
    494                                    bool aIsInterval, Timeout::Reason aReason,
    495                                    int32_t* aReturn) {
    496  // If we don't have a document (we could have been unloaded since
    497  // the call to setTimeout was made), do nothing.
    498  if (mIsWindow) {
    499    nsCOMPtr<Document> doc = mGlobalObject.GetAsInnerWindow()->GetExtantDoc();
    500    if (!doc || mGlobalObject.IsDying()) {
    501      return NS_OK;
    502    }
    503  }
    504 
    505  auto scopeExit = MakeScopeExit([&] {
    506    if (!mIsWindow && !HasTimeouts()) {
    507      mGlobalObject.TriggerUpdateCCFlag();
    508    }
    509  });
    510 
    511  // Disallow negative intervals.
    512  interval = std::max(0, interval);
    513 
    514  // Make sure we don't proceed with an interval larger than our timer
    515  // code can handle. (Note: we already forced |interval| to be non-negative,
    516  // so the uint32_t cast (to avoid compiler warnings) is ok.)
    517  uint32_t maxTimeoutMs = PR_IntervalToMilliseconds(DOM_MAX_TIMEOUT_VALUE);
    518  if (static_cast<uint32_t>(interval) > maxTimeoutMs) {
    519    interval = maxTimeoutMs;
    520  }
    521 
    522  RefPtr<Timeout> timeout = new Timeout();
    523 #ifdef DEBUG
    524  timeout->mFiringIndex = -1;
    525 #endif
    526  timeout->mGlobal = &mGlobalObject;
    527  timeout->mIsInterval = aIsInterval;
    528  timeout->mInterval = TimeDuration::FromMilliseconds(interval);
    529  timeout->mScriptHandler = aHandler;
    530  timeout->mReason = aReason;
    531 
    532  if (mIsWindow) {
    533    // No popups from timeouts by default
    534    timeout->mPopupState = PopupBlocker::openAbused;
    535  }
    536 
    537  // XXX: Does eIdleCallbackTimeout need clamping?
    538  if (aReason == Timeout::Reason::eTimeoutOrInterval ||
    539      aReason == Timeout::Reason::eIdleCallbackTimeout) {
    540    const uint32_t nestingLevel{mIsWindow ? GetNestingLevelForWindow()
    541                                          : GetNestingLevelForWorker()};
    542    timeout->mNestingLevel =
    543        nestingLevel < StaticPrefs::dom_clamp_timeout_nesting_level()
    544            ? nestingLevel + 1
    545            : nestingLevel;
    546  }
    547 
    548  // Now clamp the actual interval we will use for the timer based on
    549  TimeDuration realInterval = CalculateDelay(timeout);
    550  TimeStamp now = TimeStamp::Now();
    551  timeout->SetWhenOrTimeRemaining(now, realInterval);
    552 
    553  // If we're not suspended, then set the timer.
    554  if (!mGlobalObject.IsSuspended()) {
    555    nsresult rv = MaybeSchedule(timeout->When(), now);
    556    if (NS_FAILED(rv)) {
    557      return rv;
    558    }
    559  }
    560 
    561  if (mIsWindow) {
    562    if (gRunningTimeoutDepth == 0 &&
    563        PopupBlocker::GetPopupControlState() < PopupBlocker::openBlocked) {
    564      // This timeout is *not* set from another timeout and it's set
    565      // while popups are enabled. Propagate the state to the timeout if
    566      // its delay (interval) is equal to or less than what
    567      // "dom.disable_open_click_delay" is set to (in ms).
    568 
    569      // This is checking |interval|, not realInterval, on purpose,
    570      // because our lower bound for |realInterval| could be pretty high
    571      // in some cases.
    572      if (interval <= StaticPrefs::dom_disable_open_click_delay()) {
    573        timeout->mPopupState = PopupBlocker::GetPopupControlState();
    574      }
    575    }
    576  }
    577 
    578  Timeouts::SortBy sort(mGlobalObject.IsFrozen()
    579                            ? Timeouts::SortBy::TimeRemaining
    580                            : Timeouts::SortBy::TimeWhen);
    581 
    582  timeout->mTimeoutId = GetTimeoutId(aReason);
    583  mTimeouts.Insert(timeout, sort);
    584 
    585  *aReturn = timeout->mTimeoutId;
    586 
    587  MOZ_LOG(
    588      gTimeoutLog, LogLevel::Debug,
    589      ("Set%s(TimeoutManager=%p, timeout=%p, delay=%i, "
    590       "minimum=%f, throttling=%s, state=%s(%s), realInterval=%f) "
    591       "returned timeout ID %u, budget=%d\n",
    592       aIsInterval ? "Interval" : "Timeout", this, timeout.get(), interval,
    593       (CalculateDelay(timeout) - timeout->mInterval).ToMilliseconds(),
    594       mThrottleTimeouts ? "yes" : (mThrottleTimeoutsTimer ? "pending" : "no"),
    595       IsActive() ? "active" : "inactive",
    596       mGlobalObject.IsBackgroundInternal() ? "background" : "foreground",
    597       realInterval.ToMilliseconds(), timeout->mTimeoutId,
    598       int(mExecutionBudget.ToMilliseconds())));
    599 
    600  return NS_OK;
    601 }
    602 
    603 // Make sure we clear it no matter which list it's in
    604 void TimeoutManager::ClearTimeout(int32_t aTimerId, Timeout::Reason aReason) {
    605  if (ClearTimeoutInternal(aTimerId, aReason, false) ||
    606      mIdleTimeouts.IsEmpty()) {
    607    return;  // no need to check the other list if we cleared the timeout
    608  }
    609  ClearTimeoutInternal(aTimerId, aReason, true);
    610 }
    611 
    612 bool TimeoutManager::ClearTimeoutInternal(int32_t aTimerId,
    613                                          Timeout::Reason aReason,
    614                                          bool aIsIdle) {
    615  MOZ_ASSERT(aReason == Timeout::Reason::eTimeoutOrInterval ||
    616                 aReason == Timeout::Reason::eIdleCallbackTimeout,
    617             "This timeout reason doesn't support cancellation.");
    618 
    619  Timeouts& timeouts = aIsIdle ? mIdleTimeouts : mTimeouts;
    620  RefPtr<TimeoutExecutor>& executor = aIsIdle ? mIdleExecutor : mExecutor;
    621  bool deferredDeletion = false;
    622 
    623  Timeout* timeout = timeouts.GetTimeout(aTimerId, aReason);
    624  if (!timeout) {
    625    return false;
    626  }
    627  bool firstTimeout = timeout == timeouts.GetFirst();
    628 
    629  MOZ_LOG(gTimeoutLog, LogLevel::Debug,
    630          ("%s(TimeoutManager=%p, timeout=%p, ID=%u)\n",
    631           timeout->mReason == Timeout::Reason::eIdleCallbackTimeout
    632               ? "CancelIdleCallback"
    633           : timeout->mIsInterval ? "ClearInterval"
    634                                  : "ClearTimeout",
    635           this, timeout, timeout->mTimeoutId));
    636 
    637  if (timeout->mRunning) {
    638    /* We're running from inside the timeout. Mark this
    639       timeout for deferred deletion by the code in
    640       RunTimeout() */
    641    timeout->mIsInterval = false;
    642    deferredDeletion = true;
    643  } else {
    644    /* Delete the aTimeout from the pending aTimeout list */
    645    timeout->remove();
    646  }
    647 
    648  // We don't need to reschedule the executor if any of the following are true:
    649  //  * If the we weren't cancelling the first timeout, then the executor's
    650  //    state doesn't need to change.  It will only reflect the next soonest
    651  //    Timeout.
    652  //  * If we did cancel the first Timeout, but its currently running, then
    653  //    RunTimeout() will handle rescheduling the executor.
    654  //  * If the window/worker has become suspended then we should not start
    655  //  executing
    656  //    Timeouts.
    657  if (!firstTimeout || deferredDeletion || (mGlobalObject.IsSuspended())) {
    658    return true;
    659  }
    660 
    661  // Stop the executor and restart it at the next soonest deadline.
    662  executor->Cancel();
    663 
    664  Timeout* nextTimeout = timeouts.GetFirst();
    665  if (nextTimeout) {
    666    if (aIsIdle) {
    667      MOZ_ALWAYS_SUCCEEDS(
    668          executor->MaybeSchedule(nextTimeout->When(), TimeDuration(0)));
    669    } else {
    670      MOZ_ALWAYS_SUCCEEDS(MaybeSchedule(nextTimeout->When()));
    671    }
    672  }
    673  return true;
    674 }
    675 
    676 void TimeoutManager::RunTimeout(const TimeStamp& aNow,
    677                                const TimeStamp& aTargetDeadline,
    678                                bool aProcessIdle) {
    679  MOZ_DIAGNOSTIC_ASSERT(!aNow.IsNull());
    680  MOZ_DIAGNOSTIC_ASSERT(!aTargetDeadline.IsNull());
    681 
    682  nsCOMPtr<nsIGlobalObject> global = &mGlobalObject;
    683 
    684  MOZ_ASSERT_IF(mGlobalObject.IsFrozen(), mGlobalObject.IsSuspended());
    685 
    686  if (mGlobalObject.IsSuspended()) {
    687    return;
    688  }
    689 
    690  if (!GetInnerWindow()) {
    691    // Workers don't use TaskController at the moment, so all the
    692    // runnables have the same priorities. So we special case it
    693    // here to allow "higher" prority tasks to run first before
    694    // timers.
    695    if (mGlobalObject.HasScheduledNormalOrHighPriorityWebTasks()) {
    696      MOZ_ALWAYS_SUCCEEDS(MaybeSchedule(aNow));
    697      return;
    698    }
    699  }
    700 
    701  Timeouts& timeouts(aProcessIdle ? mIdleTimeouts : mTimeouts);
    702 
    703  // Limit the overall time spent in RunTimeout() to reduce jank.
    704  uint32_t totalTimeLimitMS =
    705      std::max(1u, StaticPrefs::dom_timeout_max_consecutive_callbacks_ms());
    706  const TimeDuration totalTimeLimit =
    707      TimeDuration::Min(TimeDuration::FromMilliseconds(totalTimeLimitMS),
    708                        TimeDuration::Max(TimeDuration(), mExecutionBudget));
    709 
    710  // Allow up to 25% of our total time budget to be used figuring out which
    711  // timers need to run.  This is the initial loop in this method.
    712  const TimeDuration initialTimeLimit =
    713      TimeDuration::FromMilliseconds(totalTimeLimit.ToMilliseconds() / 4);
    714 
    715  // Ammortize overhead from from calling TimeStamp::Now() in the initial
    716  // loop, though, by only checking for an elapsed limit every N timeouts.
    717  const uint32_t kNumTimersPerInitialElapsedCheck = 100;
    718 
    719  // Start measuring elapsed time immediately.  We won't potentially expire
    720  // the time budget until at least one Timeout has run, though.
    721  TimeStamp now(aNow);
    722  TimeStamp start = now;
    723 
    724  uint32_t firingId = CreateFiringId();
    725  auto guard = MakeScopeExit([&] { DestroyFiringId(firingId); });
    726 
    727  // Accessing members of mGlobalObject here is safe, because the lifetime of
    728  // TimeoutManager is the same as the lifetime of the containing
    729  // nsGlobalWindow.
    730 
    731  // A native timer has gone off. See which of our timeouts need
    732  // servicing
    733  TimeStamp deadline;
    734 
    735  if (aTargetDeadline > now) {
    736    // The OS timer fired early (which can happen due to the timers
    737    // having lower precision than TimeStamp does).  Set |deadline| to
    738    // be the time when the OS timer *should* have fired so that any
    739    // timers that *should* have fired *will* be fired now.
    740 
    741    deadline = aTargetDeadline;
    742  } else {
    743    deadline = now;
    744  }
    745 
    746  TimeStamp nextDeadline;
    747  uint32_t numTimersToRun = 0;
    748 
    749  // The timeout list is kept in deadline order. Discover the latest timeout
    750  // whose deadline has expired. On some platforms, native timeout events fire
    751  // "early", but we handled that above by setting deadline to aTargetDeadline
    752  // if the timer fired early.  So we can stop walking if we get to timeouts
    753  // whose When() is greater than deadline, since once that happens we know
    754  // nothing past that point is expired.
    755 
    756  for (Timeout* timeout = timeouts.GetFirst(); timeout != nullptr;
    757       timeout = timeout->getNext()) {
    758    if (totalTimeLimit.IsZero() || timeout->When() > deadline) {
    759      nextDeadline = timeout->When();
    760      break;
    761    }
    762 
    763    if (IsInvalidFiringId(timeout->mFiringId)) {
    764      // Mark any timeouts that are on the list to be fired with the
    765      // firing depth so that we can reentrantly run timeouts
    766      timeout->mFiringId = firingId;
    767 
    768      numTimersToRun += 1;
    769 
    770      // Run only a limited number of timers based on the configured maximum.
    771      if (numTimersToRun % kNumTimersPerInitialElapsedCheck == 0) {
    772        now = TimeStamp::Now();
    773        TimeDuration elapsed(now - start);
    774        if (elapsed >= initialTimeLimit) {
    775          nextDeadline = timeout->When();
    776          break;
    777        }
    778      }
    779    }
    780  }
    781  if (aProcessIdle) {
    782    MOZ_LOG(
    783        gTimeoutLog, LogLevel::Debug,
    784        ("Running %u deferred timeouts on idle (TimeoutManager=%p), "
    785         "nextDeadline = %gms from now",
    786         numTimersToRun, this,
    787         nextDeadline.IsNull() ? 0.0 : (nextDeadline - now).ToMilliseconds()));
    788  }
    789 
    790  now = TimeStamp::Now();
    791 
    792  // Wherever we stopped in the timer list, schedule the executor to
    793  // run for the next unexpired deadline.  Note, this *must* be done
    794  // before we start executing any content script handlers.  If one
    795  // of them spins the event loop the executor must already be scheduled
    796  // in order for timeouts to fire properly.
    797  if (!nextDeadline.IsNull()) {
    798    // Note, we verified the window/worker is not suspended at the top of
    799    // method and the window/worker should not have been suspended while
    800    // executing the loop above since it doesn't call out to js.
    801    MOZ_DIAGNOSTIC_ASSERT(!mGlobalObject.IsSuspended());
    802    if (aProcessIdle) {
    803      // We don't want to update timing budget for idle queue firings, and
    804      // all timeouts in the IdleTimeouts list have hit their deadlines,
    805      // and so should run as soon as possible.
    806      MOZ_ALWAYS_SUCCEEDS(
    807          mIdleExecutor->MaybeSchedule(nextDeadline, TimeDuration()));
    808    } else {
    809      MOZ_ALWAYS_SUCCEEDS(MaybeSchedule(nextDeadline, now));
    810    }
    811  }
    812 
    813  // Maybe the timeout that the event was fired for has been deleted
    814  // and there are no others timeouts with deadlines that make them
    815  // eligible for execution yet. Go away.
    816  if (!numTimersToRun) {
    817    return;
    818  }
    819 
    820  // Now we need to search the normal and tracking timer list at the same
    821  // time to run the timers in the scheduled order.
    822 
    823  // We stop iterating each list when we go past the last expired timeout from
    824  // that list that we have observed above.  That timeout will either be the
    825  // next item after the last timeout we looked at or nullptr if we have
    826  // exhausted the entire list while looking for the last expired timeout.
    827  {
    828    // Use a nested scope in order to make sure the strong references held while
    829    // iterating are freed after the loop.
    830 
    831    // The next timeout to run. This is used to advance the loop, but
    832    // we cannot set it until we've run the current timeout, since
    833    // running the current timeout might remove the immediate next
    834    // timeout.
    835    RefPtr<Timeout> next;
    836 
    837    for (RefPtr<Timeout> timeout = timeouts.GetFirst(); timeout != nullptr;
    838         timeout = next) {
    839      next = timeout->getNext();
    840      // We should only execute callbacks for the set of expired Timeout
    841      // objects we computed above.
    842      if (timeout->mFiringId != firingId) {
    843        // If the FiringId does not match, but is still valid, then this is
    844        // a Timeout for another RunTimeout() on the call stack (such as in
    845        // the case of nested event loops, for alert() or more likely XHR).
    846        // Just skip it.
    847        if (IsValidFiringId(timeout->mFiringId)) {
    848          MOZ_LOG(gTimeoutLog, LogLevel::Debug,
    849                  ("Skipping Run%s(TimeoutManager=%p, timeout=%p) since "
    850                   "firingId %d is valid (processing firingId %d)"
    851 #ifdef DEBUG
    852                   " - FiringIndex %" PRId64 " (mLastFiringIndex %" PRId64 ")"
    853 #endif
    854                   ,
    855                   timeout->mIsInterval ? "Interval" : "Timeout", this,
    856                   timeout.get(), timeout->mFiringId, firingId
    857 #ifdef DEBUG
    858                   ,
    859                   timeout->mFiringIndex, mFiringIndex
    860 #endif
    861                   ));
    862 #ifdef DEBUG
    863          // The old FiringIndex assumed no recursion; recursion can cause
    864          // other timers to get fired "in the middle" of a sequence we've
    865          // already assigned firingindexes to.  Since we're not going to
    866          // run this timeout now, remove any FiringIndex that was already
    867          // set.
    868 
    869          // Since all timers that have FiringIndexes set *must* be ready
    870          // to run and have valid FiringIds, all of them will be 'skipped'
    871          // and reset if we recurse - we don't have to look through the
    872          // list past where we'll stop on the first InvalidFiringId.
    873          timeout->mFiringIndex = -1;
    874 #endif
    875          continue;
    876        }
    877 
    878        // If, however, the FiringId is invalid then we have reached Timeout
    879        // objects beyond the list we calculated above.  This can happen
    880        // if the Timeout just beyond our last expired Timeout is cancelled
    881        // by one of the callbacks we've just executed.  In this case we
    882        // should just stop iterating.  We're done.
    883        else {
    884          break;
    885        }
    886      }
    887 
    888      MOZ_ASSERT_IF(mGlobalObject.IsFrozen(), mGlobalObject.IsSuspended());
    889      if (mGlobalObject.IsSuspended()) {
    890        break;
    891      }
    892 
    893      // The timeout is on the list to run at this depth, go ahead and
    894      // process it.
    895 
    896      if (mIsLoading && !aProcessIdle) {
    897        // Any timeouts that would fire during a load will be deferred
    898        // until the load event occurs, but if there's an idle time,
    899        // they'll be run before the load event.
    900        timeout->remove();
    901        // MOZ_RELEASE_ASSERT(timeout->When() <= (TimeStamp::Now()));
    902        mIdleTimeouts.InsertBack(timeout);
    903        if (MOZ_LOG_TEST(gTimeoutLog, LogLevel::Debug)) {
    904          uint32_t num = 0;
    905          for (Timeout* t = mIdleTimeouts.GetFirst(); t != nullptr;
    906               t = t->getNext()) {
    907            num++;
    908          }
    909          MOZ_LOG(
    910              gTimeoutLog, LogLevel::Debug,
    911              ("Deferring Run%s(TimeoutManager=%p, timeout=%p (%gms in the "
    912               "past)) (%u deferred)",
    913               timeout->mIsInterval ? "Interval" : "Timeout", this,
    914               timeout.get(), (now - timeout->When()).ToMilliseconds(), num));
    915        }
    916        MOZ_ALWAYS_SUCCEEDS(mIdleExecutor->MaybeSchedule(now, TimeDuration()));
    917      } else {
    918        // Record the first time we try to fire a timeout, and ensure that
    919        // all actual firings occur in that order.  This ensures that we
    920        // retain compliance with the spec language
    921        // (https://html.spec.whatwg.org/#dom-settimeout) specifically items
    922        // 15 ("If method context is a Window object, wait until the Document
    923        // associated with method context has been fully active for a further
    924        // timeout milliseconds (not necessarily consecutively)") and item 16
    925        // ("Wait until any invocations of this algorithm that had the same
    926        // method context, that started before this one, and whose timeout is
    927        // equal to or less than this one's, have completed.").
    928 #ifdef DEBUG
    929        if (timeout->mFiringIndex == -1) {
    930          timeout->mFiringIndex = mFiringIndex++;
    931        }
    932 #endif
    933 
    934        if (mGlobalObject.IsDying()) {
    935          timeout->remove();
    936          continue;
    937        }
    938 
    939 #ifdef DEBUG
    940        if (timeout->mFiringIndex <= mLastFiringIndex) {
    941          MOZ_LOG(gTimeoutLog, LogLevel::Debug,
    942                  ("Incorrect firing index for Run%s(TimeoutManager=%p, "
    943                   "timeout=%p) with "
    944                   "firingId %d - FiringIndex %" PRId64
    945                   " (mLastFiringIndex %" PRId64 ")",
    946                   timeout->mIsInterval ? "Interval" : "Timeout", this,
    947                   timeout.get(), timeout->mFiringId, timeout->mFiringIndex,
    948                   mFiringIndex));
    949        }
    950        MOZ_ASSERT(timeout->mFiringIndex > mLastFiringIndex);
    951        mLastFiringIndex = timeout->mFiringIndex;
    952 #endif
    953        // This timeout is good to run.
    954        bool timeout_was_cleared = false;
    955 
    956        timeout_was_cleared = global->RunTimeoutHandler(timeout);
    957 
    958        MOZ_LOG(gTimeoutLog, LogLevel::Debug,
    959                ("Run%s(TimeoutManager=%p, timeout=%p) returned %d\n",
    960                 timeout->mIsInterval ? "Interval" : "Timeout", this,
    961                 timeout.get(), !!timeout_was_cleared));
    962 
    963        if (timeout_was_cleared) {
    964          // Make sure we're not holding any Timeout objects alive.
    965          next = nullptr;
    966 
    967          // Since ClearAllTimeouts() was called the lists should be empty.
    968          MOZ_DIAGNOSTIC_ASSERT(!HasTimeouts());
    969 
    970          return;
    971        }
    972 
    973        // If we need to reschedule a setInterval() the delay should be
    974        // calculated based on when its callback started to execute.  So
    975        // save off the last time before updating our "now" timestamp to
    976        // account for its callback execution time.
    977        TimeStamp lastCallbackTime = now;
    978        now = TimeStamp::Now();
    979 
    980        // If we have a regular interval timer, we re-schedule the
    981        // timeout, accounting for clock drift.
    982        bool needsReinsertion =
    983            RescheduleTimeout(timeout, lastCallbackTime, now);
    984 
    985        // Running a timeout can cause another timeout to be deleted, so
    986        // we need to reset the pointer to the following timeout.
    987        next = timeout->getNext();
    988 
    989        timeout->remove();
    990 
    991        if (needsReinsertion) {
    992          // Insert interval timeout onto the corresponding list sorted in
    993          // deadline order. AddRefs timeout.
    994          // Always re-insert into the normal time queue!
    995          mTimeouts.Insert(timeout, mGlobalObject.IsFrozen()
    996                                        ? Timeouts::SortBy::TimeRemaining
    997                                        : Timeouts::SortBy::TimeWhen);
    998        }
    999      }
   1000      // Check to see if we have run out of time to execute timeout handlers.
   1001      // If we've exceeded our time budget then terminate the loop immediately.
   1002      //
   1003      // Or if there are high priority tasks dispatched by the Scheduler API,
   1004      // they should run first before timers.
   1005      TimeDuration elapsed = now - start;
   1006      if (elapsed >= totalTimeLimit ||
   1007          mGlobalObject.HasScheduledNormalOrHighPriorityWebTasks()) {
   1008        // We ran out of time.  Make sure to schedule the executor to
   1009        // run immediately for the next timer, if it exists.  Its possible,
   1010        // however, that the last timeout handler suspended the window.  If
   1011        // that happened then we must skip this step.
   1012        if (!mGlobalObject.IsSuspended()) {
   1013          if (next) {
   1014            if (aProcessIdle) {
   1015              // We don't want to update timing budget for idle queue firings,
   1016              // and all timeouts in the IdleTimeouts list have hit their
   1017              // deadlines, and so should run as soon as possible.
   1018 
   1019              // Shouldn't need cancelling since it never waits
   1020              MOZ_ALWAYS_SUCCEEDS(
   1021                  mIdleExecutor->MaybeSchedule(next->When(), TimeDuration()));
   1022            } else {
   1023              // If we ran out of execution budget we need to force a
   1024              // reschedule. By cancelling the executor we will not run
   1025              // immediately, but instead reschedule to the minimum
   1026              // scheduling delay.
   1027              if (mExecutionBudget < TimeDuration()) {
   1028                mExecutor->Cancel();
   1029              }
   1030 
   1031              MOZ_ALWAYS_SUCCEEDS(MaybeSchedule(next->When(), now));
   1032            }
   1033          }
   1034        }
   1035        break;
   1036      }
   1037    }
   1038  }
   1039 }
   1040 
   1041 bool TimeoutManager::RescheduleTimeout(Timeout* aTimeout,
   1042                                       const TimeStamp& aLastCallbackTime,
   1043                                       const TimeStamp& aCurrentNow) {
   1044  MOZ_DIAGNOSTIC_ASSERT(aLastCallbackTime <= aCurrentNow);
   1045 
   1046  if (!aTimeout->mIsInterval) {
   1047    return false;
   1048  }
   1049 
   1050  // Automatically increase the nesting level when a setInterval()
   1051  // is rescheduled just as if it was using a chained setTimeout().
   1052  if (aTimeout->mNestingLevel <
   1053      StaticPrefs::dom_clamp_timeout_nesting_level()) {
   1054    aTimeout->mNestingLevel += 1;
   1055  }
   1056 
   1057  // Compute time to next timeout for interval timer.
   1058  // Make sure nextInterval is at least CalculateDelay().
   1059  TimeDuration nextInterval = CalculateDelay(aTimeout);
   1060 
   1061  TimeStamp firingTime = aLastCallbackTime + nextInterval;
   1062  TimeDuration delay = firingTime - aCurrentNow;
   1063 
   1064 #ifdef DEBUG
   1065  aTimeout->mFiringIndex = -1;
   1066 #endif
   1067  // And make sure delay is nonnegative; that might happen if the timer
   1068  // thread is firing our timers somewhat early or if they're taking a long
   1069  // time to run the callback.
   1070  if (delay < TimeDuration(0)) {
   1071    delay = TimeDuration(0);
   1072  }
   1073 
   1074  aTimeout->SetWhenOrTimeRemaining(aCurrentNow, delay);
   1075 
   1076  if (mGlobalObject.IsSuspended()) {
   1077    return true;
   1078  }
   1079 
   1080  nsresult rv = MaybeSchedule(aTimeout->When(), aCurrentNow);
   1081  NS_ENSURE_SUCCESS(rv, false);
   1082 
   1083  return true;
   1084 }
   1085 
   1086 void TimeoutManager::ClearAllTimeouts() {
   1087  bool seenRunningTimeout = false;
   1088 
   1089  MOZ_LOG(gTimeoutLog, LogLevel::Debug,
   1090          ("ClearAllTimeouts(TimeoutManager=%p)\n", this));
   1091 
   1092  if (mThrottleTimeoutsTimer) {
   1093    mThrottleTimeoutsTimer->Cancel();
   1094    mThrottleTimeoutsTimer = nullptr;
   1095  }
   1096 
   1097  mExecutor->Cancel();
   1098  mIdleExecutor->Cancel();
   1099 
   1100  ForEachUnorderedTimeout([&](Timeout* aTimeout) {
   1101    /* If RunTimeout() is higher up on the stack for this
   1102       window, e.g. as a result of document.write from a timeout,
   1103       then we need to reset the list insertion point for
   1104       newly-created timeouts in case the user adds a timeout,
   1105       before we pop the stack back to RunTimeout. */
   1106    if (mRunningTimeout == aTimeout) {
   1107      seenRunningTimeout = true;
   1108    }
   1109 
   1110    // Set timeout->mCleared to true to indicate that the timeout was
   1111    // cleared and taken out of the list of timeouts
   1112    aTimeout->mCleared = true;
   1113  });
   1114 
   1115  // Clear out our lists
   1116  mTimeouts.Clear();
   1117  mIdleTimeouts.Clear();
   1118 }
   1119 
   1120 void TimeoutManager::Timeouts::Insert(Timeout* aTimeout, SortBy aSortBy) {
   1121  // Start at mLastTimeout and go backwards.  Stop if we see a Timeout with a
   1122  // valid FiringId since those timers are currently being processed by
   1123  // RunTimeout.  This optimizes for the common case of insertion at the end.
   1124  Timeout* prevSibling;
   1125  for (prevSibling = GetLast();
   1126       prevSibling &&
   1127       // This condition needs to match the one in SetTimeoutOrInterval that
   1128       // determines whether to set When() or TimeRemaining().
   1129       (aSortBy == SortBy::TimeRemaining
   1130            ? prevSibling->TimeRemaining() > aTimeout->TimeRemaining()
   1131            : prevSibling->When() > aTimeout->When()) &&
   1132       // Check the firing ID last since it will evaluate true in the vast
   1133       // majority of cases.
   1134       mManager.IsInvalidFiringId(prevSibling->mFiringId);
   1135       prevSibling = prevSibling->getPrevious()) {
   1136    /* Do nothing; just searching */
   1137  }
   1138 
   1139  // Now link in aTimeout after prevSibling.
   1140  if (prevSibling) {
   1141    aTimeout->SetTimeoutContainer(mTimeouts);
   1142    prevSibling->setNext(aTimeout);
   1143  } else {
   1144    InsertFront(aTimeout);
   1145  }
   1146 
   1147  aTimeout->mFiringId = InvalidFiringId;
   1148 }
   1149 
   1150 Timeout* TimeoutManager::BeginRunningTimeout(Timeout* aTimeout) {
   1151  Timeout* currentTimeout = mRunningTimeout;
   1152  mRunningTimeout = aTimeout;
   1153  if (mIsWindow) {
   1154    ++gRunningTimeoutDepth;
   1155  }
   1156 
   1157  RecordExecution(currentTimeout, aTimeout);
   1158  return currentTimeout;
   1159 }
   1160 
   1161 void TimeoutManager::EndRunningTimeout(Timeout* aTimeout) {
   1162  if (mIsWindow) {
   1163    --gRunningTimeoutDepth;
   1164  }
   1165 
   1166  RecordExecution(mRunningTimeout, aTimeout);
   1167  mRunningTimeout = aTimeout;
   1168 }
   1169 
   1170 void TimeoutManager::UnmarkGrayTimers() {
   1171  ForEachUnorderedTimeout([](Timeout* aTimeout) {
   1172    if (aTimeout->mScriptHandler) {
   1173      aTimeout->mScriptHandler->MarkForCC();
   1174    }
   1175  });
   1176 }
   1177 
   1178 void TimeoutManager::Suspend() {
   1179  MOZ_LOG(gTimeoutLog, LogLevel::Debug, ("Suspend(TimeoutManager=%p)\n", this));
   1180 
   1181  if (mThrottleTimeoutsTimer) {
   1182    mThrottleTimeoutsTimer->Cancel();
   1183    mThrottleTimeoutsTimer = nullptr;
   1184  }
   1185 
   1186  mExecutor->Cancel();
   1187  mIdleExecutor->Cancel();
   1188 }
   1189 
   1190 void TimeoutManager::Resume() {
   1191  MOZ_LOG(gTimeoutLog, LogLevel::Debug, ("Resume(TimeoutManager=%p)\n", this));
   1192  nsGlobalWindowInner* window = GetInnerWindow();
   1193 
   1194  // When Suspend() has been called after IsDocumentLoaded(), but the
   1195  // throttle tracking timer never managed to fire, start the timer
   1196  // again.
   1197  if (window && window->IsDocumentLoaded() && !mThrottleTimeouts) {
   1198    MaybeStartThrottleTimeout();
   1199  }
   1200 
   1201  Timeout* nextTimeout = mTimeouts.GetFirst();
   1202  if (nextTimeout) {
   1203    MOZ_ALWAYS_SUCCEEDS(MaybeSchedule(nextTimeout->When()));
   1204  }
   1205  nextTimeout = mIdleTimeouts.GetFirst();
   1206  if (nextTimeout) {
   1207    MOZ_ALWAYS_SUCCEEDS(
   1208        mIdleExecutor->MaybeSchedule(nextTimeout->When(), TimeDuration()));
   1209  }
   1210 }
   1211 
   1212 void TimeoutManager::Freeze() {
   1213  MOZ_LOG(gTimeoutLog, LogLevel::Debug, ("Freeze(TimeoutManager=%p)\n", this));
   1214 
   1215  // When freezing, preemptively move timeouts from the idle timeout queue to
   1216  // the normal queue. This way they get scheduled automatically when we thaw.
   1217  // We don't need to cancel the idle executor here, since that is done in
   1218  // Suspend.
   1219  size_t num = 0;
   1220  while (RefPtr<Timeout> timeout = mIdleTimeouts.GetLast()) {
   1221    num++;
   1222    timeout->remove();
   1223    mTimeouts.InsertFront(timeout);
   1224  }
   1225 
   1226  MOZ_LOG(gTimeoutLog, LogLevel::Debug,
   1227          ("%p: Moved %zu (frozen) timeouts from Idle to active", this, num));
   1228 
   1229  TimeStamp now = TimeStamp::Now();
   1230  ForEachUnorderedTimeout([&](Timeout* aTimeout) {
   1231    // Save the current remaining time for this timeout.  We will
   1232    // re-apply it when the window is Thaw()'d.  This effectively
   1233    // shifts timers to the right as if time does not pass while
   1234    // the window is frozen.
   1235    TimeDuration delta(0);
   1236    if (aTimeout->When() > now) {
   1237      delta = aTimeout->When() - now;
   1238    }
   1239    aTimeout->SetWhenOrTimeRemaining(now, delta);
   1240    MOZ_DIAGNOSTIC_ASSERT(aTimeout->TimeRemaining() == delta);
   1241  });
   1242 }
   1243 
   1244 void TimeoutManager::Thaw() {
   1245  MOZ_LOG(gTimeoutLog, LogLevel::Debug, ("Thaw(TimeoutManager=%p)\n", this));
   1246 
   1247  TimeStamp now = TimeStamp::Now();
   1248 
   1249  ForEachUnorderedTimeout([&](Timeout* aTimeout) {
   1250    // Set When() back to the time when the timer is supposed to fire.
   1251    aTimeout->SetWhenOrTimeRemaining(now, aTimeout->TimeRemaining());
   1252    MOZ_DIAGNOSTIC_ASSERT(!aTimeout->When().IsNull());
   1253  });
   1254 }
   1255 
   1256 void TimeoutManager::UpdateBackgroundState() {
   1257  mExecutionBudget = GetMaxBudget(mGlobalObject.IsBackgroundInternal());
   1258 
   1259  // When the window/worker moves to the background or foreground we should
   1260  // reschedule the TimeoutExecutor in case the MinSchedulingDelay()
   1261  // changed.  Only do this if the window/worker is not suspended and we
   1262  // actually have a timeout.
   1263  if (!mGlobalObject.IsSuspended()) {
   1264    Timeout* nextTimeout = mTimeouts.GetFirst();
   1265    if (nextTimeout) {
   1266      mExecutor->Cancel();
   1267      MOZ_ALWAYS_SUCCEEDS(MaybeSchedule(nextTimeout->When()));
   1268    }
   1269    // the Idle queue should all be past their firing time, so there we just
   1270    // need to restart the queue
   1271 
   1272    // XXX May not be needed if we don't stop the idle queue, as
   1273    // MinSchedulingDelay isn't relevant here
   1274    nextTimeout = mIdleTimeouts.GetFirst();
   1275    if (nextTimeout) {
   1276      mIdleExecutor->Cancel();
   1277      MOZ_ALWAYS_SUCCEEDS(
   1278          mIdleExecutor->MaybeSchedule(nextTimeout->When(), TimeDuration()));
   1279    }
   1280  }
   1281 }
   1282 
   1283 namespace {
   1284 
   1285 class ThrottleTimeoutsCallback final : public nsITimerCallback,
   1286                                       public nsINamed {
   1287 public:
   1288  explicit ThrottleTimeoutsCallback(nsIGlobalObject* aHandle)
   1289      : mGlobalObject(aHandle) {}
   1290 
   1291  NS_DECL_ISUPPORTS
   1292  NS_DECL_NSITIMERCALLBACK
   1293 
   1294  NS_IMETHOD GetName(nsACString& aName) override {
   1295    aName.AssignLiteral("ThrottleTimeoutsCallback");
   1296    return NS_OK;
   1297  }
   1298 
   1299 private:
   1300  ~ThrottleTimeoutsCallback() = default;
   1301 
   1302 private:
   1303  // The strong reference here keeps the Window/worker and hence the
   1304  // TimeoutManager object itself alive.
   1305  RefPtr<nsIGlobalObject> mGlobalObject;
   1306 };
   1307 
   1308 NS_IMPL_ISUPPORTS(ThrottleTimeoutsCallback, nsITimerCallback, nsINamed)
   1309 
   1310 NS_IMETHODIMP
   1311 ThrottleTimeoutsCallback::Notify(nsITimer* aTimer) {
   1312  if (mGlobalObject) {
   1313    mGlobalObject->GetTimeoutManager()->StartThrottlingTimeouts();
   1314  }
   1315  mGlobalObject = nullptr;
   1316  return NS_OK;
   1317 }
   1318 
   1319 }  // namespace
   1320 
   1321 bool TimeoutManager::BudgetThrottlingEnabled(bool aIsBackground) const {
   1322  // do not throttle workers if dom_workers_throttling is disabled
   1323  if (!mIsWindow && !StaticPrefs::dom_workers_throttling_enabled_AtStartup()) {
   1324    return false;
   1325  }
   1326 
   1327  // A window/worker can be throttled using budget if
   1328  // * It isn't active
   1329  // * If it isn't using WebRTC
   1330  // * If it hasn't got open WebSockets
   1331  // * If it hasn't got active IndexedDB databases
   1332 
   1333  // Note that we allow both foreground and background to be
   1334  // considered for budget throttling. What determines if they are if
   1335  // budget throttling is enabled is the max budget.
   1336  if ((aIsBackground
   1337           ? StaticPrefs::dom_timeout_background_throttling_max_budget()
   1338           : StaticPrefs::dom_timeout_foreground_throttling_max_budget()) < 0) {
   1339    return false;
   1340  }
   1341 
   1342  if (!mBudgetThrottleTimeouts || IsActive()) {
   1343    return false;
   1344  }
   1345 
   1346  // Check if there are any active IndexedDB databases
   1347  // TODO: mGlobalObject must implement HasActiveIndexedDBDatabases()
   1348  // Not implemented yet in workers
   1349  if (mGlobalObject.HasActiveIndexedDBDatabases()) {
   1350    // TODO: A window/worker can be throttled using budget if mGlobalObject has
   1351    // active IndexedDB Databases
   1352    // Not implemented yet in workers
   1353    return false;
   1354  }
   1355 
   1356  // Check if we have active PeerConnection
   1357  // TODO: mGlobalObject must implement HasActivePeerConnections()
   1358  if (mGlobalObject.HasActivePeerConnections()) {
   1359    // TODO: A window/worker can be throttled using budget if mGlobalObject has
   1360    // active peer connections
   1361    // Not implemented yet in workers
   1362    return false;
   1363  }
   1364 
   1365  if (mGlobalObject.HasOpenWebSockets()) {
   1366    // TODO: A window/worker can be throttled using budget if mGlobalObject has
   1367    // open web sockets
   1368    // Not implemented yet in workers
   1369    return false;
   1370  }
   1371 
   1372  return true;
   1373 }
   1374 
   1375 void TimeoutManager::StartThrottlingTimeouts() {
   1376  MOZ_ASSERT(NS_IsMainThread());
   1377  MOZ_DIAGNOSTIC_ASSERT(mThrottleTimeoutsTimer);
   1378 
   1379  MOZ_LOG(gTimeoutLog, LogLevel::Debug,
   1380          ("TimeoutManager %p started to throttle tracking timeouts\n", this));
   1381 
   1382  MOZ_DIAGNOSTIC_ASSERT(!mThrottleTimeouts);
   1383  mThrottleTimeouts = true;
   1384  mThrottleTrackingTimeouts = true;
   1385  mBudgetThrottleTimeouts =
   1386      StaticPrefs::dom_timeout_enable_budget_timer_throttling();
   1387  mThrottleTimeoutsTimer = nullptr;
   1388 }
   1389 
   1390 void TimeoutManager::OnDocumentLoaded() {
   1391  // The load event may be firing again if we're coming back to the page by
   1392  // navigating through the session history, so we need to ensure to only call
   1393  // this when mThrottleTimeouts hasn't been set yet.
   1394  if (!mThrottleTimeouts) {
   1395    MaybeStartThrottleTimeout();
   1396  }
   1397 }
   1398 
   1399 void TimeoutManager::MaybeStartThrottleTimeout() {
   1400  if (StaticPrefs::dom_timeout_throttling_delay() <= 0 ||
   1401      mGlobalObject.IsDying() || mGlobalObject.IsSuspended()) {
   1402    return;
   1403  }
   1404 
   1405  MOZ_DIAGNOSTIC_ASSERT(!mThrottleTimeouts);
   1406 
   1407  MOZ_LOG(gTimeoutLog, LogLevel::Debug,
   1408          ("TimeoutManager %p delaying tracking timeout throttling by %dms\n",
   1409           this, StaticPrefs::dom_timeout_throttling_delay()));
   1410 
   1411  nsCOMPtr<nsITimerCallback> callback =
   1412      new ThrottleTimeoutsCallback(&mGlobalObject);
   1413 
   1414  NS_NewTimerWithCallback(getter_AddRefs(mThrottleTimeoutsTimer), callback,
   1415                          StaticPrefs::dom_timeout_throttling_delay(),
   1416                          nsITimer::TYPE_ONE_SHOT, EventTarget());
   1417 }
   1418 
   1419 void TimeoutManager::BeginSyncOperation() {
   1420  // If we're beginning a sync operation, the currently running
   1421  // timeout will be put on hold. To not get into an inconsistent
   1422  // state, where the currently running timeout appears to take time
   1423  // equivalent to the period of us spinning up a new event loop,
   1424  // record what we have and stop recording until we reach
   1425  // EndSyncOperation.
   1426  RecordExecution(mRunningTimeout, nullptr);
   1427 }
   1428 
   1429 void TimeoutManager::EndSyncOperation() {
   1430  // If we're running a timeout, restart the measurement from here.
   1431  RecordExecution(nullptr, mRunningTimeout);
   1432 }
   1433 
   1434 nsIEventTarget* TimeoutManager::EventTarget() { return mEventTarget; }