Bug 1313864 - Move running idle callbacks to executor. r?smaug draft
authorAndreas Farre <farre@mozilla.com>
Tue, 08 Nov 2016 13:54:40 +0100
changeset 464128 541b1318e20dd142fd45be16eca2fd639b8ab45c
parent 464126 bb868860dfc35876d2d9c421c037c75a4fb9b3d2
child 542862 84a04d995649a765fa7123cd3d229f9613fe4305
push id42278
push userbmo:afarre@mozilla.com
push dateFri, 20 Jan 2017 13:07:04 +0000
reviewerssmaug
bugs1313864
milestone53.0a1
Bug 1313864 - Move running idle callbacks to executor. r?smaug MozReview-Commit-ID: EuJfpkM6Acf
dom/base/IdleRequest.cpp
dom/base/IdleRequest.h
dom/base/Timeout.h
dom/base/TimeoutHandler.cpp
dom/base/TimeoutHandler.h
dom/base/moz.build
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
--- a/dom/base/IdleRequest.cpp
+++ b/dom/base/IdleRequest.cpp
@@ -3,155 +3,57 @@
 /* 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/. */
 
 #include "IdleRequest.h"
 
 #include "mozilla/TimeStamp.h"
 #include "mozilla/dom/IdleDeadline.h"
-#include "mozilla/dom/Performance.h"
 #include "mozilla/dom/PerformanceTiming.h"
 #include "mozilla/dom/TimeoutManager.h"
 #include "mozilla/dom/WindowBinding.h"
 #include "nsComponentManagerUtils.h"
 #include "nsGlobalWindow.h"
 #include "nsISupportsPrimitives.h"
 #include "nsPIDOMWindow.h"
 
 namespace mozilla {
 namespace dom {
 
-IdleRequest::IdleRequest(JSContext* aCx, nsPIDOMWindowInner* aWindow,
-                         IdleRequestCallback& aCallback, uint32_t aHandle)
-  : mWindow(aWindow)
-  , mCallback(&aCallback)
-  , mHandle(aHandle)
-  , mTimeoutHandle(Nothing())
-{
-  MOZ_ASSERT(aWindow);
-
-  // Get the calling location.
-  nsJSUtils::GetCallingLocation(aCx, mFileName, &mLineNo, &mColumn);
-}
+IdleRequest::IdleRequest(IdleRequestCallback& aCallback, uint32_t aHandle)
+    : mCallback(&aCallback), mHandle(aHandle), mTimeoutHandle(Nothing()) {}
 
 IdleRequest::~IdleRequest()
 {
 }
 
-NS_IMPL_CYCLE_COLLECTION_CLASS(IdleRequest)
+NS_IMPL_CYCLE_COLLECTION(IdleRequest, mCallback)
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(IdleRequest)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(IdleRequest)
 
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IdleRequest)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback)
-NS_IMPL_CYCLE_COLLECTION_UNLINK_END
-
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IdleRequest)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback)
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
-
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleRequest)
-  NS_INTERFACE_MAP_ENTRY(nsIRunnable)
-  NS_INTERFACE_MAP_ENTRY(nsICancelableRunnable)
-  NS_INTERFACE_MAP_ENTRY(nsIIncrementalRunnable)
-  NS_INTERFACE_MAP_ENTRY(nsITimeoutHandler)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimeoutHandler)
 NS_INTERFACE_MAP_END
 
-nsresult
-IdleRequest::SetTimeout(uint32_t aTimeout)
+void
+IdleRequest::SetTimeoutHandle(int32_t aHandle)
 {
-  int32_t handle;
-  nsresult rv = mWindow->TimeoutManager().SetTimeout(
-    this, aTimeout, false, Timeout::Reason::eIdleCallbackTimeout, &handle);
-  mTimeoutHandle = Some(handle);
-
-  return rv;
+  mTimeoutHandle = Some(aHandle);
 }
 
 nsresult
-IdleRequest::Run()
-{
-  if (mCallback) {
-    RunIdleRequestCallback(false);
-  }
-
-  return NS_OK;
-}
-
-nsresult
-IdleRequest::Cancel()
-{
-  mCallback = nullptr;
-  CancelTimeout();
-  if (isInList()) {
-    remove();
-  }
-  Release();
-
-  return NS_OK;
-}
-
-void
-IdleRequest::SetDeadline(TimeStamp aDeadline)
-{
-  mozilla::dom::Performance* perf = mWindow->GetPerformance();
-  mDeadline =
-    perf ? perf->GetDOMTiming()->TimeStampToDOMHighRes(aDeadline) : 0.0;
-}
-
-nsresult
-IdleRequest::RunIdleRequestCallback(bool aDidTimeout)
+IdleRequest::Run(nsPIDOMWindowInner* aWindow, DOMHighResTimeStamp aDeadline,
+                 bool aDidTimeout)
 {
   MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mCallback);
 
