Exemple #1
0
nsresult
TimeoutManager::Timeouts::ResetTimersForThrottleReduction(int32_t aPreviousThrottleDelayMS,
                                                          int32_t aMinTimeoutValueMS,
                                                          SortBy aSortBy,
                                                          nsIEventTarget* aQueue)
{
  TimeStamp now = TimeStamp::Now();

  // If insertion point is non-null, we're in the middle of firing timers and
  // the timers we're planning to fire all come before insertion point;
  // insertion point itself is a dummy timeout with an When() that may be
  // semi-bogus.  In that case, we don't need to do anything with insertion
  // point or anything before it, so should start at the timer after insertion
  // point, if there is one.
  // Otherwise, start at the beginning of the list.
  for (Timeout* timeout = InsertionPoint() ?
         InsertionPoint()->getNext() : GetFirst();
       timeout; ) {
    // It's important that this check be <= so that we guarantee that
    // taking std::max with |now| won't make a quantity equal to
    // timeout->When() below.
    if (timeout->When() <= now) {
      timeout = timeout->getNext();
      continue;
    }

    if (timeout->When() - now >
        TimeDuration::FromMilliseconds(aPreviousThrottleDelayMS)) {
      // No need to loop further.  Timeouts are sorted in When() order
      // and the ones after this point were all set up for at least
      // gMinBackgroundTimeoutValue ms and hence were not clamped.
      break;
    }

    // We reduced our throttled delay. Re-init the timer appropriately.
    // Compute the interval the timer should have had if it had not been set in a
    // background window
    TimeDuration interval =
      TimeDuration::FromMilliseconds(std::max(timeout->mInterval,
                                            uint32_t(aMinTimeoutValueMS)));
    uint32_t oldIntervalMillisecs = 0;
    timeout->mTimer->GetDelay(&oldIntervalMillisecs);
    TimeDuration oldInterval = TimeDuration::FromMilliseconds(oldIntervalMillisecs);
    if (oldInterval > interval) {
      // unclamp
      TimeStamp firingTime =
        std::max(timeout->When() - oldInterval + interval, now);

      NS_ASSERTION(firingTime < timeout->When(),
                   "Our firing time should strictly decrease!");

      TimeDuration delay = firingTime - now;
      timeout->SetWhenOrTimeRemaining(now, delay);
      MOZ_DIAGNOSTIC_ASSERT(timeout->When() == firingTime);

      // Since we reset When() we need to move |timeout| to the right
      // place in the list so that it remains sorted by When().

      // Get the pointer to the next timeout now, before we move the
      // current timeout in the list.
      Timeout* nextTimeout = timeout->getNext();

      // It is safe to remove and re-insert because When() is now
      // strictly smaller than it used to be, so we know we'll insert
      // |timeout| before nextTimeout.
      NS_ASSERTION(!nextTimeout ||
                   timeout->When() < nextTimeout->When(), "How did that happen?");
      timeout->remove();
      // Insert() will addref |timeout| and reset mFiringDepth.  Make sure to
      // undo that after calling it.
      uint32_t firingDepth = timeout->mFiringDepth;
      Insert(timeout, aSortBy);
      timeout->mFiringDepth = firingDepth;
      timeout->Release();

      nsresult rv = timeout->InitTimer(aQueue, delay.ToMilliseconds());

      if (NS_FAILED(rv)) {
        NS_WARNING("Error resetting non background timer for DOM timeout!");
        return rv;
      }

      timeout = nextTimeout;
    } else {
      timeout = timeout->getNext();
    }
  }

  return NS_OK;
}
Exemple #2
0
void
TimeoutManager::RunTimeout(Timeout* aTimeout)
{
  if (mWindow.IsSuspended()) {
    return;
  }

  NS_ASSERTION(!mWindow.IsFrozen(), "Timeout running on a window in the bfcache!");

  Timeout* last_expired_normal_timeout = nullptr;
  Timeout* last_expired_tracking_timeout = nullptr;
  bool     last_expired_timeout_is_normal = false;
  Timeout* last_normal_insertion_point = nullptr;
  Timeout* last_tracking_insertion_point = nullptr;
  uint32_t firingDepth = mTimeoutFiringDepth + 1;

  // Make sure that the window and the script context don't go away as
  // a result of running timeouts
  nsCOMPtr<nsIScriptGlobalObject> windowKungFuDeathGrip(&mWindow);
  // Silence the static analysis error about windowKungFuDeathGrip.  Accessing
  // members of mWindow here is safe, because the lifetime of TimeoutManager is
  // the same as the lifetime of the containing nsGlobalWindow.
  Unused << windowKungFuDeathGrip;

  // A native timer has gone off. See which of our timeouts need
  // servicing
  TimeStamp now = TimeStamp::Now();
  TimeStamp deadline;

  if (aTimeout && aTimeout->When() > now) {
    // The OS timer fired early (which can happen due to the timers
    // having lower precision than TimeStamp does).  Set |deadline| to
    // be the time when the OS timer *should* have fired so that any
    // timers that *should* have fired before aTimeout *will* be fired
    // now.

    deadline = aTimeout->When();
  } else {
    deadline = now;
  }

  // The timeout list is kept in deadline order. Discover the latest timeout
  // whose deadline has expired. On some platforms, native timeout events fire
  // "early", but we handled that above by setting deadline to aTimeout->When()
  // if the timer fired early.  So we can stop walking if we get to timeouts
  // whose When() is greater than deadline, since once that happens we know
  // nothing past that point is expired.
  {
    // Use a nested scope in order to make sure the strong references held by
    // the iterator are freed after the loop.
    OrderedTimeoutIterator expiredIter(mNormalTimeouts,
                                       mTrackingTimeouts,
                                       nullptr,
                                       nullptr);
    while (true) {
      Timeout* timeout = expiredIter.Next();
      if (!timeout || timeout->When() > deadline) {
        break;
      }

      if (timeout->mFiringDepth == 0) {
        // Mark any timeouts that are on the list to be fired with the
        // firing depth so that we can reentrantly run timeouts
        timeout->mFiringDepth = firingDepth;
        last_expired_timeout_is_normal = expiredIter.PickedNormalIter();
        if (last_expired_timeout_is_normal) {
          last_expired_normal_timeout = timeout;
        } else {
          last_expired_tracking_timeout = timeout;
        }

        // Run available timers until we see our target timer.  After
        // that, however, stop coalescing timers so we can yield the
        // main thread.  Further timers that are ready will get picked
        // up by their own nsITimer runnables when they execute.
        //
        // For chrome windows, however, we do coalesce all timers and
        // do not yield the main thread.  This is partly because we
        // trust chrome windows not to misbehave and partly because a
        // number of browser chrome tests have races that depend on this
        // coalescing.
        if (timeout == aTimeout && !mWindow.IsChromeWindow()) {
          break;
        }
      }

      expiredIter.UpdateIterator();
    }
  }

  // Maybe the timeout that the event was fired for has been deleted
  // and there are no others timeouts with deadlines that make them
  // eligible for execution yet. Go away.
  if (!last_expired_normal_timeout && !last_expired_tracking_timeout) {
    return;
  }

  // Insert a dummy timeout into the list of timeouts between the
  // portion of the list that we are about to process now and those
  // timeouts that will be processed in a future call to
  // win_run_timeout(). This dummy timeout serves as the head of the
  // list for any timeouts inserted as a result of running a timeout.
  RefPtr<Timeout> dummy_normal_timeout = new Timeout();
  dummy_normal_timeout->mFiringDepth = firingDepth;
  dummy_normal_timeout->SetDummyWhen(now);
  if (last_expired_timeout_is_normal) {
    last_expired_normal_timeout->setNext(dummy_normal_timeout);
  }

  RefPtr<Timeout> dummy_tracking_timeout = new Timeout();
  dummy_tracking_timeout->mFiringDepth = firingDepth;
  dummy_tracking_timeout->SetDummyWhen(now);
  if (!last_expired_timeout_is_normal) {
    last_expired_tracking_timeout->setNext(dummy_tracking_timeout);
  }

  RefPtr<Timeout> timeoutExtraRef1(dummy_normal_timeout);
  RefPtr<Timeout> timeoutExtraRef2(dummy_tracking_timeout);

  // Now we need to search the normal and tracking timer list at the same
  // time to run the timers in the scheduled order.

  last_normal_insertion_point = mNormalTimeouts.InsertionPoint();
  if (last_expired_timeout_is_normal) {
    // If we ever start setting insertion point to a non-dummy timeout, the logic
    // in ResetTimersForThrottleReduction will need to change.
    mNormalTimeouts.SetInsertionPoint(dummy_normal_timeout);
  }

  last_tracking_insertion_point = mTrackingTimeouts.InsertionPoint();
  if (!last_expired_timeout_is_normal) {
    // If we ever start setting mTrackingTimeoutInsertionPoint to a non-dummy timeout,
    // the logic in ResetTimersForThrottleReduction will need to change.
    mTrackingTimeouts.SetInsertionPoint(dummy_tracking_timeout);
  }

  // We stop iterating each list when we go past the last expired timeout from
  // that list that we have observed above.  That timeout will either be the
  // dummy timeout for the list that the last expired timeout came from, or it
  // will be the next item after the last timeout we looked at (or nullptr if
  // we have exhausted the entire list while looking for the last expired
  // timeout).
  {
    // Use a nested scope in order to make sure the strong references held by
    // the iterator are freed after the loop.
    OrderedTimeoutIterator runIter(mNormalTimeouts,
                                   mTrackingTimeouts,
                                   last_expired_normal_timeout ?
                                     last_expired_normal_timeout->getNext() :
                                     nullptr,
                                   last_expired_tracking_timeout ?
                                     last_expired_tracking_timeout->getNext() :
                                     nullptr);
    while (!mWindow.IsFrozen()) {
      Timeout* timeout = runIter.Next();
      MOZ_ASSERT(timeout != dummy_normal_timeout &&
                 timeout != dummy_tracking_timeout,
                 "We should have stopped iterating before getting to the dummy timeout");
      if (!timeout) {
        // We have run out of timeouts!
        break;
      }
      runIter.UpdateIterator();

      if (timeout->mFiringDepth != firingDepth) {
        // We skip the timeout since it's on the list to run at another
        // depth.
        continue;
      }

      if (mWindow.IsSuspended()) {
        // Some timer did suspend us. Make sure the
        // rest of the timers get executed later.
        timeout->mFiringDepth = 0;
        continue;
      }

      // The timeout is on the list to run at this depth, go ahead and
      // process it.

      // Get the script context (a strong ref to prevent it going away)
      // for this timeout and ensure the script language is enabled.
      nsCOMPtr<nsIScriptContext> scx = mWindow.GetContextInternal();

      if (!scx) {
        // No context means this window was closed or never properly
        // initialized for this language.
        continue;
      }

      // This timeout is good to run
      bool timeout_was_cleared = mWindow.RunTimeoutHandler(timeout, scx);

      if (timeout_was_cleared) {
        // Make sure the iterator isn't holding any Timeout objects alive.
        runIter.Clear();

        // The running timeout's window was cleared, this means that
        // ClearAllTimeouts() was called from a *nested* call, possibly
        // through a timeout that fired while a modal (to this window)
        // dialog was open or through other non-obvious paths.
        // Note that if the last expired timeout corresponding to each list
        // is null, then we should expect a refcount of two, since the
        // dummy timeout for this queue was never injected into it, and the
        // corresponding timeoutExtraRef variable hasn't been cleared yet.
        if (last_expired_timeout_is_normal) {
          MOZ_ASSERT(dummy_normal_timeout->HasRefCnt(1), "dummy_normal_timeout may leak");
          MOZ_ASSERT(dummy_tracking_timeout->HasRefCnt(2), "dummy_tracking_timeout may leak");
          Unused << timeoutExtraRef1.forget().take();
        } else {
          MOZ_ASSERT(dummy_normal_timeout->HasRefCnt(2), "dummy_normal_timeout may leak");
          MOZ_ASSERT(dummy_tracking_timeout->HasRefCnt(1), "dummy_tracking_timeout may leak");
          Unused << timeoutExtraRef2.forget().take();
        }

        mNormalTimeouts.SetInsertionPoint(last_normal_insertion_point);
        mTrackingTimeouts.SetInsertionPoint(last_tracking_insertion_point);

        return;
      }

      // If we have a regular interval timer, we re-schedule the
      // timeout, accounting for clock drift.
      bool needsReinsertion = RescheduleTimeout(timeout, now, !aTimeout);

      // Running a timeout can cause another timeout to be deleted, so
      // we need to reset the pointer to the following timeout.
      runIter.UpdateIterator();

      timeout->remove();

      if (needsReinsertion) {
        // Insert interval timeout onto the corresponding list sorted in
        // deadline order. AddRefs timeout.
        if (runIter.PickedTrackingIter()) {
          mTrackingTimeouts.Insert(timeout,
                                   mWindow.IsFrozen() ? Timeouts::SortBy::TimeRemaining
                                                      : Timeouts::SortBy::TimeWhen);
        } else {
          mNormalTimeouts.Insert(timeout,
                                 mWindow.IsFrozen() ? Timeouts::SortBy::TimeRemaining
                                                    : Timeouts::SortBy::TimeWhen);
        }
      }

      // Release the timeout struct since it's possibly out of the list
      timeout->Release();
    }
  }

  // Take the dummy timeout off the head of the list
  if (dummy_normal_timeout->isInList()) {
    dummy_normal_timeout->remove();
  }
  timeoutExtraRef1 = nullptr;
  MOZ_ASSERT(dummy_normal_timeout->HasRefCnt(1), "dummy_normal_timeout may leak");
  if (dummy_tracking_timeout->isInList()) {
    dummy_tracking_timeout->remove();
  }
  timeoutExtraRef2 = nullptr;
  MOZ_ASSERT(dummy_tracking_timeout->HasRefCnt(1), "dummy_tracking_timeout may leak");

  mNormalTimeouts.SetInsertionPoint(last_normal_insertion_point);
  mTrackingTimeouts.SetInsertionPoint(last_tracking_insertion_point);

  MaybeApplyBackPressure();
}