Bug 1198381 - Extend nsIThread with idleDispatch, r=froydnj,smaug
☠☠ backed out by 186fcc0dd237 ☠ ☠
authorAndreas Farre <farre@mozilla.com>
Wed, 24 Aug 2016 16:18:06 +0200
changeset 362041 333a899fb5e6922ed7ab29e5a0a240d322d88023
parent 362040 ffaf3d371130200ed62ffb462386a191901d1421
child 362042 eb2606332cb8aa0dce91fca6b618637407371bd8
push id6795
push userjlund@mozilla.com
push dateMon, 23 Jan 2017 14:19:46 +0000
treeherdermozilla-beta@76101b503191 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj, smaug
bugs1198381
milestone52.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1198381 - Extend nsIThread with idleDispatch, r=froydnj,smaug The intent of idleDispatch is the possibility to have a runnable executed when the thread is idle. This is accomplished by adding an event queue for idle tasks that will only be considered when the main event queue is empty and the caller of ProcessNextEvent doesn't require that we wait until there is an event on the main event queue. MozReview-Commit-ID: IDWQfzZqWpZ
xpcom/glue/nsThreadUtils.cpp
xpcom/glue/nsThreadUtils.h
xpcom/threads/LazyIdleThread.cpp
xpcom/threads/MainThreadIdlePeriod.cpp
xpcom/threads/MainThreadIdlePeriod.h
xpcom/threads/moz.build
xpcom/threads/nsIIdlePeriod.idl
xpcom/threads/nsIIncrementalRunnable.h
xpcom/threads/nsIThread.idl
xpcom/threads/nsThread.cpp
xpcom/threads/nsThread.h
xpcom/threads/nsThreadManager.cpp
--- a/xpcom/glue/nsThreadUtils.cpp
+++ b/xpcom/glue/nsThreadUtils.cpp
@@ -27,16 +27,25 @@ using mozilla::IsVistaOrLater;
 
 #include <pratom.h>
 #include <prthread.h>
 
 using namespace mozilla;
 
 #ifndef XPCOM_GLUE_AVOID_NSPR
 
+NS_IMPL_ISUPPORTS(IdlePeriod, nsIIdlePeriod)
+
+NS_IMETHODIMP
+IdlePeriod::GetIdlePeriodHint(TimeStamp* aIdleDeadline)
+{
+  *aIdleDeadline = TimeStamp();
+  return NS_OK;
+}
+
 NS_IMPL_ISUPPORTS(Runnable, nsIRunnable)
 
 NS_IMETHODIMP
 Runnable::Run()
 {
   // Do nothing
   return NS_OK;
 }