-  if (!aDidTimeout) {
-    CancelTimeout();
-  }
-
-  remove();
   ErrorResult error;
   RefPtr<IdleDeadline> deadline =
-    new IdleDeadline(mWindow, aDidTimeout, mDeadline);
+    new IdleDeadline(aWindow, aDidTimeout, aDeadline);
   mCallback->Call(*deadline, error, "requestIdleCallback handler");
-  mCallback = nullptr;
-  Release();
 
   return error.StealNSResult();
 }
 
-void
-IdleRequest::CancelTimeout()
-{
-  if (mTimeoutHandle.isSome()) {
-    mWindow->TimeoutManager().ClearTimeout(
-      mTimeoutHandle.value(), Timeout::Reason::eIdleCallbackTimeout);
-  }
-}
-
-nsresult
-IdleRequest::Call()
-{
-  SetDeadline(TimeStamp::Now());
-  return RunIdleRequestCallback(true);
-}
-
-void
-IdleRequest::GetLocation(const char** aFileName, uint32_t* aLineNo,
-                         uint32_t* aColumn)
-{
-  *aFileName = mFileName.get();
-  *aLineNo = mLineNo;
-  *aColumn = mColumn;
-}
-
-void
-IdleRequest::MarkForCC()
-{
-  mCallback->MarkForCC();
-}
-
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/IdleRequest.h
+++ b/dom/base/IdleRequest.h
@@ -7,68 +7,50 @@
 #ifndef mozilla_dom_idlerequest_h
 #define mozilla_dom_idlerequest_h
 
 #include "mozilla/LinkedList.h"
 #include "mozilla/Maybe.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsDOMNavigationTiming.h"
-#include "nsITimeoutHandler.h"
 
 class nsPIDOMWindowInner;
 
 namespace mozilla {
 namespace dom {
 
 class IdleRequestCallback;
 
-class IdleRequest final : public nsITimeoutHandler
-                        , public nsIRunnable
-                        , public nsICancelableRunnable
-                        , public nsIIncrementalRunnable
-                        , public LinkedListElement<IdleRequest>
+class IdleRequest final : public nsISupports,
+                          public LinkedListElement<IdleRequest>
 {
 public:
-  IdleRequest(JSContext* aCx, nsPIDOMWindowInner* aWindow,
-              IdleRequestCallback& aCallback, uint32_t aHandle);
-
-  virtual nsresult Call() override;
-  virtual void GetLocation(const char** aFileName, uint32_t* aLineNo,
-                           uint32_t* aColumn) override;
-  virtual void MarkForCC() override;
+  IdleRequest(IdleRequestCallback& aCallback,
+              uint32_t aHandle);
 
-  nsresult SetTimeout(uint32_t aTimout);
-  nsresult RunIdleRequestCallback(bool aDidTimeout);
-  void CancelTimeout();
+  nsresult Run(nsPIDOMWindowInner* aWindow, DOMHighResTimeStamp aDeadline,
+               bool aDidTimeout);
 
-  NS_DECL_NSIRUNNABLE;
-  virtual nsresult Cancel() override;
-  virtual void SetDeadline(mozilla::TimeStamp aDeadline) override;
+  void SetTimeoutHandle(int32_t aHandle);
+  bool HasTimeout() const { return mTimeoutHandle.isSome(); }
+
 
   uint32_t Handle() const
   {
     return mHandle;
   }
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(IdleRequest, nsITimeoutHandler)
+  NS_DECL_CYCLE_COLLECTION_CLASS(IdleRequest)
 
 private:
   ~IdleRequest();
 
-  // filename, line number and JS language version string of the
-  // caller of setTimeout()
-  nsCString mFileName;
-  uint32_t mLineNo;
-  uint32_t mColumn;
-
-  nsCOMPtr<nsPIDOMWindowInner> mWindow;
   RefPtr<IdleRequestCallback> mCallback;
   uint32_t mHandle;
   mozilla::Maybe<int32_t> mTimeoutHandle;
-  DOMHighResTimeStamp mDeadline;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_idlerequest_h
--- a/dom/base/Timeout.h
+++ b/dom/base/Timeout.h
@@ -36,17 +36,21 @@ public:
   NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(Timeout)
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(Timeout)
 
   // The target may be specified to use a particular event queue for the
   // resulting timer runnable.  A nullptr target will result in the
   // default main thread being used.
   nsresult InitTimer(nsIEventTarget* aTarget, uint32_t aDelay);
 
-  enum class Reason { eTimeoutOrInterval, eIdleCallbackTimeout };
+  enum class Reason
+  {
+    eTimeoutOrInterval,
+    eIdleCallbackTimeout,
+  };
 
 #ifdef DEBUG
   bool HasRefCnt(uint32_t aCount) const;
 #endif // DEBUG
 
   void SetWhenOrTimeRemaining(const TimeStamp& aBaseTime,
                               const TimeDuration& aDelay);
 
@@ -71,16 +75,18 @@ public:
   bool mRunning;
 
   // True if this is a repeating/interval timer
   bool mIsInterval;
 
   // True if this is a timeout coming from a tracking script
   bool mIsTracking;
 
+  // Used to allow several reasons for setting a timeout, where each
+  // 'Reason' value is using a possibly overlapping set of id:s.
   Reason mReason;
 
   // Returned as value of setTimeout()
   uint32_t mTimeoutId;
 
   // Interval in milliseconds
   uint32_t mInterval;
 
new file mode 100644
--- /dev/null
+++ b/dom/base/TimeoutHandler.cpp
@@ -0,0 +1,35 @@
+/* -*- 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/. */
+
+#include "TimeoutHandler.h"
+
+namespace mozilla {
+namespace dom {
+
+TimeoutHandler::TimeoutHandler(JSContext* aCx)
+{
+  nsJSUtils::GetCallingLocation(aCx, mFileName, &mLineNo, &mColumn);
+}
+
+nsresult
+TimeoutHandler::Call()
+{
+  return NS_OK;
+}
+
+void
+TimeoutHandler::GetLocation(const char** aFileName, uint32_t* aLineNo,
+                                    uint32_t* aColumn)
+{
+  *aFileName = mFileName.get();
+  *aLineNo = mLineNo;
+  *aColumn = mColumn;
+}
+
+NS_IMPL_ISUPPORTS(TimeoutHandler, nsITimeoutHandler)
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/base/TimeoutHandler.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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_timeout_handler_h
+#define mozilla_dom_timeout_handler_h
+
+#include "nsCOMPtr.h"
+#include "nsISupports.h"
+#include "nsITimeoutHandler.h"
+
+namespace mozilla {
+namespace dom {
+
+/**
+ * Utility class for implementing nsITimeoutHandlers, designed to be subclassed.
+ */
+class TimeoutHandler : public nsITimeoutHandler
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  TimeoutHandler() : mFileName(""), mLineNo(0), mColumn(0) {}
+  explicit TimeoutHandler(JSContext *aCx);
+
+  virtual nsresult Call() override;
+  virtual void GetLocation(const char** aFileName, uint32_t* aLineNo,
+                           uint32_t* aColumn) override;
+  virtual void MarkForCC() override {}
+protected:
+  virtual ~TimeoutHandler() {}
+private:
+  TimeoutHandler(const TimeoutHandler&) = delete;
+  TimeoutHandler& operator=(const TimeoutHandler&) = delete;
+  TimeoutHandler& operator=(const TimeoutHandler&&) = delete;
+
+  nsCString mFileName;
+  uint32_t mLineNo;
+  uint32_t mColumn;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_timeout_handler_h
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -200,16 +200,17 @@ EXPORTS.mozilla.dom += [
     'ShadowRoot.h',
     'StructuredCloneHolder.h',
     'StructuredCloneTags.h',
     'StyleSheetList.h',
     'SubtleCrypto.h',
     'TabGroup.h',
     'Text.h',
     'Timeout.h',
+    'TimeoutHandler.h',
     'TimeoutManager.h',
     'TreeWalker.h',
     'WebKitCSSMatrix.h',
     'WebSocket.h',
     'WindowOrientationObserver.h',
 ]
 
 UNIFIED_SOURCES += [
@@ -340,16 +341,17 @@ UNIFIED_SOURCES += [
     'StructuredCloneHolder.cpp',
     'StyleSheetList.cpp',
     'SubtleCrypto.cpp',
     'TabGroup.cpp',
     'Text.cpp',
     'TextInputProcessor.cpp',
     'ThirdPartyUtil.cpp',
     'Timeout.cpp',
+    'TimeoutHandler.cpp',
     'TimeoutManager.cpp',
     'TreeWalker.cpp',
     'WebKitCSSMatrix.cpp',
     'WebSocket.cpp',
     'WindowNamedPropertiesHandler.cpp',
     'WindowOrientationObserver.cpp',
 ]
 
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -18,16 +18,17 @@
 #include "nsDOMNavigationTiming.h"
 #include "nsIDOMStorageManager.h"
 #include "mozilla/dom/Storage.h"
 #include "mozilla/dom/IdleRequest.h"
 #include "mozilla/dom/Performance.h"
 #include "mozilla/dom/StorageEvent.h"
 #include "mozilla/dom/StorageEventBinding.h"
 #include "mozilla/dom/Timeout.h"
+#include "mozilla/dom/TimeoutHandler.h"
 #include "mozilla/dom/TimeoutManager.h"
 #include "mozilla/IntegerPrintfMacros.h"
 #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
 #include "mozilla/dom/WindowOrientationObserver.h"
 #endif
 #include "nsDOMOfflineResourceList.h"
 #include "nsError.h"
 #include "nsIIdleService.h"
@@ -517,124 +518,329 @@ DialogValueHolder::Get(JSContext* aCx, J
   if (aSubject->Subsumes(mOrigin)) {
     aError = nsContentUtils::XPConnect()->VariantToJS(aCx, aScope,
                                                       mValue, aResult);
   } else {
     aResult.setUndefined();
   }
 }
 
-void
-nsGlobalWindow::PostThrottledIdleCallback()
+class IdleRequestExecutor final : public nsIRunnable
+                                , public nsICancelableRunnable
+                                , public nsIIncrementalRunnable
+{
+public:
+  explicit IdleRequestExecutor(nsGlobalWindow* aWindow)
+    : mDispatched(false)
+    , mDeadline(TimeStamp::Now())
+    , mWindow(aWindow)
+  {
+    MOZ_ASSERT(aWindow->IsInnerWindow());
+  }
+
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(IdleRequestExecutor, nsIRunnable)
+
+  NS_DECL_NSIRUNNABLE
+  nsresult Cancel() override;
+  void SetDeadline(TimeStamp aDeadline) override;
+
+  void Dispatch();
+private:
+  ~IdleRequestExecutor() {
+    mWindow = nullptr;
+  }
+
+  bool mDispatched;
+  TimeStamp mDeadline;
+  RefPtr<nsGlobalWindow> mWindow;
+};
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(IdleRequestExecutor)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(IdleRequestExecutor)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(IdleRequestExecutor)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IdleRequestExecutor)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IdleRequestExecutor)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleRequestExecutor)
+  NS_INTERFACE_MAP_ENTRY(nsIRunnable)
+  NS_INTERFACE_MAP_ENTRY(nsICancelableRunnable)
+  NS_INTERFACE_MAP_ENTRY(nsIIncrementalRunnable)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRunnable)
+NS_INTERFACE_MAP_END
+
+NS_IMETHODIMP
+IdleRequestExecutor::Run()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  mDispatched = false;
+  if (mWindow) {
+    return mWindow->ExecuteIdleRequest(mDeadline);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+IdleRequestExecutor::Cancel()
+{
+  mWindow = nullptr;
+  return NS_OK;
+}
+
+void
+IdleRequestExecutor::SetDeadline(TimeStamp aDeadline)
+{
+  if (!mWindow) {
+    return;
+  }
+
+  mDeadline = aDeadline;
+}
+
+void
+IdleRequestExecutor::Dispatch()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!mDispatched) {
+    mDispatched = true;
+    RefPtr<IdleRequestExecutor> request = this;
+    NS_IdleDispatchToCurrentThread(request.forget());
+  }
+}
+
+class IdleRequestExecutorTimeoutHandler final : public TimeoutHandler
+{
+public:
+  explicit IdleRequestExecutorTimeoutHandler(IdleRequestExecutor *aExecutor)
+      : mExecutor(aExecutor) {}
+
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS(IdleRequestExecutorTimeoutHandler)
+
+  nsresult Call() override
+  {
+    mExecutor->Dispatch();
+    return NS_OK;
+  }
+private:
+  ~IdleRequestExecutorTimeoutHandler() {}
+  RefPtr<IdleRequestExecutor> mExecutor;
+};
+
+NS_IMPL_CYCLE_COLLECTION(IdleRequestExecutorTimeoutHandler, mExecutor)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(IdleRequestExecutorTimeoutHandler)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(IdleRequestExecutorTimeoutHandler)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleRequestExecutorTimeoutHandler)
+  NS_INTERFACE_MAP_ENTRY(nsITimeoutHandler)
+NS_INTERFACE_MAP_END
+
+void
+nsGlobalWindow::ScheduleIdleRequestDispatch()
 {
   AssertIsOnMainThread();
 
-  if (mThrottledIdleRequestCallbacks.isEmpty())
-    return;
-
-  RefPtr<IdleRequest> request(mThrottledIdleRequestCallbacks.popFirst());
-  // ownership transferred from mThrottledIdleRequestCallbacks to
-  // mIdleRequestCallbacks
-  mIdleRequestCallbacks.insertBack(request);
-  NS_IdleDispatchToCurrentThread(request.forget());
+  if (mIdleRequestCallbacks.isEmpty()) {
+    if (mIdleRequestExecutor) {
+      mIdleRequestExecutor->Cancel();
+      mIdleRequestExecutor = nullptr;
+    }
+
+    return;
+  }
+
+  if (!mIdleRequestExecutor) {
+    mIdleRequestExecutor = new IdleRequestExecutor(this);
+  }
+
+  nsPIDOMWindowOuter* outer = GetOuterWindow();
+  if (outer && outer->AsOuter()->IsBackground()) {
+    nsCOMPtr<nsITimeoutHandler> handler = new IdleRequestExecutorTimeoutHandler(mIdleRequestExecutor);
+    int32_t dummy;
+    mTimeoutManager->SetTimeout(handler, 0, false,
+                                Timeout::Reason::eIdleCallbackTimeout, &dummy);
+    return;
+  }
+
+  mIdleRequestExecutor->Dispatch();
 }
 
 /* static */ void
 nsGlobalWindow::InsertIdleCallbackIntoList(IdleRequest* aRequest,
                                            IdleRequests& aList)
 {
+  AssertIsOnMainThread();
   aList.insertBack(aRequest);
   aRequest->AddRef();
 }
 
+/* static */ void
+nsGlobalWindow::RemoveIdleCallbackFromList(mozilla::dom::IdleRequest* aRequest,
+                                           IdleRequests& aList)
+{
+  AssertIsOnMainThread();
+  aRequest->removeFrom(aList);
+  aRequest->Release();
+}
+
+nsresult
+nsGlobalWindow::RunIdleRequest(IdleRequest* aRequest,
+                               DOMHighResTimeStamp aDeadline, bool aDidTimeout)
+{
+  AssertIsOnMainThread();
+  RefPtr<IdleRequest> request(aRequest);
+  nsresult result = request->Run(AsInner(), aDeadline, aDidTimeout);
+  RemoveIdleCallbackFromList(request, mIdleRequestCallbacks);
+  return result;
+}
+
+nsresult
+nsGlobalWindow::ExecuteIdleRequest(TimeStamp aDeadline)
+{
+  AssertIsOnMainThread();
+  RefPtr<IdleRequest> request = mIdleRequestCallbacks.getFirst();
+
+  if (!request) {
+    return NS_OK;
+  }
+
+  if (request->HasTimeout()) {
+    mTimeoutManager->ClearTimeout(request->Handle(),
+                                  Timeout::Reason::eIdleCallbackTimeout);
+  }
+
+  DOMHighResTimeStamp deadline = 0.0;
+
+  if (Performance* perf = GetPerformance()) {
+    deadline = perf->GetDOMTiming()->TimeStampToDOMHighRes(aDeadline);
+  }
+
+  nsresult result = RunIdleRequest(request, deadline, false);
+
+  ScheduleIdleRequestDispatch();
+  return result;
+}
+
+class IdleRequestTimeoutHandler final : public TimeoutHandler
+{
+public:
+  IdleRequestTimeoutHandler(JSContext* aCx, IdleRequest *aIdleRequest,
+                            nsPIDOMWindowInner *aWindow)
+    : TimeoutHandler(aCx), mIdleRequest(aIdleRequest), mWindow(aWindow) {}
+
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS(IdleRequestTimeoutHandler)
+
+  nsresult Call() override
+  {
+    return nsGlobalWindow::Cast(mWindow)->RunIdleRequest(mIdleRequest, 0.0, true);
+  }
+
+private:
+  ~IdleRequestTimeoutHandler() {}
+
+  RefPtr<IdleRequest> mIdleRequest;
+  nsCOMPtr<nsPIDOMWindowInner> mWindow;
+};
+
+NS_IMPL_CYCLE_COLLECTION(IdleRequestTimeoutHandler, mIdleRequest, mWindow)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(IdleRequestTimeoutHandler)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(IdleRequestTimeoutHandler)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleRequestTimeoutHandler)
+  NS_INTERFACE_MAP_ENTRY(nsITimeoutHandler)
+NS_INTERFACE_MAP_END
+
 uint32_t
 nsGlobalWindow::RequestIdleCallback(JSContext* aCx,
                                     IdleRequestCallback& aCallback,
                                     const IdleRequestOptions& aOptions,
                                     ErrorResult& aError)
 {
   MOZ_RELEASE_ASSERT(IsInnerWindow());
   AssertIsOnMainThread();
 
-  uint32_t handle = ++mIdleRequestCallbackCounter;
+  uint32_t handle = mIdleRequestCallbackCounter++;
 
   RefPtr<IdleRequest> request =
-    new IdleRequest(aCx, AsInner(), aCallback, handle);
+    new IdleRequest(aCallback, handle);
 
   if (aOptions.mTimeout.WasPassed()) {
-    aError = request->SetTimeout(aOptions.mTimeout.Value());
-    if (NS_WARN_IF(aError.Failed())) {
+    int32_t timeoutHandle;
+    nsCOMPtr<nsITimeoutHandler> handler(new IdleRequestTimeoutHandler(aCx, request, AsInner()));
+
+    nsresult rv = mTimeoutManager->SetTimeout(
+        handler, aOptions.mTimeout.Value(), false,
+        Timeout::Reason::eIdleCallbackTimeout, &timeoutHandle);
+
+    if (NS_WARN_IF(NS_FAILED(rv))) {
       return 0;
     }
-  }
-
-  nsGlobalWindow* outer = GetOuterWindowInternal();
-  if (outer && outer->AsOuter()->IsBackground()) {
-    // mThrottledIdleRequestCallbacks now owns request
-    InsertIdleCallbackIntoList(request, mThrottledIdleRequestCallbacks);
-
-    NS_DelayedDispatchToCurrentThread(
-      NewRunnableMethod(this, &nsGlobalWindow::PostThrottledIdleCallback),
-      10000);
-  } else {
-    MOZ_ASSERT(mThrottledIdleRequestCallbacks.isEmpty());
-
-    // mIdleRequestCallbacks now owns request
-    InsertIdleCallbackIntoList(request, mIdleRequestCallbacks);
-
-    NS_IdleDispatchToCurrentThread(request.forget());
+
+    request->SetTimeoutHandle(timeoutHandle);
+  }
+
+  // If the list of idle callback requests is not empty it means that
+  // we've already dispatched the first idle request. It is the
+  // responsibility of that to dispatch the next.
+  bool needsScheduling = mIdleRequestCallbacks.isEmpty();
+  // mIdleRequestCallbacks now owns request
+  InsertIdleCallbackIntoList(request, mIdleRequestCallbacks);
+
+  if (needsScheduling) {
+    ScheduleIdleRequestDispatch();
   }
 
   return handle;
 }
 
 void
 nsGlobalWindow::CancelIdleCallback(uint32_t aHandle)
 {
   MOZ_RELEASE_ASSERT(IsInnerWindow());
 
   for (IdleRequest* r : mIdleRequestCallbacks) {
     if (r->Handle() == aHandle) {
-      r->Cancel();
+      RemoveIdleCallbackFromList(r, mIdleRequestCallbacks);
       break;
     }
   }
 }
 
 void
 nsGlobalWindow::DisableIdleCallbackRequests()
 {
+  if (mIdleRequestExecutor) {
+    mIdleRequestExecutor->Cancel();
+    mIdleRequestExecutor = nullptr;
+  }
+
   while (!mIdleRequestCallbacks.isEmpty()) {
-    RefPtr<IdleRequest> request = mIdleRequestCallbacks.popFirst();
-    request->Cancel();
-  }
-
-  while (!mThrottledIdleRequestCallbacks.isEmpty()) {
-    RefPtr<IdleRequest> request = mThrottledIdleRequestCallbacks.popFirst();
-    request->Cancel();
-  }
-}
-
-void nsGlobalWindow::UnthrottleIdleCallbackRequests()
-{
-  AssertIsOnMainThread();
-
-  while (!mThrottledIdleRequestCallbacks.isEmpty()) {
-    RefPtr<IdleRequest> request(mThrottledIdleRequestCallbacks.popFirst());
-    mIdleRequestCallbacks.insertBack(request);
-    NS_IdleDispatchToCurrentThread(request.forget());
+    RefPtr<IdleRequest> request = mIdleRequestCallbacks.getFirst();
+    RemoveIdleCallbackFromList(request, mIdleRequestCallbacks);
   }
 }
 
 bool
 nsGlobalWindow::IsBackgroundInternal() const
 {
   return !mOuterWindow || mOuterWindow->IsBackground();
 }
-
 namespace mozilla {
 namespace dom {
 extern uint64_t
 NextWindowID();
 } // namespace dom
 } // namespace mozilla
 
 template<class T>
@@ -1215,16 +1421,17 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalW
     mHasSeenGamepadInput(false),
     mNotifiedIDDestroyed(false),
     mAllowScriptsToClose(false),
     mSuspendDepth(0),
     mFreezeDepth(0),
     mFocusMethod(0),
     mSerial(0),
     mIdleRequestCallbackCounter(1),
+    mIdleRequestExecutor(nullptr),
 #ifdef DEBUG
     mSetOpenerWindowCalled(false),
 #endif
 #ifdef MOZ_B2G
     mNetworkUploadObserverEnabled(false),
     mNetworkDownloadObserverEnabled(false),
 #endif
     mCleanedUp(false),
@@ -1926,24 +2133,21 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuspendedDoc)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIndexedDB)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentPrincipal)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDoc)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleService)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWakeLock)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingStorageEvents)
 
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleRequestExecutor)
   for (IdleRequest* request : tmp->mIdleRequestCallbacks) {
     cb.NoteNativeChild(request, NS_CYCLE_COLLECTION_PARTICIPANT(IdleRequest));
   }
 