@@ -46,16 +55,25 @@ NS_IMPL_ISUPPORTS_INHERITED(CancelableRu
 
 nsresult
 CancelableRunnable::Cancel()
 {
   // Do nothing
   return NS_OK;
 }
 
+NS_IMPL_ISUPPORTS_INHERITED(IncrementalRunnable, CancelableRunnable,
+                            nsIIncrementalRunnable)
+
+void
+IncrementalRunnable::SetDeadline(TimeStamp aDeadline)
+{
+  // Do nothing
+}
+
 #endif  // XPCOM_GLUE_AVOID_NSPR
 
 //-----------------------------------------------------------------------------
 
 nsresult
 NS_NewThread(nsIThread** aResult, nsIRunnable* aEvent, uint32_t aStackSize)
 {
   nsCOMPtr<nsIThread> thread;
@@ -197,16 +215,48 @@ NS_DispatchToMainThread(already_AddRefed
 // release them here.
 nsresult
 NS_DispatchToMainThread(nsIRunnable* aEvent, uint32_t aDispatchFlags)
 {
   nsCOMPtr<nsIRunnable> event(aEvent);
   return NS_DispatchToMainThread(event.forget(), aDispatchFlags);
 }
 
+extern NS_METHOD
+nsresult
+NS_IdleDispatchToCurrentThread(already_AddRefed<nsIRunnable>&& aEvent)
+{
+  nsresult rv;
+  nsCOMPtr<nsIRunnable> event(aEvent);
+#ifdef MOZILLA_INTERNAL_API
+  nsIThread* thread = NS_GetCurrentThread();
+  if (!thread) {
+    return NS_ERROR_UNEXPECTED;
+  }
+#else
+  nsCOMPtr<nsIThread> thread;
+  rv = NS_GetCurrentThread(getter_AddRefs(thread));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+#endif
+  // To keep us from leaking the runnable if dispatch method fails,
+  // we grab the reference on failures and release it.
+  nsIRunnable* temp = event.get();
+  rv = thread->IdleDispatch(event.forget());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    // Dispatch() leaked the reference to the event, but due to caller's
+    // assumptions, we shouldn't leak here. And given we are on the same
+    // thread as the dispatch target, it's mostly safe to do it here.
+    NS_RELEASE(temp);
+  }
+
+  return rv;
+}
+
 #ifndef XPCOM_GLUE_AVOID_NSPR
 nsresult
 NS_ProcessPendingEvents(nsIThread* aThread, PRIntervalTime aTimeout)
 {
   nsresult rv = NS_OK;
 
 #ifdef MOZILLA_INTERNAL_API
   if (!aThread) {
--- a/xpcom/glue/nsThreadUtils.h
+++ b/xpcom/glue/nsThreadUtils.h
@@ -9,16 +9,18 @@
 
 #include "prthread.h"
 #include "prinrval.h"
 #include "MainThreadUtils.h"
 #include "nsIThreadManager.h"
 #include "nsIThread.h"
 #include "nsIRunnable.h"
 #include "nsICancelableRunnable.h"
+#include "nsIIdlePeriod.h"
+#include "nsIIncrementalRunnable.h"
 #include "nsStringGlue.h"
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/IndexSequence.h"
 #include "mozilla/Likely.h"
 #include "mozilla/Move.h"
 #include "mozilla/Tuple.h"
@@ -125,16 +127,19 @@ NS_DispatchToCurrentThread(already_AddRe
  */
 extern nsresult
 NS_DispatchToMainThread(nsIRunnable* aEvent,
                         uint32_t aDispatchFlags = NS_DISPATCH_NORMAL);
 extern nsresult
 NS_DispatchToMainThread(already_AddRefed<nsIRunnable>&& aEvent,
                         uint32_t aDispatchFlags = NS_DISPATCH_NORMAL);
 
+extern nsresult
+NS_IdleDispatchToCurrentThread(already_AddRefed<nsIRunnable>&& aEvent);
+
 #ifndef XPCOM_GLUE_AVOID_NSPR
 /**
  * Process all pending events for the given thread before returning.  This
  * method simply calls ProcessNextEvent on the thread while HasPendingEvents
  * continues to return true and the time spent in NS_ProcessPendingEvents
  * does not exceed the given timeout value.
  *
  * @param aThread
@@ -218,16 +223,33 @@ extern nsIThread* NS_GetCurrentThread();
 
 //-----------------------------------------------------------------------------
 
 #ifndef XPCOM_GLUE_AVOID_NSPR
 
 namespace mozilla {
 
 // This class is designed to be subclassed.
+class IdlePeriod : public nsIIdlePeriod
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIIDLEPERIOD
+
+  IdlePeriod() {}
+
+protected:
+  virtual ~IdlePeriod() {}
+private:
+  IdlePeriod(const IdlePeriod&) = delete;
+  IdlePeriod& operator=(const IdlePeriod&) = delete;
+  IdlePeriod& operator=(const IdlePeriod&&) = delete;
+};
+
+// This class is designed to be subclassed.
 class Runnable : public nsIRunnable
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIRUNNABLE
 
   Runnable() {}
 
@@ -253,16 +275,35 @@ public:
 protected:
   virtual ~CancelableRunnable() {}
 private:
   CancelableRunnable(const CancelableRunnable&) = delete;
   CancelableRunnable& operator=(const CancelableRunnable&) = delete;
   CancelableRunnable& operator=(const CancelableRunnable&&) = delete;
 };
 
+// This class is designed to be subclassed.
+class IncrementalRunnable : public CancelableRunnable,
+                            public nsIIncrementalRunnable
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  // nsIIncrementalRunnable
+  virtual void SetDeadline(TimeStamp aDeadline) override;
+
+  IncrementalRunnable() {}
+
+protected:
+  virtual ~IncrementalRunnable() {}
+private:
+  IncrementalRunnable(const IncrementalRunnable&) = delete;
+  IncrementalRunnable& operator=(const IncrementalRunnable&) = delete;
+  IncrementalRunnable& operator=(const IncrementalRunnable&&) = delete;
+};
+
 namespace detail {
 
 // An event that can be used to call a C++11 functions or function objects,
 // including lambdas. The function must have no required arguments, and must
 // return void.
 template<typename StoredFunction>
 class RunnableFunction : public Runnable
 {
--- a/xpcom/threads/LazyIdleThread.cpp
+++ b/xpcom/threads/LazyIdleThread.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "LazyIdleThread.h"
 
 #include "nsIObserverService.h"
 
 #include "GeckoProfiler.h"
 #include "nsComponentManagerUtils.h"
+#include "nsIIdlePeriod.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "mozilla/Services.h"
 
 #ifdef DEBUG
 #define ASSERT_OWNING_THREAD()                                                 \
   PR_BEGIN_MACRO                                                               \
     nsIThread* currentThread = NS_GetCurrentThread();                          \
@@ -502,16 +503,28 @@ LazyIdleThread::HasPendingEvents(bool* a
 {
   // This is only supposed to be called from the thread itself so it's not
   // implemented here.
   NS_NOTREACHED("Shouldn't ever call this!");
   return NS_ERROR_UNEXPECTED;
 }
 
 NS_IMETHODIMP
+LazyIdleThread::IdleDispatch(already_AddRefed<nsIRunnable> aEvent)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+LazyIdleThread::RegisterIdlePeriod(already_AddRefed<nsIIdlePeriod> aIdlePeriod)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
 LazyIdleThread::ProcessNextEvent(bool aMayWait,
                                  bool* aEventWasProcessed)
 {
   // This is only supposed to be called from the thread itself so it's not
   // implemented here.
   NS_NOTREACHED("Shouldn't ever call this!");
   return NS_ERROR_UNEXPECTED;
 }
new file mode 100644
--- /dev/null
+++ b/xpcom/threads/MainThreadIdlePeriod.cpp
@@ -0,0 +1,49 @@
+/* -*- 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 "MainThreadIdlePeriod.h"
+
+#include "mozilla/Maybe.h"
+#include "mozilla/Preferences.h"
+#include "nsRefreshDriver.h"
+
+#define DEFAULT_LONG_IDLE_PERIOD 50.0f
+
+namespace mozilla {
+
+NS_IMETHODIMP
+MainThreadIdlePeriod::GetIdlePeriodHint(TimeStamp* aIdleDeadline)
+{
+  MOZ_ASSERT(aIdleDeadline);
+
+  Maybe<TimeStamp> deadline = nsRefreshDriver::GetIdleDeadlineHint();
+
+  if (deadline.isSome()) {
+    *aIdleDeadline = deadline.value();
+  } else {
+    *aIdleDeadline =
+      TimeStamp::Now() + TimeDuration::FromMilliseconds(GetLongIdlePeriod());
+  }
+
+  return NS_OK;
+}
+
+/* static */ float
+MainThreadIdlePeriod::GetLongIdlePeriod()
+{
+  static float sLongIdlePeriod = DEFAULT_LONG_IDLE_PERIOD;
+  static bool sInitialized = false;
+
+  if (!sInitialized) {
+    sInitialized = true;
+    Preferences::AddFloatVarCache(&sLongIdlePeriod, "idle_queue.long_period",
+                                  DEFAULT_LONG_IDLE_PERIOD);
+  }
+
+  return sLongIdlePeriod;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/xpcom/threads/MainThreadIdlePeriod.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_mainthreadidleperiod_h
+#define mozilla_dom_mainthreadidleperiod_h
+
+#include "mozilla/TimeStamp.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+
+class MainThreadIdlePeriod final : public IdlePeriod
+{
+public:
+  NS_DECL_NSIIDLEPERIOD
+
+  static float GetLongIdlePeriod();
+private:
+  virtual ~MainThreadIdlePeriod() {}
+};
+
+} // namespace mozilla
+
+#endif // mozilla_dom_mainthreadidleperiod_h
--- a/xpcom/threads/moz.build
+++ b/xpcom/threads/moz.build
@@ -2,57 +2,61 @@
 # vim: set filetype=python:
 # 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/.
 
 XPIDL_SOURCES += [
     'nsIEnvironment.idl',
     'nsIEventTarget.idl',
+    'nsIIdlePeriod.idl',
     'nsIProcess.idl',
     'nsIRunnable.idl',
     'nsISupportsPriority.idl',
     'nsIThread.idl',
     'nsIThreadInternal.idl',
     'nsIThreadManager.idl',
     'nsIThreadPool.idl',
     'nsITimer.idl',
 ]
 
 XPIDL_MODULE = 'xpcom_threads'
 
 EXPORTS += [
     'nsEventQueue.h',
     'nsICancelableRunnable.h',
+    'nsIIncrementalRunnable.h',
     'nsMemoryPressure.h',
     'nsProcess.h',
     'nsThread.h',
 ]
 
 EXPORTS.mozilla += [
     'AbstractThread.h',
     'BackgroundHangMonitor.h',
     'HangAnnotations.h',
     'HangMonitor.h',
     'LazyIdleThread.h',
+    'MainThreadIdlePeriod.h',
     'MozPromise.h',
     'SharedThreadPool.h',
     'StateMirroring.h',
     'StateWatching.h',
     'SyncRunnable.h',
     'TaskDispatcher.h',
     'TaskQueue.h',
 ]
 
 UNIFIED_SOURCES += [
     'AbstractThread.cpp',
     'BackgroundHangMonitor.cpp',
     'HangAnnotations.cpp',
     'HangMonitor.cpp',
     'LazyIdleThread.cpp',
+    'MainThreadIdlePeriod.cpp',
     'nsEnvironment.cpp',
     'nsEventQueue.cpp',
     'nsMemoryPressure.cpp',
     'nsProcessCommon.cpp',
     'nsThread.cpp',
     'nsThreadManager.cpp',
     'nsThreadPool.cpp',
     'nsTimerImpl.cpp',
new file mode 100644
--- /dev/null
+++ b/xpcom/threads/nsIIdlePeriod.idl
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsISupports.idl"
+
+%{C++
+namespace mozilla {
+class TimeStamp;
+}
+%}
+
+native TimeStamp(mozilla::TimeStamp);
+
+/**
+ * An instance implementing nsIIdlePeriod is used by an associated
+ * nsIThread to estimate when it is likely that it will receive an
+ * event.
+ */
+[builtinclass, uuid(21dd35a2-eae9-4bd8-b470-0dfa35a0e3b9)]
+interface nsIIdlePeriod : nsISupports
+{
+    /**
+     * Return an estimate of a point in time in the future when we
+     * think that the associated thread will become busy. Should
+     * return TimeStamp() (i.e. the null time) or a time less than
+     * TimeStamp::Now() if the thread is currently busy or will become
+     * busy very soon.
+     */
+    TimeStamp getIdlePeriodHint();
+};
new file mode 100644
--- /dev/null
+++ b/xpcom/threads/nsIIncrementalRunnable.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIIncrementalRunnable_h__
+#define nsIIncrementalRunnable_h__
+
+#include "nsISupports.h"
+#include "mozilla/TimeStamp.h"
+
+#define NS_IINCREMENTALRUNNABLE_IID \
+{ 0x688be92e, 0x7ade, 0x4fdc, \
+{ 0x9d, 0x83, 0x74, 0xcb, 0xef, 0xf4, 0xa5, 0x2c } }
+
+
+/**
+ * A task interface for tasks that can schedule their work to happen
+ * in increments bounded by a deadline.
+ */
+class nsIIncrementalRunnable : public nsISupports
+{
+public:
+  NS_DECLARE_STATIC_IID_ACCESSOR(NS_IINCREMENTALRUNNABLE_IID)
+
+  /**
+   * Notify the task of a point in time in the future when the task
+   * should stop executing.
+   */
+  virtual void SetDeadline(mozilla::TimeStamp aDeadline) = 0;
+
+protected:
+  nsIIncrementalRunnable() { }
+  virtual ~nsIIncrementalRunnable() {}
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIIncrementalRunnable,
+                              NS_IINCREMENTALRUNNABLE_IID)
+
+#endif // nsIIncrementalRunnable_h__
--- a/xpcom/threads/nsIThread.idl
+++ b/xpcom/threads/nsIThread.idl
@@ -1,18 +1,25 @@
 /* -*- 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/. */
 
 #include "nsIEventTarget.idl"
+#include "nsIIdlePeriod.idl"
+
+%{C++
+#include "mozilla/AlreadyAddRefed.h"
+%}
 
 [ptr] native PRThread(PRThread);
 
+native alreadyAddRefed_nsIIdlePeriod(already_AddRefed<nsIIdlePeriod>);
+
 /**
  * This interface provides a high-level abstraction for an operating system
  * thread.
  *
  * Threads have a built-in event queue, and a thread is an event target that
  * can receive nsIRunnable objects (events) to be processed on the thread.
  *
  * See nsIThreadManager for the API used to create and locate threads.
@@ -107,9 +114,36 @@ interface nsIThread : nsIEventTarget
    *
    * @throws NS_ERROR_UNEXPECTED
    *   Indicates that this method was erroneously called when this thread was
    *   the current thread, that this thread was not created with a call to
    *   nsIThreadManager::NewThread, or if this method was called more than once
    *   on the thread object.
    */
   void asyncShutdown();
+
+  /**
+   * Register an instance of nsIIdlePeriod which works as a facade of
+   * the abstract notion of a "current idle period". The
+   * nsIIdlePeriod should always represent the "current" idle period
+   * with regard to expected work for the thread. The thread is free
+   * to use this when there are no higher prioritized tasks to process
+   * to determine if it is reasonable to schedule tasks that could run
+   * when the thread is idle. The responsibility of the registered
+   * nsIIdlePeriod is to answer with an estimated deadline at which
+   * the thread should expect that it could receive new higher
+   * priority tasks.
+   */
+  [noscript] void registerIdlePeriod(in alreadyAddRefed_nsIIdlePeriod aIdlePeriod);
+
+  /**
+   * Dispatch an event to the thread's idle queue.  This function may be called
+   * from any thread, and it may be called re-entrantly.
+   *
+   * @param event
+   *   The alreadyAddRefed<> event to dispatch.
+   *   NOTE that the event will be leaked if it fails to dispatch.
+   *
+   * @throws NS_ERROR_INVALID_ARG
+   *   Indicates that event is null.
+   */
+  [noscript] void idleDispatch(in alreadyAddRefed_nsIRunnable event);
 };
--- a/xpcom/threads/nsThread.cpp
+++ b/xpcom/threads/nsThread.cpp
@@ -29,16 +29,18 @@
 #include "mozilla/ipc/MessageChannel.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/Services.h"
 #include "nsXPCOMPrivate.h"
 #include "mozilla/ChaosMode.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Unused.h"
 #include "mozilla/dom/ScriptSettings.h"
+#include "nsIIdlePeriod.h"
+#include "nsIIncrementalRunnable.h"
 #include "nsThreadSyncDispatch.h"
 #include "LeakRefPtr.h"
 
 #ifdef MOZ_CRASHREPORTER
 #include "nsServiceManagerUtils.h"
 #include "nsICrashReporter.h"
 #include "mozilla/dom/ContentChild.h"
 #endif
@@ -589,16 +591,17 @@ nsThread::SaveMemoryReportNearOOM(Should
 int sCanaryOutputFD = -1;
 #endif
 
 nsThread::nsThread(MainThreadFlag aMainThread, uint32_t aStackSize)
   : mLock("nsThread.mLock")
   , mScriptObserver(nullptr)
   , mEvents(WrapNotNull(&mEventsRoot))
   , mEventsRoot(mLock)
+  , mIdleEvents(mLock)
   , mPriority(PRIORITY_NORMAL)
   , mThread(nullptr)
   , mNestedEventLoopDepth(0)
   , mStackSize(aStackSize)
   , mShutdownContext(nullptr)
   , mShutdownRequired(false)
   , mEventsAreDoomed(false)
   , mIsMainThread(aMainThread)
@@ -626,16 +629,18 @@ nsThread::~nsThread()
 nsresult
 nsThread::Init()
 {
   // spawn thread and wait until it is fully setup
   RefPtr<nsThreadStartupEvent> startup = new nsThreadStartupEvent();
 
   NS_ADDREF_THIS();
 
+  mIdlePeriod = new IdlePeriod();
+
   mShutdownRequired = true;
 
   // ThreadFunc is responsible for setting mThread
   if (!PR_CreateThread(PR_USER_THREAD, ThreadFunc, this,
                        PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
                        PR_JOINABLE_THREAD, mStackSize)) {
     NS_RELEASE_THIS();
     return NS_ERROR_OUT_OF_MEMORY;
@@ -656,16 +661,18 @@ nsThread::Init()
 }
 
 nsresult
 nsThread::InitCurrentThread()
 {
   mThread = PR_GetCurrentThread();
   SetupCurrentThreadForChaosMode();
 
+  mIdlePeriod = new IdlePeriod();
+
   nsThreadManager::get().RegisterCurrentThread(*this);
   return NS_OK;
 }
 
 nsresult
 nsThread::PutEvent(nsIRunnable* aEvent, nsNestedEventTarget* aTarget)
 {
   nsCOMPtr<nsIRunnable> event(aEvent);
@@ -955,16 +962,47 @@ nsThread::HasPendingEvents(bool* aResult
 
   {
     MutexAutoLock lock(mLock);
     *aResult = mEvents->HasPendingEvent(lock);
   }
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsThread::RegisterIdlePeriod(already_AddRefed<nsIIdlePeriod> aIdlePeriod)
+{
+  if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
+    return NS_ERROR_NOT_SAME_THREAD;
+  }
+
+  MutexAutoLock lock(mLock);
+  mIdlePeriod = aIdlePeriod;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::IdleDispatch(already_AddRefed<nsIRunnable> aEvent)
+{
+  MutexAutoLock lock(mLock);
+  LeakRefPtr<nsIRunnable> event(Move(aEvent));
+
+  if (NS_WARN_IF(!event)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  if (mEventsAreDoomed) {
+    NS_WARNING("An idle event was posted to a thread that will never run it (rejected)");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  mIdleEvents.PutEvent(event.take(), lock);
+  return NS_OK;
+}
+
 #ifdef MOZ_CANARY
 void canary_alarm_handler(int signum);
 
 class Canary
 {
   //XXX ToDo: support nested loops
 public:
   Canary()
@@ -1007,16 +1045,76 @@ void canary_alarm_handler(int signum)
       nsCOMPtr<nsIThreadObserver> obs_;                                        \
       while (iter_.HasMore()) {                                                \
         obs_ = iter_.GetNext();                                                \
         obs_ -> func_ params_ ;                                                \
       }                                                                        \
     }                                                                          \
   PR_END_MACRO
 
+void
+nsThread::GetIdleEvent(nsIRunnable** aEvent, MutexAutoLock& aProofOfLock)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == mThread);
+  MOZ_ASSERT(aEvent);
+
+  TimeStamp idleDeadline;
+  {
+    mIdlePeriod->GetIdlePeriodHint(&idleDeadline);
+  }
+
+  if (!idleDeadline || idleDeadline < TimeStamp::Now()) {
+    aEvent = nullptr;
+    return;
+  }
+
+  {
+    mIdleEvents.GetEvent(false, aEvent, aProofOfLock);
+  }
+
+  if (*aEvent) {
+    nsCOMPtr<nsIIncrementalRunnable> incrementalEvent(do_QueryInterface(*aEvent));
+    if (incrementalEvent) {
+      incrementalEvent->SetDeadline(idleDeadline);
+    }
+  }
+}
+
+void
+nsThread::GetEvent(bool aWait, nsIRunnable** aEvent, MutexAutoLock& aProofOfLock)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == mThread);
+  MOZ_ASSERT(aEvent);
+
+  // We'll try to get an event to execute in three stages.
+  // [1] First we just try to get it from the regular queue without waiting.
+  {
+    mEvents->GetEvent(false, aEvent, aProofOfLock);
+  }
+
+  // [2] If we didn't get an event from the regular queue, try to
+  // get one from the idle queue
+  if (!*aEvent) {
+    // Since events in mEvents have higher priority than idle
+    // events, we will only consider idle events when there are no
+    // pending events in mEvents. We will for the same reason never
+    // wait for an idle event, since a higher priority event might
+    // appear at any time.
+    GetIdleEvent(aEvent, aProofOfLock);
+  }
+
+  // [3] If we neither got an event from the regular queue nor the
+  // idle queue, then if we should wait for events we block on the
+  // main queue until an event is available.
+  // If we are shutting down, then do not wait for new events.
+  if (!*aEvent && aWait) {
+    mEvents->GetEvent(aWait, aEvent, aProofOfLock);
+  }
+}
+
 NS_IMETHODIMP
 nsThread::ProcessNextEvent(bool aMayWait, bool* aResult)
 {
   LOG(("THRD(%p) ProcessNextEvent [%u %u]\n", this, aMayWait,
        mNestedEventLoopDepth));
 
   if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
     return NS_ERROR_NOT_SAME_THREAD;
@@ -1059,22 +1157,20 @@ nsThread::ProcessNextEvent(bool aMayWait
   Canary canary;
 #endif
   nsresult rv = NS_OK;
 
   {
     // Scope for |event| to make sure that its destructor fires while
     // mNestedEventLoopDepth has been incremented, since that destructor can
     // also do work.
-
-    // If we are shutting down, then do not wait for new events.
     nsCOMPtr<nsIRunnable> event;
     {
       MutexAutoLock lock(mLock);
-      mEvents->GetEvent(reallyWait, getter_AddRefs(event), lock);
+      GetEvent(reallyWait, getter_AddRefs(event), lock);
     }
 
     *aResult = (event.get() != nullptr);
 
     if (event) {
       LOG(("THRD(%p) running [%p]\n", this, event.get()));
       if (MAIN_THREAD == mIsMainThread) {
         HangMonitor::NotifyActivity();
--- a/xpcom/threads/nsThread.h
+++ b/xpcom/threads/nsThread.h
@@ -3,16 +3,17 @@
 /* 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 nsThread_h__
 #define nsThread_h__
 
 #include "mozilla/Mutex.h"
+#include "nsIIdlePeriod.h"
 #include "nsIThreadInternal.h"
 #include "nsISupportsPriority.h"
 #include "nsEventQueue.h"
 #include "nsThreadUtils.h"
 #include "nsString.h"
 #include "nsTObserverArray.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/NotNull.h"
@@ -89,16 +90,20 @@ public:
   };
 
   static bool SaveMemoryReportNearOOM(ShouldSaveMemoryReport aShouldSave);
 #endif
 
 private:
   void DoMainThreadSpecificProcessing(bool aReallyWait);
 
+  void GetIdleEvent(nsIRunnable** aEvent, mozilla::MutexAutoLock& aProofOfLock);
+  void GetEvent(bool aWait, nsIRunnable** aEvent,
+                mozilla::MutexAutoLock& aProofOfLock);
+
 protected:
   class nsChainedEventQueue;
 
   class nsNestedEventTarget;
   friend class nsNestedEventTarget;
 
   friend class nsThreadShutdownEvent;
 
@@ -173,46 +178,56 @@ protected:
   public:
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSIEVENTTARGET
 
     nsNestedEventTarget(NotNull<nsThread*> aThread,
                         NotNull<nsChainedEventQueue*> aQueue)
       : mThread(aThread)
       , mQueue(aQueue)
+
+
+
     {
     }
 
     NotNull<RefPtr<nsThread>> mThread;
 
     // This is protected by mThread->mLock.
     nsChainedEventQueue* mQueue;
 
   private:
     ~nsNestedEventTarget()
     {
     }
   };
 
-  // This lock protects access to mObserver, mEvents and mEventsAreDoomed.
+  // This lock protects access to mObserver, mEvents, mIdleEvents,
   // All of those fields are only modified on the thread itself (never from
   // another thread).  This means that we can avoid holding the lock while
   // using mObserver and mEvents on the thread itself.  When calling PutEvent
   // on mEvents, we have to hold the lock to synchronize with PopEventQueue.
   mozilla::Mutex mLock;
 
   nsCOMPtr<nsIThreadObserver> mObserver;
   mozilla::CycleCollectedJSContext* mScriptObserver;
 
   // Only accessed on the target thread.
   nsAutoTObserverArray<NotNull<nsCOMPtr<nsIThreadObserver>>, 2> mEventObservers;
 
   NotNull<nsChainedEventQueue*> mEvents;  // never null
   nsChainedEventQueue mEventsRoot;
 
+  // mIdlePeriod keeps track of the current idle period. If at any
+  // time the main event queue is empty, calling
+  // mIdlePeriod->GetIdlePeriodHint() will give an estimate of when
+  // the current idle period will end.
+  nsCOMPtr<nsIIdlePeriod> mIdlePeriod;
+  nsEventQueue mIdleEvents;
+
   int32_t   mPriority;
   PRThread* mThread;
   uint32_t  mNestedEventLoopDepth;
   uint32_t  mStackSize;
 
   // The shutdown context for ourselves.
   struct nsThreadShutdownContext* mShutdownContext;
   // The shutdown contexts for any other threads we've asked to shut down.
--- a/xpcom/threads/nsThreadManager.cpp
+++ b/xpcom/threads/nsThreadManager.cpp
@@ -11,16 +11,18 @@
 #include "nsTArray.h"
 #include "nsAutoPtr.h"
 #include "mozilla/ThreadLocal.h"
 #ifdef MOZ_CANARY
 #include <fcntl.h>
 #include <unistd.h>
 #endif
 
+#include "MainThreadIdlePeriod.h"
+
 using namespace mozilla;
 
 static MOZ_THREAD_LOCAL(bool) sTLSIsMainThread;
 
 bool
 NS_IsMainThread()
 {
   return sTLSIsMainThread.get();
@@ -94,16 +96,21 @@ nsThreadManager::Init()
   mMainThread = new nsThread(nsThread::MAIN_THREAD, 0);
 
   nsresult rv = mMainThread->InitCurrentThread();
   if (NS_FAILED(rv)) {
     mMainThread = nullptr;
     return rv;
   }
 
+  {
+    nsCOMPtr<nsIIdlePeriod> idlePeriod = new MainThreadIdlePeriod();
+    mMainThread->RegisterIdlePeriod(idlePeriod.forget());
+  }
+
   // We need to keep a pointer to the current thread, so we can satisfy
   // GetIsMainThread calls that occur post-Shutdown.
   mMainThread->GetPRThread(&mMainPRThread);
 
   mInitialized = true;
   return NS_OK;
 }