-  for (IdleRequest* request : tmp->mThrottledIdleRequestCallbacks) {
-    cb.NoteNativeChild(request, NS_CYCLE_COLLECTION_PARTICIPANT(IdleRequest));
-  }
-
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleObservers)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGamepads)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCacheStorage)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVRDisplays)
 
   // Traverse stuff from nsPIDOMWindow
@@ -2038,16 +2242,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsole)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioWorklet)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPaintWorklet)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mExternal)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mMozSelfSupport)
 
   tmp->UnlinkHostObjectURIs();
 
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleRequestExecutor)
   tmp->DisableIdleCallbackRequests();
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 #ifdef DEBUG
 void
 nsGlobalWindow::RiskyUnlink()
@@ -10040,17 +10245,16 @@ void nsGlobalWindow::SetIsBackground(boo
   if (!inner) {
     return;
   }
 
   if (resetTimers) {
     inner->mTimeoutManager->ResetTimersForThrottleReduction();
   }
 
-  inner->UnthrottleIdleCallbackRequests();
   inner->SyncGamepadState();
 }
 
 void nsGlobalWindow::MaybeUpdateTouchState()
 {
   FORWARD_TO_INNER_VOID(MaybeUpdateTouchState, ());
 
   if (mMayHaveTouchEventListener) {
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -97,32 +97,35 @@ class nsHistory;
 class nsGlobalWindowObserver;
 class nsGlobalWindow;
 class nsDOMWindowUtils;
 class nsIIdleService;
 struct nsRect;
 
 class nsWindowSizes;
 
+class IdleRequestExecutor;
+
 namespace mozilla {
 class DOMEventTargetHelper;
 class ThrottledEventQueue;
 namespace dom {
 class BarProp;
 struct ChannelPixelLayout;
 class Console;
 class Crypto;
 class CustomElementRegistry;
 class DocGroup;
 class External;
 class Function;
 class Gamepad;
 enum class ImageBitmapFormat : uint8_t;
 class IdleRequest;
 class IdleRequestCallback;
+class IncrementalRunnable;
 class Location;
 class MediaQueryList;
 class MozSelfSupport;
 class Navigator;
 class OwningExternalOrWindowProxy;
 class Promise;
 class PostMessageEvent;
 struct RequestInit;
@@ -1100,17 +1103,16 @@ public:
   void CancelAnimationFrame(int32_t aHandle, mozilla::ErrorResult& aError);
 
   uint32_t RequestIdleCallback(JSContext* aCx,
                                mozilla::dom::IdleRequestCallback& aCallback,
                                const mozilla::dom::IdleRequestOptions& aOptions,
                                mozilla::ErrorResult& aError);
   void CancelIdleCallback(uint32_t aHandle);
 
-
 #ifdef MOZ_WEBSPEECH
   mozilla::dom::SpeechSynthesis*
     GetSpeechSynthesis(mozilla::ErrorResult& aError);
   bool HasActiveSpeechSynthesis();
 #endif
   already_AddRefed<nsICSSDeclaration>
     GetDefaultComputedStyle(mozilla::dom::Element& aElt,
                             const nsAString& aPseudoElt,
@@ -1770,16 +1772,30 @@ public:
   // Dispatch a runnable related to the global.
   virtual nsresult Dispatch(const char* aName,
                             mozilla::dom::TaskCategory aCategory,
                             already_AddRefed<nsIRunnable>&& aRunnable) override;
 
   virtual nsIEventTarget*
   EventTargetFor(mozilla::dom::TaskCategory aCategory) const override;
 
+  void DisableIdleCallbackRequests();
+  uint32_t IdleRequestHandle() const { return mIdleRequestCallbackCounter; }
+  nsresult RunIdleRequest(mozilla::dom::IdleRequest* aRequest,
+                          DOMHighResTimeStamp aDeadline, bool aDidTimeout);
+  nsresult ExecuteIdleRequest(TimeStamp aDeadline);
+  void ScheduleIdleRequestDispatch();
+
+  typedef mozilla::LinkedList<mozilla::dom::IdleRequest> IdleRequests;
+  static void InsertIdleCallbackIntoList(mozilla::dom::IdleRequest* aRequest,
+                                         IdleRequests& aList);
+
+  static void RemoveIdleCallbackFromList(mozilla::dom::IdleRequest* aRequest,
+                                         IdleRequests& aList);
+
 protected:
   // These members are only used on outer window objects. Make sure
   // you never set any of these on an inner object!
   bool                          mFullScreen : 1;
   bool                          mFullscreenMode : 1;
   bool                          mIsClosed : 1;
   bool                          mInClose : 1;
   // mHavePendingClose means we've got a termination function set to
@@ -1906,29 +1922,23 @@ protected:
   uint32_t mSuspendDepth;
   uint32_t mFreezeDepth;
 
   // the method that was used to focus mFocusedNode
   uint32_t mFocusMethod;
 
   uint32_t mSerial;
 
-  void DisableIdleCallbackRequests();
-  void UnthrottleIdleCallbackRequests();
-
-  void PostThrottledIdleCallback();
-
-  typedef mozilla::LinkedList<mozilla::dom::IdleRequest> IdleRequests;
-  static void InsertIdleCallbackIntoList(mozilla::dom::IdleRequest* aRequest,
-                                         IdleRequests& aList);
+  // The value for the next idle request timeout handler handle
+  uint32_t mIdleRequestTimeoutHandlerCounter;
 
   // The current idle request callback handle
   uint32_t mIdleRequestCallbackCounter;
   IdleRequests mIdleRequestCallbacks;
-  IdleRequests mThrottledIdleRequestCallbacks;
+  RefPtr<IdleRequestExecutor> mIdleRequestExecutor;
 
 #ifdef DEBUG
   bool mSetOpenerWindowCalled;
   nsCOMPtr<nsIURI> mLastOpenedURI;
 #endif
 
 #ifdef MOZ_B2G
   bool mNetworkUploadObserverEnabled;
@@ -1995,16 +2005,17 @@ protected:
 
   nsAutoPtr<mozilla::dom::VREventObserver> mVREventObserver;
 
   friend class nsDOMScriptableHelper;
   friend class nsDOMWindowUtils;
   friend class mozilla::dom::PostMessageEvent;
   friend class DesktopNotification;
   friend class mozilla::dom::TimeoutManager;
+  friend class IdleRequestExecutor;
 
   static WindowByIdTable* sWindowsById;
   static bool sWarnedAboutWindowInternal;
 };
 
 inline nsISupports*
 ToSupports(nsGlobalWindow *p)
 {