Merge autoland to m-c, a=merge
authorPhil Ringnalda <philringnalda@gmail.com>
Sun, 30 Oct 2016 10:53:33 -0700
changeset 320183 6a79c696da8bd7210cd4354293e1f9929f8dc1e8
parent 320153 f4f6435ed714df693381b80184f26f297ec1a461 (current diff)
parent 320182 141c1a49b1b19bac1a23fc24d2843cc218b930a3 (diff)
child 320205 e3279760cd977aac30bd9e8032d3ee71f55d2a67
push id20751
push userphilringnalda@gmail.com
push dateSun, 30 Oct 2016 18:06:35 +0000
treeherderfx-team@e3279760cd97 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone52.0a1
Merge autoland to m-c, a=merge
--- a/accessible/tests/browser/.eslintrc.js
+++ b/accessible/tests/browser/.eslintrc.js
@@ -46,17 +46,16 @@ module.exports = { // eslint-disable-lin
     "waitForEvent": true,
     "waitForMultipleEvents": true,
     "invokeSetAttribute": true,
     "invokeSetStyle": true,
     "invokeFocus": true,
     "findAccessibleChildByID": true
   },
   "rules": {
-    "mozilla/mark-test-function-used": 1,
     "mozilla/no-aArgs": 1,
     "mozilla/no-cpows-in-tests": 1,
     "mozilla/reject-importGlobalProperties": 1,
     "mozilla/var-only-at-top-level": 1,
 
     "block-scoped-var": 2,
     "brace-style": [2, "1tbs"],
     "camelcase": 2,
--- a/browser/components/migration/tests/unit/test_fx_telemetry.js
+++ b/browser/components/migration/tests/unit/test_fx_telemetry.js
@@ -1,16 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /* globals do_get_tempdir */
 
 "use strict";
 
-/* exported run_test */
 function run_test() {
   run_next_test();
 }
 
 function readFile(file) {
   let stream = Cc["@mozilla.org/network/file-input-stream;1"]
                .createInstance(Ci.nsIFileInputStream);
   stream.init(file, -1, -1, Ci.nsIFileInputStream.CLOSE_ON_EOF);
--- a/browser/components/newtab/tests/xpcshell/test_NewTabPrefsProvider.js
+++ b/browser/components/newtab/tests/xpcshell/test_NewTabPrefsProvider.js
@@ -1,12 +1,11 @@
 "use strict";
 
 /* global XPCOMUtils, equal, Preferences, NewTabPrefsProvider, run_next_test */
-/* exported run_test */
 /* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
 
 const Cu = Components.utils;
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
     "resource:///modules/NewTabPrefsProvider.jsm");
--- a/browser/components/newtab/tests/xpcshell/test_NewTabSearchProvider.js
+++ b/browser/components/newtab/tests/xpcshell/test_NewTabSearchProvider.js
@@ -1,12 +1,11 @@
 "use strict";
 
 /* global XPCOMUtils, NewTabSearchProvider, run_next_test, ok, equal, do_check_true, do_get_profile, Services */
-/* exported run_test */
 /* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
 
 const Cu = Components.utils;
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabSearchProvider",
     "resource:///modules/NewTabSearchProvider.jsm");
@@ -76,9 +75,8 @@ add_task(function* test_search() {
   let expectedEngineName = Services.search.currentEngine.name;
 
   // emitter should fire and return the new engine
   let [eventName, actualEngineName] = yield promise;
   equal(eventName, "browser-search-engine-modified", `emitter sent the correct event ${eventName}`);
   equal(expectedEngineName, actualEngineName, `emitter set the correct engine ${expectedEngineName}`);
   NewTabSearchProvider.search.uninit();
 });
-
--- a/browser/components/newtab/tests/xpcshell/test_PlacesProvider.js
+++ b/browser/components/newtab/tests/xpcshell/test_PlacesProvider.js
@@ -1,14 +1,13 @@
 "use strict";
 
 /* global XPCOMUtils, PlacesUtils, PlacesTestUtils, PlacesProvider, NetUtil */
 /* global do_get_profile, run_next_test, add_task */
 /* global equal, ok */
-/* exported run_test */
 
 const {
   utils: Cu,
   interfaces: Ci,
 } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
--- a/browser/components/preferences/cookies.js
+++ b/browser/components/preferences/cookies.js
@@ -122,20 +122,20 @@ var gCookiesWindow = {
     // We don't yet handle aData == "deleted" - it's a less common case
     // and is rather complicated as selection tracking is difficult
   },
 
   _handleCookieChanged: function (changedCookie, strippedHost) {
     var rowIndex = 0;
     var cookieItem = null;
     if (!this._view._filtered) {
-      for (let host of this._hostsOrder) { // (var host in this._hosts) {
+      for (let host of this._hostOrder) {
         ++rowIndex;
         var hostItem = this._hosts[host];
-        if (this._hostOrder[i] == strippedHost) { // host == strippedHost) {
+        if (host == strippedHost) {
           // Host matches, look for the cookie within this Host collection
           // and update its data
           for (let currCookie of hostItem.cookies) {
             ++rowIndex;
             if (this._cookieEquals(currCookie, changedCookie, strippedHost)) {
               currCookie.value    = changedCookie.value;
               currCookie.isSecure = changedCookie.isSecure;
               currCookie.isDomain = changedCookie.isDomain;
@@ -883,17 +883,17 @@ var gCookiesWindow = {
     for (let i = 0; i < rangeCount; ++i) {
       var min = {}; var max = {};
       seln.getRangeAt(i, min, max);
       this._lastSelectedRanges.push({ min: min.value, max: max.value });
     }
 
     // Save open states
     this._openIndices = [];
-    for (i = 0; i < this._view.rowCount; ++i) {
+    for (let i = 0; i < this._view.rowCount; ++i) {
       var item = this._view._getItemAtIndex(i);
       if (item && item.container && item.open)
         this._openIndices.push(i);
     }
   },
 
   _updateRemoveAllButton: function gCookiesWindow__updateRemoveAllButton() {
     document.getElementById("removeAllCookies").disabled = this._view._rowCount == 0;
--- a/browser/experiments/test/xpcshell/.eslintrc.js
+++ b/browser/experiments/test/xpcshell/.eslintrc.js
@@ -3,13 +3,13 @@
 module.exports = {
   "extends": [
     "../../../../testing/xpcshell/xpcshell.eslintrc.js"
   ],
 
   "rules": {
     "no-unused-vars": ["error", {
       "vars": "all",
-      "varsIgnorePattern": "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS|run_test)$",
+      "varsIgnorePattern": "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS)$",
       "args": "none"
     }]
   }
 };
--- a/browser/extensions/formautofill/.eslintrc.js
+++ b/browser/extensions/formautofill/.eslintrc.js
@@ -8,17 +8,16 @@ module.exports = { // eslint-disable-lin
     "dump": true,
     "TextDecoder": false,
     "TextEncoder": false,
   },
 
   "rules": {
     // Rules from the mozilla plugin
     "mozilla/balanced-listeners": 2,
-    "mozilla/mark-test-function-used": 1,
     "mozilla/no-aArgs": 1,
     "mozilla/no-cpows-in-tests": 1,
     "mozilla/var-only-at-top-level": 1,
 
     "valid-jsdoc": [2, {
       "prefer": {
         "return": "returns",
       },
--- a/devtools/.eslintrc.js
+++ b/devtools/.eslintrc.js
@@ -32,17 +32,16 @@ module.exports = {
     "WebSocket": true,
     "XMLHttpRequest": true,
   },
   "rules": {
     // These are the rules that have been configured so far to match the
     // devtools coding style.
 
     // Rules from the mozilla plugin
-    "mozilla/mark-test-function-used": 1,
     "mozilla/no-aArgs": 1,
     "mozilla/no-cpows-in-tests": 2,
     "mozilla/no-single-arg-cu-import": 2,
     // See bug 1224289.
     "mozilla/reject-importGlobalProperties": 2,
     // devtools/shared/platform is special; see the README.md in that
     // directory for details.  We reject requires using explicit
     // subdirectories of this directory.
--- a/devtools/.eslintrc.xpcshell.js
+++ b/devtools/.eslintrc.xpcshell.js
@@ -8,14 +8,12 @@ module.exports = {
     "camelcase": 0,
     // Allow using undefined variables so that tests can refer to functions
     // and variables defined in head.js files, without having to maintain a
     // list of globals in each .eslintrc file.
     // Note that bug 1168340 will eventually help auto-registering globals
     // from head.js files.
     "no-undef": 0,
     "block-scoped-var": 0,
-    // Allow run_test to be unused in xpcshell
-    "no-unused-vars": [2, { "varsIgnorePattern": "run_test" }],
     // Tests can always import anything.
     "mozilla/reject-some-requires": 0,
   }
 };
new file mode 100644
--- /dev/null
+++ b/dom/base/IdleDeadline.cpp
@@ -0,0 +1,70 @@
+/* -*- 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 <algorithm>
+
+#include "mozilla/dom/IdleDeadline.h"
+#include "mozilla/dom/IdleDeadlineBinding.h"
+#include "mozilla/dom/Performance.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsPIDOMWindow.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(IdleDeadline, mWindow)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(IdleDeadline)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(IdleDeadline)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleDeadline)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+IdleDeadline::IdleDeadline(nsPIDOMWindowInner* aWindow, bool aDidTimeout,
+                           DOMHighResTimeStamp aDeadline)
+  : mWindow(aWindow)
+  , mDidTimeout(aDidTimeout)
+  , mDeadline(aDeadline)
+{
+}
+
+IdleDeadline::~IdleDeadline()
+{
+}
+
+JSObject*
+IdleDeadline::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return IdleDeadlineBinding::Wrap(aCx, this, aGivenProto);
+}
+
+DOMHighResTimeStamp
+IdleDeadline::TimeRemaining()
+{
+  if (mDidTimeout) {
+    return 0.0;
+  }
+
+  RefPtr<Performance> performance = mWindow->GetPerformance();
+  if (!performance) {
+    // If there is no performance object the window is partially torn
+    // down, so we can safely say that there is no time remaining.
+    return 0.0;
+  }
+
+  return std::max(mDeadline - performance->Now(), 0.0);
+}
+
+bool
+IdleDeadline::DidTimeout() const
+{
+  return mDidTimeout;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/base/IdleDeadline.h
@@ -0,0 +1,55 @@
+/* -*- 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_IdleDeadline_h
+#define mozilla_dom_IdleDeadline_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsWrapperCache.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+namespace dom {
+
+class IdleDeadline final
+  : public nsISupports
+  , public nsWrapperCache
+{
+public:
+  IdleDeadline(nsPIDOMWindowInner* aWindow, bool aDidTimeout,
+               DOMHighResTimeStamp aDeadline);
+
+  nsPIDOMWindowInner* GetParentObject() const { return mWindow; }
+
+  virtual JSObject* WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aGivenProto) override;
+
+  DOMHighResTimeStamp TimeRemaining();
+  bool DidTimeout() const;
+
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(IdleDeadline)
+
+private:
+  ~IdleDeadline();
+
+  nsCOMPtr<nsPIDOMWindowInner> mWindow;
+  const bool mDidTimeout;
+  const DOMHighResTimeStamp mDeadline;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_IdleDeadline_h
new file mode 100644
--- /dev/null
+++ b/dom/base/IdleRequest.cpp
@@ -0,0 +1,157 @@
+/* -*- 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 "IdleRequest.h"
+
+#include "mozilla/Function.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/dom/IdleDeadline.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/PerformanceTiming.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()
+{
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(IdleRequest)
+
+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)
+{
+  int32_t handle;
+  nsresult rv = nsGlobalWindow::Cast(mWindow)->SetTimeoutOrInterval(
+    this, aTimeout, false, Timeout::Reason::eIdleCallbackTimeout, &handle);
+  mTimeoutHandle = Some(handle);
+
+  return rv;
+}
+
+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)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!aDidTimeout) {
+    CancelTimeout();
+  }
+
+  remove();
+  ErrorResult error;
+  RefPtr<IdleDeadline> deadline =
+    new IdleDeadline(mWindow, aDidTimeout, mDeadline);
+  mCallback->Call(*deadline, error, "requestIdleCallback handler");
+  mCallback = nullptr;
+  Release();
+
+  return error.StealNSResult();
+}
+
+void
+IdleRequest::CancelTimeout()
+{
+  if (mTimeoutHandle.isSome()) {
+    nsGlobalWindow::Cast(mWindow)->ClearTimeoutOrInterval(
+      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
new file mode 100644
--- /dev/null
+++ b/dom/base/IdleRequest.h
@@ -0,0 +1,74 @@
+/* -*- 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_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>
+{
+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;
+
+  nsresult SetTimeout(uint32_t aTimout);
+  nsresult RunIdleRequestCallback(bool aDidTimeout);
+  void CancelTimeout();
+
+  NS_DECL_NSIRUNNABLE;
+  virtual nsresult Cancel() override;
+  virtual void SetDeadline(mozilla::TimeStamp aDeadline) override;
+
+  uint32_t Handle() const
+  {
+    return mHandle;
+  }
+
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(IdleRequest, nsITimeoutHandler)
+
+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
new file mode 100644
--- /dev/null
+++ b/dom/base/Timeout.cpp
@@ -0,0 +1,91 @@
+/* -*- 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 "Timeout.h"
+
+#include "nsGlobalWindow.h"
+#include "nsITimeoutHandler.h"
+#include "nsITimer.h"
+#include "nsPIDOMWindow.h"
+
+namespace mozilla {
+namespace dom {
+
+Timeout::Timeout()
+  : mCleared(false),
+    mRunning(false),
+    mIsInterval(false),
+    mReason(Reason::eTimeoutOrInterval),
+    mTimeoutId(0),
+    mInterval(0),
+    mFiringDepth(0),
+    mNestingLevel(0),
+    mPopupState(openAllowed)
+{
+  MOZ_COUNT_CTOR(Timeout);
+}
+
+Timeout::~Timeout()
+{
+  if (mTimer) {
+    mTimer->Cancel();
+    mTimer = nullptr;
+  }
+
+  MOZ_COUNT_DTOR(Timeout);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(Timeout)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Timeout)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mScriptHandler)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Timeout)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptHandler)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(Timeout, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(Timeout, Release)
+
+nsresult
+Timeout::InitTimer(uint32_t aDelay)
+{
+  return mTimer->InitWithNameableFuncCallback(
+    nsGlobalWindow::TimerCallback, this, aDelay,
+    nsITimer::TYPE_ONE_SHOT, Timeout::TimerNameCallback);
+}
+
+// static
+void
+Timeout::TimerNameCallback(nsITimer* aTimer, void* aClosure, char* aBuf,
+                           size_t aLen)
+{
+  RefPtr<Timeout> timeout = (Timeout*)aClosure;
+
+  const char* filename;
+  uint32_t lineNum, column;
+  timeout->mScriptHandler->GetLocation(&filename, &lineNum, &column);
+  snprintf(aBuf, aLen, "[content] %s:%u:%u", filename, lineNum, column);
+}
+
+
+// Return true if this timeout has a refcount of 1. This is used to check
+// that dummy_timeout doesn't leak from nsGlobalWindow::RunTimeout.
+#ifdef DEBUG
+bool
+Timeout::HasRefCntOne() const
+{
+  return mRefCnt.get() == 1;
+}
+#endif // DEBUG
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/base/Timeout.h
@@ -0,0 +1,101 @@
+/* -*- 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_h
+#define mozilla_dom_timeout_h
+
+#include "mozilla/LinkedList.h"
+#include "mozilla/TimeStamp.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsGlobalWindow;
+class nsIPrincipal;
+class nsITimeoutHandler;
+class nsITimer;
+
+namespace mozilla {
+namespace dom {
+
+/*
+ * Timeout struct that holds information about each script
+ * timeout.  Holds a strong reference to an nsITimeoutHandler, which
+ * abstracts the language specific cruft.
+ */
+class Timeout final
+  : public LinkedListElement<Timeout>
+{
+public:
+  Timeout();
+
+  NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(Timeout)
+  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(Timeout)
+
+  nsresult InitTimer(uint32_t aDelay);
+
+  enum class Reason { eTimeoutOrInterval, eIdleCallbackTimeout };
+
+  static void TimerNameCallback(nsITimer* aTimer, void* aClosure, char* aBuf,
+                                size_t aLen);
+
+#ifdef DEBUG
+  bool HasRefCntOne() const;
+#endif // DEBUG
+
+  // Window for which this timeout fires
+  RefPtr<nsGlobalWindow> mWindow;
+
+  // The actual timer object
+  nsCOMPtr<nsITimer> mTimer;
+
+  // True if the timeout was cleared
+  bool mCleared;
+
+  // True if this is one of the timeouts that are currently running
+  bool mRunning;
+
+  // True if this is a repeating/interval timer
+  bool mIsInterval;
+
+  Reason mReason;
+
+  // Returned as value of setTimeout()
+  uint32_t mTimeoutId;
+
+  // Interval in milliseconds
+  uint32_t mInterval;
+
+  // mWhen and mTimeRemaining can't be in a union, sadly, because they
+  // have constructors.
+  // Nominal time to run this timeout.  Use only when timeouts are not
+  // suspended.
+  TimeStamp mWhen;
+  // Remaining time to wait.  Used only when timeouts are suspended.
+  TimeDuration mTimeRemaining;
+
+  // Principal with which to execute
+  nsCOMPtr<nsIPrincipal> mPrincipal;
+
+  // stack depth at which timeout is firing
+  uint32_t mFiringDepth;
+
+  uint32_t mNestingLevel;
+
+  // The popup state at timeout creation time if not created from
+  // another timeout
+  PopupControlState mPopupState;
+
+  // The language-specific information about the callback.
+  nsCOMPtr<nsITimeoutHandler> mScriptHandler;
+
+private:
+  ~Timeout();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_timeout_h
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -97,16 +97,17 @@ EXPORTS += [
     'nsINodeList.h',
     'nsIScriptContext.h',
     'nsIScriptElement.h',
     'nsIScriptGlobalObject.h',
     'nsIScriptNameSpaceManager.h',
     'nsIScriptObjectPrincipal.h',
     'nsIScriptTimeoutHandler.h',
     'nsIStyleSheetLinkingElement.h',
+    'nsITimeoutHandler.h',
     'nsJSEnvironment.h',
     'nsJSUtils.h',
     'nsLineBreaker.h',
     'nsMappedAttributeElement.h',
     'nsNameSpaceManager.h',
     'nsNodeInfoManager.h',
     'nsNodeUtils.h',
     'nsPIDOMWindow.h',
@@ -179,16 +180,18 @@ EXPORTS.mozilla.dom += [
     'EventSource.h',
     'File.h',
     'FileList.h',
     'FileReader.h',
     'FormData.h',
     'FragmentOrElement.h',
     'FromParser.h',
     'GroupedSHistory.h',
+    'IdleDeadline.h',
+    'IdleRequest.h',
     'ImageEncoder.h',
     'ImageTracker.h',
     'ImportManager.h',
     'Link.h',
     'Location.h',
     'MutableBlobStorage.h',
     'MutableBlobStreamListener.h',
     'NameSpaceConstants.h',
@@ -203,16 +206,17 @@ EXPORTS.mozilla.dom += [
     'ScreenOrientation.h',
     'ScriptSettings.h',
     'ShadowRoot.h',
     'StructuredCloneHolder.h',
     'StructuredCloneTags.h',
     'StyleSheetList.h',
     'SubtleCrypto.h',
     'Text.h',
+    'Timeout.h',
     'TreeWalker.h',
     'WebKitCSSMatrix.h',
     'WebSocket.h',
     'WindowOrientationObserver.h',
 ]
 
 UNIFIED_SOURCES += [
     'AnonymousContent.cpp',
@@ -245,16 +249,18 @@ UNIFIED_SOURCES += [
     'Element.cpp',
     'EventSource.cpp',
     'File.cpp',
     'FileList.cpp',
     'FileReader.cpp',
     'FormData.cpp',
     'FragmentOrElement.cpp',
     'GroupedSHistory.cpp',
+    'IdleDeadline.cpp',
+    'IdleRequest.cpp',
     'ImageEncoder.cpp',
     'ImageTracker.cpp',
     'ImportManager.cpp',
     'Link.cpp',
     'Location.cpp',
     'MultipartBlobImpl.cpp',
     'MutableBlobStorage.cpp',
     'MutableBlobStreamListener.cpp',
@@ -345,16 +351,17 @@ UNIFIED_SOURCES += [
     'ScriptSettings.cpp',
     'ShadowRoot.cpp',
     'StructuredCloneHolder.cpp',
     'StyleSheetList.cpp',
     'SubtleCrypto.cpp',
     'Text.cpp',
     'TextInputProcessor.cpp',
     'ThirdPartyUtil.cpp',
+    'Timeout.cpp',
     'TreeWalker.cpp',
     'WebKitCSSMatrix.cpp',
     'WebSocket.cpp',
     'WindowNamedPropertiesHandler.cpp',
     'WindowOrientationObserver.cpp',
 ]
 
 if CONFIG['MOZ_WEBRTC']:
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -13,19 +13,21 @@
 // Local Includes
 #include "Navigator.h"
 #include "nsContentSecurityManager.h"
 #include "nsScreen.h"
 #include "nsHistory.h"
 #include "nsDOMNavigationTiming.h"
 #include "nsIDOMStorageManager.h"
 #include "mozilla/dom/DOMStorage.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/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"
 #include "nsISizeOfEventTarget.h"
@@ -35,16 +37,17 @@
 #include "nsDOMWindowList.h"
 #include "mozilla/dom/WakeLock.h"
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "nsIDocShellTreeOwner.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIPermissionManager.h"
 #include "nsIScriptContext.h"
 #include "nsIScriptTimeoutHandler.h"
+#include "nsITimeoutHandler.h"
 #include "nsIController.h"
 #include "nsScriptNameSpaceManager.h"
 #include "nsISlowScriptDebug.h"
 #include "nsWindowMemoryReporter.h"
 #include "WindowNamedPropertiesHandler.h"
 #include "nsFrameSelection.h"
 #include "nsNetUtil.h"
 #include "nsVariant.h"
@@ -320,16 +323,21 @@ nsGlobalWindow::DOMMinTimeoutValue() con
 // uses 5.
 #define DOM_CLAMP_TIMEOUT_NESTING_LEVEL 5
 
 // The longest interval (as PRIntervalTime) we permit, or that our
 // timer code can handle, really. See DELAY_INTERVAL_LIMIT in
 // nsTimerImpl.h for details.
 #define DOM_MAX_TIMEOUT_VALUE    DELAY_INTERVAL_LIMIT
 
+// The interval at which we execute idle callbacks
+static uint32_t gThrottledIdlePeriodLength;
+
+#define DEFAULT_THROTTLED_IDLE_PERIOD_LENGTH 10000
+
 #define FORWARD_TO_OUTER(method, args, err_rval)                              \
   PR_BEGIN_MACRO                                                              \
   if (IsInnerWindow()) {                                                      \
     nsGlobalWindow *outer = GetOuterWindowInternal();                         \
     if (!AsInner()->HasActiveDocument()) {                                    \
       NS_WARNING(outer ?                                                      \
                  "Inner window does not have active document." :              \
                  "No outer window available!");                               \
@@ -498,81 +506,16 @@ private:
 
   // This reference is non-owning and safe because it's cleared by
   // nsGlobalWindow::CleanUp().
   nsGlobalWindow* MOZ_NON_OWNING_REF mWindow;
 };
 
 NS_IMPL_ISUPPORTS(nsGlobalWindowObserver, nsIObserver, nsIInterfaceRequestor)
 
-nsTimeout::nsTimeout()
-  : mCleared(false),
-    mRunning(false),
-    mIsInterval(false),
-    mPublicId(0),
-    mInterval(0),
-    mFiringDepth(0),
-    mNestingLevel(0),
-    mPopupState(openAllowed)
-{
-#ifdef DEBUG_jst
-  {
-    extern int gTimeoutCnt;
-
-    ++gTimeoutCnt;
-  }
-#endif
-
-  MOZ_COUNT_CTOR(nsTimeout);
-}
-
-nsTimeout::~nsTimeout()
-{
-#ifdef DEBUG_jst
-  {
-    extern int gTimeoutCnt;
-
-    --gTimeoutCnt;
-  }
-#endif
-
-  if (mTimer) {
-    mTimer->Cancel();
-    mTimer = nullptr;
-  }
-
-  MOZ_COUNT_DTOR(nsTimeout);
-}
-
-NS_IMPL_CYCLE_COLLECTION_CLASS(nsTimeout)
-
-NS_IMPL_CYCLE_COLLECTION_UNLINK_0(nsTimeout)
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsTimeout)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptHandler)
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
-NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsTimeout, AddRef)
-NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsTimeout, Release)
-
-nsresult
-nsTimeout::InitTimer(uint32_t aDelay)
-{
-  return mTimer->InitWithNameableFuncCallback(
-    nsGlobalWindow::TimerCallback, this, aDelay,
-    nsITimer::TYPE_ONE_SHOT, nsGlobalWindow::TimerNameCallback);
-}
-
-// Return true if this timeout has a refcount of 1. This is used to check
-// that dummy_timeout doesn't leak from nsGlobalWindow::RunTimeout.
-bool
-nsTimeout::HasRefCntOne()
-{
-  return mRefCnt.get() == 1;
-}
-
 static already_AddRefed<nsIVariant>
 CreateVoidVariant()
 {
   RefPtr<nsVariantCC> writable = new nsVariantCC();
   writable->SetAsVoid();
   return writable.forget();
 }
 
@@ -598,16 +541,119 @@ DialogValueHolder::Get(JSContext* aCx, J
   if (aSubject->Subsumes(mOrigin)) {
     aError = nsContentUtils::XPConnect()->VariantToJS(aCx, aScope,
                                                       mValue, aResult);
   } else {
     aResult.setUndefined();
   }
 }
 
+void
+nsGlobalWindow::PostThrottledIdleCallback()
+{
+  AssertIsOnMainThread();
+
+  if (mThrottledIdleRequestCallbacks.isEmpty())
+    return;
+
+  RefPtr<IdleRequest> request(mThrottledIdleRequestCallbacks.popFirst());
+  // ownership transferred from mThrottledIdleRequestCallbacks to
+  // mIdleRequestCallbacks
+  mIdleRequestCallbacks.insertBack(request);
+  NS_IdleDispatchToCurrentThread(request.forget());
+}
+
+/* static */ void
+nsGlobalWindow::InsertIdleCallbackIntoList(IdleRequest* aRequest,
+                                           IdleRequests& aList)
+{
+  aList.insertBack(aRequest);
+  aRequest->AddRef();
+}
+
+uint32_t
+nsGlobalWindow::RequestIdleCallback(JSContext* aCx,
+                                    IdleRequestCallback& aCallback,
+                                    const IdleRequestOptions& aOptions,
+                                    ErrorResult& aError)
+{
+  MOZ_RELEASE_ASSERT(IsInnerWindow());
+  AssertIsOnMainThread();
+
+  uint32_t handle = ++mIdleRequestCallbackCounter;
+
+  RefPtr<IdleRequest> request =
+    new IdleRequest(aCx, AsInner(), aCallback, handle);
+
+  if (aOptions.mTimeout.WasPassed()) {
+    aError = request->SetTimeout(aOptions.mTimeout.Value());
+    if (NS_WARN_IF(aError.Failed())) {
+      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());
+  }
+
+  return handle;
+}
+
+void
+nsGlobalWindow::CancelIdleCallback(uint32_t aHandle)
+{
+  MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+  for (IdleRequest* r : mIdleRequestCallbacks) {
+    if (r->Handle() == aHandle) {
+      r->Cancel();
+      break;
+    }
+  }
+}
+
+void
+nsGlobalWindow::DisableIdleCallbackRequests()
+{
+  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());
+  }
+}
+
+
 namespace mozilla {
 namespace dom {
 extern uint64_t
 NextWindowID();
 } // namespace dom
 } // namespace mozilla
 
 template<class T>
@@ -1206,22 +1252,24 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalW
     mHasGamepad(false),
     mHasVREvents(false),
 #ifdef MOZ_GAMEPAD
     mHasSeenGamepadInput(false),
 #endif
     mNotifiedIDDestroyed(false),
     mAllowScriptsToClose(false),
     mTimeoutInsertionPoint(nullptr),
-    mTimeoutPublicIdCounter(1),
+    mTimeoutIdCounter(1),
     mTimeoutFiringDepth(0),
     mSuspendDepth(0),
     mFreezeDepth(0),
     mFocusMethod(0),
     mSerial(0),
+    mIdleCallbackTimeoutCounter(1),
+    mIdleRequestCallbackCounter(1),
 #ifdef DEBUG
     mSetOpenerWindowCalled(false),
 #endif
 #ifdef MOZ_B2G
     mNetworkUploadObserverEnabled(false),
     mNetworkDownloadObserverEnabled(false),
 #endif
     mCleanedUp(false),
@@ -1277,16 +1325,20 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalW
                                 "dom.min_timeout_value",
                                 DEFAULT_MIN_TIMEOUT_VALUE);
     Preferences::AddIntVarCache(&gMinBackgroundTimeoutValue,
                                 "dom.min_background_timeout_value",
                                 DEFAULT_MIN_BACKGROUND_TIMEOUT_VALUE);
     Preferences::AddBoolVarCache(&sIdleObserversAPIFuzzTimeDisabled,
                                  "dom.idle-observers-api.fuzz_time.disabled",
                                  false);
+
+    Preferences::AddUintVarCache(&gThrottledIdlePeriodLength,
+                                 "dom.idle_period.throttled_length",
+                                 DEFAULT_THROTTLED_IDLE_PERIOD_LENGTH);
   }
 
   if (gDumpFile == nullptr) {
     const nsAdoptingCString& fname =
       Preferences::GetCString("browser.dom.window.dump.file");
     if (!fname.IsEmpty()) {
       // if this fails to open, Dump() knows to just go to stdout
       // on null.
@@ -1623,16 +1675,17 @@ nsGlobalWindow::CleanUp()
   if (IsInnerWindow()) {
     DisableGamepadUpdates();
     mHasGamepad = false;
     DisableVRUpdates();
     mHasVREvents = false;
 #ifdef MOZ_B2G
     DisableTimeChangeNotifications();
 #endif
+    DisableIdleCallbackRequests();
   } else {
     MOZ_ASSERT(!mHasGamepad);
     MOZ_ASSERT(!mHasVREvents);
   }
 
   if (mCleanMessageManager) {
     MOZ_ASSERT(mIsChrome, "only chrome should have msg manager cleaned");
     nsGlobalChromeWindow *asChrome = static_cast<nsGlobalChromeWindow*>(this);
@@ -1702,16 +1755,18 @@ nsGlobalWindow::FreeInnerObjects()
 
   if (mIdleTimer) {
     mIdleTimer->Cancel();
     mIdleTimer = nullptr;
   }
 
   mIdleObservers.Clear();
 
+  DisableIdleCallbackRequests();
+
   mChromeEventHandler = nullptr;
 
   if (mListenerManager) {
     mListenerManager->Disconnect();
     mListenerManager = nullptr;
   }
 
   mLocation = nullptr;
@@ -1891,36 +1946,45 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
 #ifdef MOZ_WEBSPEECH
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSpeechSynthesis)
 #endif
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOuterWindow)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
 
-  for (nsTimeout* timeout = tmp->mTimeouts.getFirst();
+  for (Timeout* timeout = tmp->mTimeouts.getFirst();
        timeout;
        timeout = timeout->getNext()) {
-    cb.NoteNativeChild(timeout, NS_CYCLE_COLLECTION_PARTICIPANT(nsTimeout));
+    cb.NoteNativeChild(timeout, NS_CYCLE_COLLECTION_PARTICIPANT(Timeout));
   }
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocation)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHistory)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCustomElements)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalStorage)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessionStorage)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplicationCache)
   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)
+
+  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)
 
 #ifdef MOZ_GAMEPAD
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGamepads)
 #endif
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCacheStorage)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVRDisplays)
@@ -1971,16 +2035,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
     nsGlobalWindow::Cast(tmp->mOuterWindow)->MaybeClearInnerWindow(tmp);
     NS_IMPL_CYCLE_COLLECTION_UNLINK(mOuterWindow)
   }
 
   if (tmp->mListenerManager) {
     tmp->mListenerManager->Disconnect();
     NS_IMPL_CYCLE_COLLECTION_UNLINK(mListenerManager)
   }
+
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocation)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mHistory)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mCustomElements)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalStorage)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSessionStorage)
   if (tmp->mApplicationCache) {
     static_cast<nsDOMOfflineResourceList*>(tmp->mApplicationCache.get())->Disconnect();
     NS_IMPL_CYCLE_COLLECTION_UNLINK(mApplicationCache)
@@ -2016,16 +2081,18 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mCrypto)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mU2F)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsole)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mExternal)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mMozSelfSupport)
 
   tmp->UnlinkHostObjectURIs();
 
+  tmp->DisableIdleCallbackRequests();
+
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 #ifdef DEBUG
 void
 nsGlobalWindow::RiskyUnlink()
 {
   NS_CYCLE_COLLECTION_INNERNAME.Unlink(this);
@@ -2054,24 +2121,21 @@ nsGlobalWindow::IsBlackForCC(bool aTraci
           IsBlack()) &&
          (!aTracingNeeded ||
           HasNothingToTrace(static_cast<nsIDOMEventTarget*>(this)));
 }
 
 void
 nsGlobalWindow::UnmarkGrayTimers()
 {
-  for (nsTimeout* timeout = mTimeouts.getFirst();
+  for (Timeout* timeout = mTimeouts.getFirst();
        timeout;
        timeout = timeout->getNext()) {
     if (timeout->mScriptHandler) {
-      Function* f = timeout->mScriptHandler->GetCallback();
-      if (f) {
-        f->MarkForCC();
-      }
+      timeout->mScriptHandler->MarkForCC();
     }
   }
 }
 
 //*****************************************************************************
 // nsGlobalWindow::nsIScriptGlobalObject
 //*****************************************************************************
 
@@ -7953,25 +8017,25 @@ nsGlobalWindow::MozRequestOverfill(Overf
 }
 
 void
 nsGlobalWindow::ClearTimeout(int32_t aHandle)
 {
   MOZ_RELEASE_ASSERT(IsInnerWindow());
 
   if (aHandle > 0) {
-    ClearTimeoutOrInterval(aHandle);
+    ClearTimeoutOrInterval(aHandle, Timeout::Reason::eTimeoutOrInterval);
   }
 }
 
 void
 nsGlobalWindow::ClearInterval(int32_t aHandle)
 {
   if (aHandle > 0) {
-    ClearTimeoutOrInterval(aHandle);
+    ClearTimeoutOrInterval(aHandle, Timeout::Reason::eTimeoutOrInterval);
   }
 }
 
 void
 nsGlobalWindow::SetResizable(bool aResizable) const
 {
   // nop
 }
@@ -9915,16 +9979,23 @@ void nsGlobalWindow::SetIsBackground(boo
 {
   MOZ_ASSERT(IsOuterWindow());
 
   bool resetTimers = (!aIsBackground && AsOuter()->IsBackground());
   nsPIDOMWindow::SetIsBackground(aIsBackground);
   if (resetTimers) {
     ResetTimersForNonBackgroundWindow();
   }
+
+  if (!aIsBackground) {
+    nsGlobalWindow* inner = GetCurrentInnerWindowInternal();
+    if (inner) {
+      inner->UnthrottleIdleCallbackRequests();
+    }
+  }
 #ifdef MOZ_GAMEPAD
   if (!aIsBackground) {
     nsGlobalWindow* inner = GetCurrentInnerWindowInternal();
     if (inner) {
       inner->SyncGamepadState();
     }
   }
 #endif
@@ -11700,17 +11771,17 @@ nsGlobalWindow::Suspend()
     for (uint32_t i = 0; i < mEnabledSensors.Length(); i++)
       ac->RemoveWindowListener(mEnabledSensors[i], this);
   }
   DisableGamepadUpdates();
   DisableVRUpdates();
 
   mozilla::dom::workers::SuspendWorkersForWindow(AsInner());
 
-  for (nsTimeout* t = mTimeouts.getFirst(); t; t = t->getNext()) {
+  for (Timeout* t = mTimeouts.getFirst(); t; t = t->getNext()) {
     // Leave the timers with the current time remaining.  This will
     // cause the timers to potentially fire when the window is
     // Resume()'d.  Time effectively passes while suspended.
 
     // Drop the XPCOM timer; we'll reschedule when restoring the state.
     if (t->mTimer) {
       t->mTimer->Cancel();
       t->mTimer = nullptr;
@@ -11759,17 +11830,17 @@ nsGlobalWindow::Resume()
   for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) {
     ErrorResult dummy;
     RefPtr<Promise> d = mAudioContexts[i]->Resume(dummy);
   }
 
   TimeStamp now = TimeStamp::Now();
   DebugOnly<bool> _seenDummyTimeout = false;
 
-  for (nsTimeout* t = mTimeouts.getFirst(); t; t = t->getNext()) {
+  for (Timeout* t = mTimeouts.getFirst(); t; t = t->getNext()) {
     // There's a chance we're being called with RunTimeout on the stack in which
     // case we have a dummy timeout in the list that *must not* be resumed. It
     // can be identified by a null mWindow.
     if (!t->mWindow) {
       NS_ASSERTION(!_seenDummyTimeout, "More than one dummy timeout?!");
       _seenDummyTimeout = true;
       continue;
     }
@@ -11846,17 +11917,17 @@ nsGlobalWindow::FreezeInternal()
   MOZ_ASSERT(mSuspendDepth >= mFreezeDepth);
   if (mFreezeDepth != 1) {
     return;
   }
 
   mozilla::dom::workers::FreezeWorkersForWindow(AsInner());
 
   TimeStamp now = TimeStamp::Now();
-  for (nsTimeout *t = mTimeouts.getFirst(); t; t = t->getNext()) {
+  for (Timeout *t = mTimeouts.getFirst(); t; t = t->getNext()) {
     // Save the current remaining time for this timeout.  We will
     // re-apply it when the window is Thaw()'d.  This effectively
     // shifts timers to the right as if time does not pass while
     // the window is frozen.
     if (t->mWhen > now) {
       t->mTimeRemaining = t->mWhen - now;
     } else {
       t->mTimeRemaining = TimeDuration(0);
@@ -11892,17 +11963,17 @@ nsGlobalWindow::ThawInternal()
   MOZ_ASSERT(mSuspendDepth >= mFreezeDepth);
   if (mFreezeDepth != 0) {
     return;
   }
 
   TimeStamp now = TimeStamp::Now();
   DebugOnly<bool> _seenDummyTimeout = false;
 
-  for (nsTimeout *t = mTimeouts.getFirst(); t; t = t->getNext()) {
+  for (Timeout *t = mTimeouts.getFirst(); t; t = t->getNext()) {
     // There's a chance we're being called with RunTimeout on the stack in which
     // case we have a dummy timeout in the list that *must not* be resumed. It
     // can be identified by a null mWindow.
     if (!t->mWindow) {
       NS_ASSERTION(!_seenDummyTimeout, "More than one dummy timeout?!");
       _seenDummyTimeout = true;
       continue;
     }
@@ -12323,16 +12394,28 @@ nsGlobalWindow::OpenInternal(const nsASt
 }
 
 //*****************************************************************************
 // nsGlobalWindow: Timeout Functions
 //*****************************************************************************
 
 uint32_t sNestingLevel;
 
+uint32_t
+nsGlobalWindow::GetTimeoutId(Timeout::Reason aReason)
+{
+  switch (aReason) {
+    case Timeout::Reason::eIdleCallbackTimeout:
+      return ++mIdleCallbackTimeoutCounter;
+    case Timeout::Reason::eTimeoutOrInterval:
+    default:
+      return ++mTimeoutIdCounter;
+  }
+}
+
 nsGlobalWindow*
 nsGlobalWindow::InnerForSetTimeoutOrInterval(ErrorResult& aError)
 {
   nsGlobalWindow* currentInner;
   nsGlobalWindow* forwardTo;
   if (IsInnerWindow()) {
     nsGlobalWindow* outer = GetOuterWindowInternal();
     currentInner = outer ? outer->GetCurrentInnerWindowInternal() : this;
@@ -12430,19 +12513,19 @@ nsGlobalWindow::SetInterval(JSContext* a
                             ErrorResult& aError)
 {
   int32_t timeout;
   bool isInterval = IsInterval(aTimeout, timeout);
   return SetTimeoutOrInterval(aCx, aHandler, timeout, isInterval, aError);
 }
 
 nsresult
-nsGlobalWindow::SetTimeoutOrInterval(nsIScriptTimeoutHandler *aHandler,
-                                     int32_t interval,
-                                     bool aIsInterval, int32_t *aReturn)
+nsGlobalWindow::SetTimeoutOrInterval(nsITimeoutHandler* aHandler,
+                                     int32_t interval, bool aIsInterval,
+                                     Timeout::Reason aReason, int32_t* aReturn)
 {
   MOZ_ASSERT(IsInnerWindow());
 
   // If we don't have a document (we could have been unloaded since
   // the call to setTimeout was made), do nothing.
   if (!mDoc) {
     return NS_OK;
   }
@@ -12454,20 +12537,21 @@ nsGlobalWindow::SetTimeoutOrInterval(nsI
   // Make sure we don't proceed with an interval larger than our timer
   // code can handle. (Note: we already forced |interval| to be non-negative,
   // so the uint32_t cast (to avoid compiler warnings) is ok.)
   uint32_t maxTimeoutMs = PR_IntervalToMilliseconds(DOM_MAX_TIMEOUT_VALUE);
   if (static_cast<uint32_t>(interval) > maxTimeoutMs) {
     interval = maxTimeoutMs;
   }
 
-  RefPtr<nsTimeout> timeout = new nsTimeout();
+  RefPtr<Timeout> timeout = new Timeout();
   timeout->mIsInterval = aIsInterval;
   timeout->mInterval = interval;
   timeout->mScriptHandler = aHandler;
+  timeout->mReason = aReason;
 
   // Now clamp the actual interval we will use for the timer based on
   uint32_t nestingLevel = sNestingLevel + 1;
   uint32_t realInterval = interval;
   if (aIsInterval || nestingLevel >= DOM_CLAMP_TIMEOUT_NESTING_LEVEL) {
     // Don't allow timeouts less than DOMMinTimeoutValue() from
     // now...
     realInterval = std::max(realInterval, uint32_t(DOMMinTimeoutValue()));
@@ -12493,17 +12577,17 @@ nsGlobalWindow::SetTimeoutOrInterval(nsI
     MOZ_ASSERT(!timeout->mWhen.IsNull());
 
     nsresult rv;
     timeout->mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
     if (NS_FAILED(rv)) {
       return rv;
     }
 
-    RefPtr<nsTimeout> copy = timeout;
+    RefPtr<Timeout> copy = timeout;
 
     rv = timeout->InitTimer(realInterval);
     if (NS_FAILED(rv)) {
       return rv;
     }
 
     // The timeout is now also held in the timer's closure.
     Unused << copy.forget();
@@ -12532,21 +12616,20 @@ nsGlobalWindow::SetTimeoutOrInterval(nsI
     // in some cases.
     if (interval <= delay) {
       timeout->mPopupState = gPopupControlState;
     }
   }
 
   InsertTimeoutIntoList(timeout);
 
-  timeout->mPublicId = ++mTimeoutPublicIdCounter;
-  *aReturn = timeout->mPublicId;
+  timeout->mTimeoutId = GetTimeoutId(aReason);
+  *aReturn = timeout->mTimeoutId;
 
   return NS_OK;
-
 }
 
 int32_t
 nsGlobalWindow::SetTimeoutOrInterval(JSContext *aCx, Function& aFunction,
                                      int32_t aTimeout,
                                      const Sequence<JS::Value>& aArguments,
                                      bool aIsInterval, ErrorResult& aError)
 {
@@ -12562,17 +12645,18 @@ nsGlobalWindow::SetTimeoutOrInterval(JSC
 
   nsCOMPtr<nsIScriptTimeoutHandler> handler =
     NS_CreateJSTimeoutHandler(aCx, this, aFunction, aArguments, aError);
   if (!handler) {
     return 0;
   }
 
   int32_t result;
-  aError = SetTimeoutOrInterval(handler, aTimeout, aIsInterval, &result);
+  aError = SetTimeoutOrInterval(handler, aTimeout, aIsInterval,
+                                Timeout::Reason::eTimeoutOrInterval, &result);
   return result;
 }
 
 int32_t
 nsGlobalWindow::SetTimeoutOrInterval(JSContext* aCx, const nsAString& aHandler,
                                      int32_t aTimeout, bool aIsInterval,
                                      ErrorResult& aError)
 {
@@ -12588,28 +12672,29 @@ nsGlobalWindow::SetTimeoutOrInterval(JSC
 
   nsCOMPtr<nsIScriptTimeoutHandler> handler =
     NS_CreateJSTimeoutHandler(aCx, this, aHandler, aError);
   if (!handler) {
     return 0;
   }
 
   int32_t result;
-  aError = SetTimeoutOrInterval(handler, aTimeout, aIsInterval, &result);
+  aError = SetTimeoutOrInterval(handler, aTimeout, aIsInterval,
+                                Timeout::Reason::eTimeoutOrInterval, &result);
   return result;
 }
 
 bool
-nsGlobalWindow::RunTimeoutHandler(nsTimeout* aTimeout,
+nsGlobalWindow::RunTimeoutHandler(Timeout* aTimeout,
                                   nsIScriptContext* aScx)
 {
   // Hold on to the timeout in case mExpr or mFunObj releases its
   // doc.
-  RefPtr<nsTimeout> timeout = aTimeout;
-  nsTimeout* last_running_timeout = mRunningTimeout;
+  RefPtr<Timeout> timeout = aTimeout;
+  Timeout* last_running_timeout = mRunningTimeout;
   mRunningTimeout = timeout;
   timeout->mRunning = true;
 
   // Push this timeout's popup control state, which should only be
   // eabled the first time a timeout fires that was created while
   // popups were enabled and with a delay less than
   // "dom.disable_open_click_delay".
   nsAutoPopupStatePusher popupStatePusher(timeout->mPopupState);
@@ -12631,49 +12716,57 @@ nsGlobalWindow::RunTimeoutHandler(nsTime
   const char *reason;
   if (timeout->mIsInterval) {
     reason = "setInterval handler";
   } else {
     reason = "setTimeout handler";
   }
 
   bool abortIntervalHandler = false;
-  nsCOMPtr<nsIScriptTimeoutHandler> handler(timeout->mScriptHandler);
-  RefPtr<Function> callback = handler->GetCallback();
-  if (!callback) {
-    // Evaluate the timeout expression.
-    const nsAString& script = handler->GetHandlerText();
-
-    const char* filename = nullptr;
-    uint32_t lineNo = 0, dummyColumn = 0;
-    handler->GetLocation(&filename, &lineNo, &dummyColumn);
-
-    // New script entry point required, due to the "Create a script" sub-step of
-    // http://www.whatwg.org/specs/web-apps/current-work/#timer-initialisation-steps
-    nsAutoMicroTask mt;
-    AutoEntryScript aes(this, reason, true);
-    JS::CompileOptions options(aes.cx());
-    options.setFileAndLine(filename, lineNo)
-           .setVersion(JSVERSION_DEFAULT);
-    JS::Rooted<JSObject*> global(aes.cx(), FastGetGlobalJSObject());
-    nsresult rv = nsJSUtils::EvaluateString(aes.cx(), script, global, options);
-    if (rv == NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE) {
-      abortIntervalHandler = true;
+  nsCOMPtr<nsIScriptTimeoutHandler> handler(do_QueryInterface(timeout->mScriptHandler));
+  if (handler) {
+    RefPtr<Function> callback = handler->GetCallback();
+
+    if (!callback) {
+      // Evaluate the timeout expression.
+      const nsAString& script = handler->GetHandlerText();
+
+      const char* filename = nullptr;
+      uint32_t lineNo = 0, dummyColumn = 0;
+      handler->GetLocation(&filename, &lineNo, &dummyColumn);
+
+      // New script entry point required, due to the "Create a script" sub-step of
+      // http://www.whatwg.org/specs/web-apps/current-work/#timer-initialisation-steps
+      nsAutoMicroTask mt;
+      AutoEntryScript aes(this, reason, true);
+      JS::CompileOptions options(aes.cx());
+      options.setFileAndLine(filename, lineNo).setVersion(JSVERSION_DEFAULT);
+      JS::Rooted<JSObject*> global(aes.cx(), FastGetGlobalJSObject());
+      nsresult rv =
+        nsJSUtils::EvaluateString(aes.cx(), script, global, options);
+      if (rv == NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE) {
+        abortIntervalHandler = true;
+      }
+    } else {
+      // Hold strong ref to ourselves while we call the callback.
+      nsCOMPtr<nsISupports> me(static_cast<nsIDOMWindow*>(this));
+      ErrorResult rv;
+      JS::Rooted<JS::Value> ignoredVal(RootingCx());
+      callback->Call(me, handler->GetArgs(), &ignoredVal, rv, reason);
+      if (rv.IsUncatchableException()) {
+        abortIntervalHandler = true;
+      }
+
+      rv.SuppressException();
     }
   } else {
-    // Hold strong ref to ourselves while we call the callback.
-    nsCOMPtr<nsISupports> me(static_cast<nsIDOMWindow *>(this));
-    ErrorResult rv;
-    JS::Rooted<JS::Value> ignoredVal(RootingCx());
-    callback->Call(me, handler->GetArgs(), &ignoredVal, rv, reason);
-    if (rv.IsUncatchableException()) {
-      abortIntervalHandler = true;
-    }
-
-    rv.SuppressException();
+    nsCOMPtr<nsITimeoutHandler> basicHandler(timeout->mScriptHandler);
+    nsCOMPtr<nsISupports> kungFuDeathGrip(static_cast<nsIDOMWindow*>(this));
+    mozilla::Unused << kungFuDeathGrip;
+    basicHandler->Call();
   }
 
   // If we received an uncatchable exception, do not schedule the timeout again.
   // This allows the slow script dialog to break easy DoS attacks like
   // setInterval(function() { while(1); }, 100);
   if (abortIntervalHandler) {
     // If it wasn't an interval timer to begin with, this does nothing.  If it
     // was, we'll treat it as a timeout that we just ran and discard it when
@@ -12705,17 +12798,17 @@ nsGlobalWindow::RunTimeoutHandler(nsTime
 
   mRunningTimeout = last_running_timeout;
   timeout->mRunning = false;
 
   return timeout->mCleared;
 }
 
 bool
-nsGlobalWindow::RescheduleTimeout(nsTimeout* aTimeout, const TimeStamp& now,
+nsGlobalWindow::RescheduleTimeout(Timeout* aTimeout, const TimeStamp& now,
                                   bool aRunningPendingTimeouts)
 {
   if (!aTimeout->mIsInterval) {
     if (aTimeout->mTimer) {
       // The timeout still has an OS timer, and it's not an interval,
       // that means that the OS timer could still fire; cancel the OS
       // timer and release its reference to the timeout.
       aTimeout->mTimer->Cancel();
@@ -12784,27 +12877,28 @@ nsGlobalWindow::RescheduleTimeout(nsTime
 
     return false;
   }
 
   return true;
 }
 
 void
-nsGlobalWindow::RunTimeout(nsTimeout *aTimeout)
+nsGlobalWindow::RunTimeout(Timeout* aTimeout)
 {
   if (IsSuspended()) {
     return;
   }
 
   NS_ASSERTION(IsInnerWindow(), "Timeout running on outer window!");
   NS_ASSERTION(!IsFrozen(), "Timeout running on a window in the bfcache!");
 
-  nsTimeout *nextTimeout;
-  nsTimeout *last_expired_timeout, *last_insertion_point;
+  Timeout* nextTimeout;
+  Timeout* last_expired_timeout;
+  Timeout* last_insertion_point;
   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(this);
 
   // A native timer has gone off. See which of our timeouts need
   // servicing
@@ -12825,17 +12919,17 @@ nsGlobalWindow::RunTimeout(nsTimeout *aT
 
   // 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->mWhen
   // if the timer fired early.  So we can stop walking if we get to timeouts
   // whose mWhen is greater than deadline, since once that happens we know
   // nothing past that point is expired.
   last_expired_timeout = nullptr;
-  for (nsTimeout *timeout = mTimeouts.getFirst();
+  for (Timeout* timeout = mTimeouts.getFirst();
        timeout && timeout->mWhen <= deadline;
        timeout = timeout->getNext()) {
     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 = timeout;
     }
@@ -12855,28 +12949,28 @@ nsGlobalWindow::RunTimeout(nsTimeout *aT
     gLastRecordedRecentTimeouts = now;
   }
 
   // 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<nsTimeout> dummy_timeout = new nsTimeout();
+  RefPtr<Timeout> dummy_timeout = new Timeout();
   dummy_timeout->mFiringDepth = firingDepth;
   dummy_timeout->mWhen = now;
   last_expired_timeout->setNext(dummy_timeout);
-  RefPtr<nsTimeout> timeoutExtraRef(dummy_timeout);
+  RefPtr<Timeout> timeoutExtraRef(dummy_timeout);
 
   last_insertion_point = mTimeoutInsertionPoint;
   // If we ever start setting mTimeoutInsertionPoint to a non-dummy timeout,
   // the logic in ResetTimersForNonBackgroundWindow will need to change.
   mTimeoutInsertionPoint = dummy_timeout;
 
-  for (nsTimeout *timeout = mTimeouts.getFirst();
+  for (Timeout* timeout = mTimeouts.getFirst();
        timeout != dummy_timeout && !IsFrozen();
        timeout = nextTimeout) {
     nextTimeout = timeout->getNext();
 
     if (timeout->mFiringDepth != firingDepth) {
       // We skip the timeout since it's on the list to run at another
       // depth.
 
@@ -12943,25 +13037,25 @@ nsGlobalWindow::RunTimeout(nsTimeout *aT
   dummy_timeout->remove();
   timeoutExtraRef = nullptr;
   MOZ_ASSERT(dummy_timeout->HasRefCntOne(), "dummy_timeout may leak");
 
   mTimeoutInsertionPoint = last_insertion_point;
 }
 
 void
-nsGlobalWindow::ClearTimeoutOrInterval(int32_t aTimerID)
+nsGlobalWindow::ClearTimeoutOrInterval(int32_t aTimerId, Timeout::Reason aReason)
 {
   MOZ_RELEASE_ASSERT(IsInnerWindow());
 
-  uint32_t public_id = (uint32_t)aTimerID;
-  nsTimeout *timeout;
+  uint32_t timerId = (uint32_t)aTimerId;
+  Timeout* timeout;
 
   for (timeout = mTimeouts.getFirst(); timeout; timeout = timeout->getNext()) {
-    if (timeout->mPublicId == public_id) {
+    if (timeout->mTimeoutId == timerId && timeout->mReason == aReason) {
       if (timeout->mRunning) {
         /* We're running from inside the timeout. Mark this
            timeout for deferred deletion by the code in
            RunTimeout() */
         timeout->mIsInterval = false;
       }
       else {
         /* Delete the timeout from the pending timeout list */
@@ -12992,17 +13086,17 @@ nsresult nsGlobalWindow::ResetTimersForN
 
   // If mTimeoutInsertionPoint is non-null, we're in the middle of firing
   // timers and the timers we're planning to fire all come before
   // mTimeoutInsertionPoint; mTimeoutInsertionPoint itself is a dummy timeout
   // with an mWhen that may be semi-bogus.  In that case, we don't need to do
   // anything with mTimeoutInsertionPoint or anything before it, so should
   // start at the timer after mTimeoutInsertionPoint, if there is one.
   // Otherwise, start at the beginning of the list.
-  for (nsTimeout *timeout = mTimeoutInsertionPoint ?
+  for (Timeout* timeout = mTimeoutInsertionPoint ?
          mTimeoutInsertionPoint->getNext() : mTimeouts.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->mWhen below.
     if (timeout->mWhen <= now) {
       timeout = timeout->getNext();
       continue;
@@ -13036,17 +13130,17 @@ nsresult nsGlobalWindow::ResetTimersForN
       TimeDuration delay = firingTime - now;
       timeout->mWhen = firingTime;
 
       // Since we reset mWhen we need to move |timeout| to the right
       // place in the list so that it remains sorted by mWhen.
 
       // Get the pointer to the next timeout now, before we move the
       // current timeout in the list.
-      nsTimeout* nextTimeout = timeout->getNext();
+      Timeout* nextTimeout = timeout->getNext();
 
       // It is safe to remove and re-insert because mWhen is now
       // strictly smaller than it used to be, so we know we'll insert
       // |timeout| before nextTimeout.
       NS_ASSERTION(!nextTimeout ||
                    timeout->mWhen < nextTimeout->mWhen, "How did that happen?");
       timeout->remove();
       // InsertTimeoutIntoList will addref |timeout| and reset
@@ -13070,17 +13164,18 @@ nsresult nsGlobalWindow::ResetTimersForN
   }
 
   return NS_OK;
 }
 
 void
 nsGlobalWindow::ClearAllTimeouts()
 {
-  nsTimeout *timeout, *nextTimeout;
+  Timeout* timeout;
+  Timeout* nextTimeout;
 
   for (timeout = mTimeouts.getFirst(); timeout; timeout = nextTimeout) {
     /* If RunTimeout() is higher up on the stack for this
        window, e.g. as a result of document.write from a timeout,
        then we need to reset the list insertion point for
        newly-created timeouts in case the user adds a timeout,
        before we pop the stack back to RunTimeout. */
     if (mRunningTimeout == timeout)
@@ -13105,25 +13200,25 @@ nsGlobalWindow::ClearAllTimeouts()
     timeout->Release();
   }
 
   // Clear out our list
   mTimeouts.clear();
 }
 
 void
-nsGlobalWindow::InsertTimeoutIntoList(nsTimeout *aTimeout)
+nsGlobalWindow::InsertTimeoutIntoList(Timeout* aTimeout)
 {
   NS_ASSERTION(IsInnerWindow(),
                "InsertTimeoutIntoList() called on outer window!");
 
   // Start at mLastTimeout and go backwards.  Don't go further than
   // mTimeoutInsertionPoint, though.  This optimizes for the common case of
   // insertion at the end.
-  nsTimeout* prevSibling;
+  Timeout* prevSibling;
   for (prevSibling = mTimeouts.getLast();
        prevSibling && prevSibling != mTimeoutInsertionPoint &&
          // This condition needs to match the one in SetTimeoutOrInterval that
          // determines whether to set mWhen or mTimeRemaining.
          (IsFrozen() ?
           prevSibling->mTimeRemaining > aTimeout->mTimeRemaining :
           prevSibling->mWhen > aTimeout->mWhen);
        prevSibling = prevSibling->getPrevious()) {
@@ -13143,34 +13238,21 @@ nsGlobalWindow::InsertTimeoutIntoList(ns
   // by the list
   aTimeout->AddRef();
 }
 
 // static
 void
 nsGlobalWindow::TimerCallback(nsITimer *aTimer, void *aClosure)
 {
-  RefPtr<nsTimeout> timeout = (nsTimeout *)aClosure;
+  RefPtr<Timeout> timeout = (Timeout*)aClosure;
 
   timeout->mWindow->RunTimeout(timeout);
 }
 
-// static
-void
-nsGlobalWindow::TimerNameCallback(nsITimer* aTimer, void* aClosure, char* aBuf,
-                                  size_t aLen)
-{
-  RefPtr<nsTimeout> timeout = (nsTimeout*)aClosure;
-
-  const char* filename;
-  uint32_t lineNum, column;
-  timeout->mScriptHandler->GetLocation(&filename, &lineNum, &column);
-  snprintf(aBuf, aLen, "[content] %s:%u:%u", filename, lineNum, column);
-}
-
 //*****************************************************************************
 // nsGlobalWindow: Helper Functions
 //*****************************************************************************
 
 already_AddRefed<nsIDocShellTreeOwner>
 nsGlobalWindow::GetTreeOwner()
 {
   FORWARD_TO_OUTER(GetTreeOwner, (), nullptr);
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -51,16 +51,17 @@
 #include "nsIDocument.h"
 #include "mozilla/dom/EventTarget.h"
 #include "mozilla/dom/WindowBinding.h"
 #include "Units.h"
 #include "nsComponentManagerUtils.h"
 #include "nsSize.h"
 #include "nsCheapSets.h"
 #include "mozilla/dom/ImageBitmapSource.h"
+#include "mozilla/dom/Timeout.h"
 
 #define DEFAULT_HOME_PAGE "www.mozilla.org"
 #define PREF_BROWSER_STARTUP_HOMEPAGE "browser.startup.homepage"
 
 // Amount of time allowed between alert/prompt/confirm before enabling
 // the stop dialog checkbox.
 #define DEFAULT_SUCCESSIVE_DIALOG_TIME_LIMIT 3 // 3 sec
 
@@ -80,16 +81,17 @@ class nsIContent;
 class nsICSSDeclaration;
 class nsIDocShellTreeOwner;
 class nsIDOMOfflineResourceList;
 class nsIScrollableFrame;
 class nsIControllers;
 class nsIJSID;
 class nsIScriptContext;
 class nsIScriptTimeoutHandler;
+class nsITimeoutHandler;
 class nsIWebBrowserChrome;
 class mozIDOMWindowProxy;
 
 class nsDOMWindowList;
 class nsScreen;
 class nsHistory;
 class nsGlobalWindowObserver;
 class nsGlobalWindow;
@@ -107,28 +109,31 @@ struct ChannelPixelLayout;
 class Console;
 class Crypto;
 class CustomElementRegistry;
 class DocGroup;
 class External;
 class Function;
 class Gamepad;
 enum class ImageBitmapFormat : uint32_t;
+class IdleRequest;
+class IdleRequestCallback;
 class Location;
 class MediaQueryList;
 class MozSelfSupport;
 class Navigator;
 class OwningExternalOrWindowProxy;
 class Promise;
 class PostMessageEvent;
 struct RequestInit;
 class RequestOrUSVString;
 class Selection;
 class SpeechSynthesis;
 class TabGroup;
+class Timeout;
 class U2F;
 class VRDisplay;
 class VREventObserver;
 class WakeLock;
 #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
 class WindowOrientationObserver;
 #endif
 namespace cache {
@@ -146,79 +151,16 @@ NS_CreateJSTimeoutHandler(JSContext* aCx
 
 extern already_AddRefed<nsIScriptTimeoutHandler>
 NS_CreateJSTimeoutHandler(JSContext* aCx, nsGlobalWindow *aWindow,
                           const nsAString& aExpression,
                           mozilla::ErrorResult& aError);
 
 extern const js::Class OuterWindowProxyClass;
 
-/*
- * Timeout struct that holds information about each script
- * timeout.  Holds a strong reference to an nsIScriptTimeoutHandler, which
- * abstracts the language specific cruft.
- */
-struct nsTimeout final
-  : mozilla::LinkedListElement<nsTimeout>
-{
-private:
-  ~nsTimeout();
-
-public:
-  nsTimeout();
-
-  NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(nsTimeout)
-  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsTimeout)
-
-  nsresult InitTimer(uint32_t aDelay);
-
-  bool HasRefCntOne();
-
-  // Window for which this timeout fires
-  RefPtr<nsGlobalWindow> mWindow;
-
-  // The actual timer object
-  nsCOMPtr<nsITimer> mTimer;
-
-  // True if the timeout was cleared
-  bool mCleared;
-
-  // True if this is one of the timeouts that are currently running
-  bool mRunning;
-
-  // True if this is a repeating/interval timer
-  bool mIsInterval;
-
-  // Returned as value of setTimeout()
-  uint32_t mPublicId;
-
-  // Interval in milliseconds
-  uint32_t mInterval;
-
-  // mWhen and mTimeRemaining can't be in a union, sadly, because they
-  // have constructors.
-  // Nominal time to run this timeout.  Use only when timeouts are not
-  // suspended.
-  mozilla::TimeStamp mWhen;
-  // Remaining time to wait.  Used only when timeouts are suspended.
-  mozilla::TimeDuration mTimeRemaining;
-
-  // stack depth at which timeout is firing
-  uint32_t mFiringDepth;
-
-  uint32_t mNestingLevel;
-
-  // The popup state at timeout creation time if not created from
-  // another timeout
-  PopupControlState mPopupState;
-
-  // The language-specific information about the callback.
-  nsCOMPtr<nsIScriptTimeoutHandler> mScriptHandler;
-};
-
 struct IdleObserverHolder
 {
   nsCOMPtr<nsIIdleObserver> mIdleObserver;
   uint32_t mTimeInS;
   bool mPrevNotificationIdle;
 
   IdleObserverHolder()
     : mTimeInS(0), mPrevNotificationIdle(false)
@@ -806,16 +748,17 @@ public:
 
   // Inner windows only.
   // Enable/disable updates for VR
   void EnableVRUpdates();
   void DisableVRUpdates();
 
   // Update the VR displays for this window
   bool UpdateVRDisplays(nsTArray<RefPtr<mozilla::dom::VRDisplay>>& aDisplays);
+
   // Inner windows only.
   // Called to inform that the set of active VR displays has changed.
   void NotifyActiveVRDisplaysChanged();
 
 #define EVENT(name_, id_, type_, struct_)                                     \
   mozilla::dom::EventHandlerNonNull* GetOn##name_()                           \
   {                                                                           \
     mozilla::EventListenerManager* elm = GetExistingListenerManager();        \
@@ -1122,16 +1065,24 @@ public:
                      mozilla::ErrorResult& aError);
   void GetOuterHeight(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
                       mozilla::ErrorResult& aError);
   void SetOuterHeight(JSContext* aCx, JS::Handle<JS::Value> aValue,
                       mozilla::ErrorResult& aError);
   int32_t RequestAnimationFrame(mozilla::dom::FrameRequestCallback& aCallback,
                                 mozilla::ErrorResult& aError);
   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,
@@ -1293,17 +1244,16 @@ public:
   void GetInterface(JSContext* aCx, nsIJSID* aIID,
                     JS::MutableHandle<JS::Value> aRetval,
                     mozilla::ErrorResult& aError);
 
   already_AddRefed<nsWindowRoot> GetWindowRootOuter();
   already_AddRefed<nsWindowRoot> GetWindowRoot(mozilla::ErrorResult& aError);
 
   mozilla::dom::Performance* GetPerformance();
-
 protected:
   // Web IDL helpers
 
   // Redefine the property called aPropName on this window object to be a value
   // property with the value aValue, much like we would do for a [Replaceable]
   // property in IDL.
   void RedefineProperty(JSContext* aCx, const char* aPropName,
                         JS::Handle<JS::Value> aValue,
@@ -1504,48 +1454,49 @@ private:
 
   void FreezeInternal();
   void ThawInternal();
 
 public:
   // Timeout Functions
   // Language agnostic timeout function (all args passed).
   // |interval| is in milliseconds.
-  nsresult SetTimeoutOrInterval(nsIScriptTimeoutHandler *aHandler,
-                                int32_t interval,
-                                bool aIsInterval, int32_t* aReturn);
+  nsresult SetTimeoutOrInterval(nsITimeoutHandler* aHandler,
+                                int32_t interval, bool aIsInterval,
+                                mozilla::dom::Timeout::Reason aReason,
+                                int32_t* aReturn);
   int32_t SetTimeoutOrInterval(JSContext* aCx,
                                mozilla::dom::Function& aFunction,
                                int32_t aTimeout,
                                const mozilla::dom::Sequence<JS::Value>& aArguments,
                                bool aIsInterval, mozilla::ErrorResult& aError);
   int32_t SetTimeoutOrInterval(JSContext* aCx, const nsAString& aHandler,
                                int32_t aTimeout, bool aIsInterval,
                                mozilla::ErrorResult& aError);
-  void ClearTimeoutOrInterval(int32_t aTimerID);
+  void ClearTimeoutOrInterval(int32_t aTimerId,
+                              mozilla::dom::Timeout::Reason aReason);
 
   // JS specific timeout functions (JS args grabbed from context).
   nsresult ResetTimersForNonBackgroundWindow();
 
   // The timeout implementation functions.
-  void RunTimeout(nsTimeout *aTimeout);
+  void RunTimeout(mozilla::dom::Timeout* aTimeout);
   void RunTimeout() { RunTimeout(nullptr); }
   // Return true if |aTimeout| was cleared while its handler ran.
-  bool RunTimeoutHandler(nsTimeout* aTimeout, nsIScriptContext* aScx);
+  bool RunTimeoutHandler(mozilla::dom::Timeout* aTimeout, nsIScriptContext* aScx);
   // Return true if |aTimeout| needs to be reinserted into the timeout list.
-  bool RescheduleTimeout(nsTimeout* aTimeout, const TimeStamp& now,
+  bool RescheduleTimeout(mozilla::dom::Timeout* aTimeout, const TimeStamp& now,
                          bool aRunningPendingTimeouts);
 
   void ClearAllTimeouts();
   // Insert aTimeout into the list, before all timeouts that would
   // fire after it, but no earlier than mTimeoutInsertionPoint, if any.
-  void InsertTimeoutIntoList(nsTimeout *aTimeout);
+  void InsertTimeoutIntoList(mozilla::dom::Timeout* aTimeout);
   static void TimerCallback(nsITimer *aTimer, void *aClosure);
-  static void TimerNameCallback(nsITimer* aTimer, void* aClosure, char* aBuf,
-                                size_t aLen);
+  uint32_t GetTimeoutId(mozilla::dom::Timeout::Reason aReason);
 
   // Helper Functions
   already_AddRefed<nsIDocShellTreeOwner> GetTreeOwner();
   already_AddRefed<nsIBaseWindow> GetTreeOwnerWindow();
   already_AddRefed<nsIWebBrowserChrome> GetWebBrowserChrome();
   nsresult SecurityCheckURL(const char *aURL);
   bool IsPrivateBrowsing();
 
@@ -1845,17 +1796,17 @@ protected:
   RefPtr<nsDOMWindowList>     mFrames;
   // All BarProps are inner window only.
   RefPtr<mozilla::dom::BarProp> mMenubar;
   RefPtr<mozilla::dom::BarProp> mToolbar;
   RefPtr<mozilla::dom::BarProp> mLocationbar;
   RefPtr<mozilla::dom::BarProp> mPersonalbar;
   RefPtr<mozilla::dom::BarProp> mStatusbar;
   RefPtr<mozilla::dom::BarProp> mScrollbars;
-  RefPtr<nsDOMWindowUtils>    mWindowUtils;
+  RefPtr<nsDOMWindowUtils>      mWindowUtils;
   nsString                      mStatus;
   nsString                      mDefaultStatus;
   RefPtr<nsGlobalWindowObserver> mObserver; // Inner windows only.
   RefPtr<mozilla::dom::Crypto>  mCrypto;
   RefPtr<mozilla::dom::U2F> mU2F;
   RefPtr<mozilla::dom::cache::CacheStorage> mCacheStorage;
   RefPtr<mozilla::dom::Console> mConsole;
   // We need to store an nsISupports pointer to this object because the
@@ -1870,23 +1821,23 @@ protected:
   RefPtr<mozilla::dom::DOMStorage> mSessionStorage;
 
   // These member variable are used only on inner windows.
   RefPtr<mozilla::EventListenerManager> mListenerManager;
   // mTimeouts is generally sorted by mWhen, unless mTimeoutInsertionPoint is
   // non-null.  In that case, the dummy timeout pointed to by
   // mTimeoutInsertionPoint may have a later mWhen than some of the timeouts
   // that come after it.
-  mozilla::LinkedList<nsTimeout> mTimeouts;
+  mozilla::LinkedList<mozilla::dom::Timeout> mTimeouts;
   // If mTimeoutInsertionPoint is non-null, insertions should happen after it.
   // This is a dummy timeout at the moment; if that ever changes, the logic in
   // ResetTimersForNonBackgroundWindow needs to change.
-  nsTimeout*                    mTimeoutInsertionPoint;
-  uint32_t                      mTimeoutPublicIdCounter;
-  uint32_t                      mTimeoutFiringDepth;
+  mozilla::dom::Timeout*      mTimeoutInsertionPoint;
+  uint32_t                    mTimeoutIdCounter;
+  uint32_t                    mTimeoutFiringDepth;
   RefPtr<mozilla::dom::Location> mLocation;
   RefPtr<nsHistory>           mHistory;
   RefPtr<mozilla::dom::CustomElementRegistry> mCustomElements;
 
   // These member variables are used on both inner and the outer windows.
   nsCOMPtr<nsIPrincipal> mDocumentPrincipal;
 
   typedef nsTArray<RefPtr<mozilla::dom::StorageEvent>> nsDOMStorageEventArray;
@@ -1895,16 +1846,32 @@ 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 current idle request callback timeout handle
+  uint32_t mIdleCallbackTimeoutCounter;
+  // The current idle request callback handle
+  uint32_t mIdleRequestCallbackCounter;
+  IdleRequests mIdleRequestCallbacks;
+  IdleRequests mThrottledIdleRequestCallbacks;
+
 #ifdef DEBUG
   bool mSetOpenerWindowCalled;
   nsCOMPtr<nsIURI> mLastOpenedURI;
 #endif
 
 #ifdef MOZ_B2G
   bool mNetworkUploadObserverEnabled;
   bool mNetworkDownloadObserverEnabled;
--- a/dom/base/nsIScriptTimeoutHandler.h
+++ b/dom/base/nsIScriptTimeoutHandler.h
@@ -1,51 +1,52 @@
 /* -*- 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 nsIScriptTimeoutHandler_h___
 #define nsIScriptTimeoutHandler_h___
 
+#include "nsITimeoutHandler.h"
 #include "nsTArray.h"
 #include "js/TypeDecls.h"
+#include "mozilla/Function.h"
+#include "mozilla/Maybe.h"
 
 namespace mozilla {
 namespace dom {
 class Function;
 } // namespace dom
 } // namespace mozilla
 
 #define NS_ISCRIPTTIMEOUTHANDLER_IID \
 { 0x53c8e80e, 0xcc78, 0x48bc, \
  { 0xba, 0x63, 0x0c, 0xb9, 0xdb, 0xf7, 0x06, 0x34 } }
 
 /**
  * Abstraction of the script objects etc required to do timeouts in a
  * language agnostic way.
  */
 
-class nsIScriptTimeoutHandler : public nsISupports
+class nsIScriptTimeoutHandler : public nsITimeoutHandler
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISCRIPTTIMEOUTHANDLER_IID)
 
   // Get the Function to call.  If this returns nullptr, GetHandlerText() will
   // be called to get the string.
-  virtual mozilla::dom::Function *GetCallback() = 0;
+  virtual mozilla::dom::Function* GetCallback() = 0;
 
   // Get the handler text of not a compiled object.
   virtual const nsAString& GetHandlerText() = 0;
 
   // Get the location of the script.
   // Note: The memory pointed to by aFileName is owned by the
   // nsIScriptTimeoutHandler and should not be freed by the caller.
-  virtual void GetLocation(const char **aFileName, uint32_t *aLineNo,
-                           uint32_t *aColumn) = 0;
 
   // If we have a Function, get the arguments for passing to it.
   virtual const nsTArray<JS::Value>& GetArgs() = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIScriptTimeoutHandler,
                               NS_ISCRIPTTIMEOUTHANDLER_IID)
 
new file mode 100644
--- /dev/null
+++ b/dom/base/nsITimeoutHandler.h
@@ -0,0 +1,31 @@
+/* -*- 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 nsITimeoutHandler_h___
+#define nsITimeoutHandler_h___
+
+#include "nsISupports.h"
+
+#define NS_ITIMEOUTHANDLER_IID \
+{ 0xb071a1d3, 0xfd54, 0x40a8,  \
+ { 0x91, 0x9f, 0xc8, 0xf3, 0x3e, 0xb8, 0x3c, 0xfe } }
+
+class nsITimeoutHandler : public nsISupports
+{
+public:
+  NS_DECLARE_STATIC_IID_ACCESSOR(NS_ITIMEOUTHANDLER_IID)
+
+  virtual nsresult Call() = 0;
+
+  virtual void GetLocation(const char** aFileName, uint32_t* aLineNo,
+                           uint32_t* aColumn) = 0;
+
+  virtual void MarkForCC() = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsITimeoutHandler,
+                              NS_ITIMEOUTHANDLER_IID)
+
+#endif // nsITimeoutHandler_h___
--- a/dom/base/nsJSTimeoutHandler.cpp
+++ b/dom/base/nsJSTimeoutHandler.cpp
@@ -1,29 +1,32 @@
 /* -*- 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 <algorithm>
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Function.h"
+#include "mozilla/Likely.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/dom/FunctionBinding.h"
+#include "nsAXPCNativeCallContext.h"
 #include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsError.h"
+#include "nsGlobalWindow.h"
+#include "nsIContentSecurityPolicy.h"
 #include "nsIDocument.h"
 #include "nsIScriptTimeoutHandler.h"
 #include "nsIXPConnect.h"
 #include "nsJSUtils.h"
-#include "nsContentUtils.h"
-#include "nsError.h"
-#include "nsGlobalWindow.h"
-#include "nsIContentSecurityPolicy.h"
-#include "mozilla/Attributes.h"
-#include "mozilla/Likely.h"
-#include <algorithm>
-#include "mozilla/dom/FunctionBinding.h"
 #include "WorkerPrivate.h"
-#include "nsAXPCNativeCallContext.h"
 
 static const char kSetIntervalStr[] = "setInterval";
 static const char kSetTimeoutStr[] = "setTimeout";
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::dom::workers;
 
@@ -32,45 +35,59 @@ class nsJSScriptTimeoutHandler final : p
 {
 public:
   // nsISupports
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsJSScriptTimeoutHandler)
 
   nsJSScriptTimeoutHandler();
   // This will call SwapElements on aArguments with an empty array.
-  nsJSScriptTimeoutHandler(JSContext* aCx, nsGlobalWindow *aWindow,
+  nsJSScriptTimeoutHandler(JSContext* aCx, nsGlobalWindow* aWindow,
                            Function& aFunction,
                            nsTArray<JS::Heap<JS::Value>>&& aArguments,
                            ErrorResult& aError);
-  nsJSScriptTimeoutHandler(JSContext* aCx, nsGlobalWindow *aWindow,
+  nsJSScriptTimeoutHandler(JSContext* aCx, nsGlobalWindow* aWindow,
                            const nsAString& aExpression, bool* aAllowEval,
                            ErrorResult& aError);
   nsJSScriptTimeoutHandler(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
                            Function& aFunction,
                            nsTArray<JS::Heap<JS::Value>>&& aArguments);
   nsJSScriptTimeoutHandler(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
                            const nsAString& aExpression);
 
   virtual const nsAString& GetHandlerText() override;
+
   virtual Function* GetCallback() override
   {
     return mFunction;
   }
+
+  virtual const nsTArray<JS::Value>& GetArgs() override
+  {
+    return mArgs;
+  }
+
+  virtual nsresult Call() override
+  {
+    return NS_OK;
+  }
+
   virtual void GetLocation(const char** aFileName, uint32_t* aLineNo,
                            uint32_t* aColumn) override
   {
     *aFileName = mFileName.get();
     *aLineNo = mLineNo;
     *aColumn = mColumn;
   }
 
-  virtual const nsTArray<JS::Value>& GetArgs() override
+  virtual void MarkForCC() override
   {
-    return mArgs;
+    if (mFunction) {
+      mFunction->MarkForCC();
+    }
   }
 
   void ReleaseJSObjects();
 
 private:
   ~nsJSScriptTimeoutHandler();
 
   void Init(JSContext* aCx,
@@ -141,16 +158,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSScriptTimeoutHandler)
   for (uint32_t i = 0; i < tmp->mArgs.Length(); ++i) {
     NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mArgs[i])
   }
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSScriptTimeoutHandler)
   NS_INTERFACE_MAP_ENTRY(nsIScriptTimeoutHandler)
+  NS_INTERFACE_MAP_ENTRY(nsITimeoutHandler)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSScriptTimeoutHandler)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSScriptTimeoutHandler)
 
 static bool
 CheckCSPForEval(JSContext* aCx, nsGlobalWindow* aWindow, ErrorResult& aError)
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -31,28 +31,28 @@ class nsIDocument;
 class nsIIdleObserver;
 class nsIPrincipal;
 class nsIScriptTimeoutHandler;
 class nsIURI;
 class nsPIDOMWindowInner;
 class nsPIDOMWindowOuter;
 class nsPIWindowRoot;
 class nsXBLPrototypeHandler;
-struct nsTimeout;
 
 typedef uint32_t SuspendTypes;
 
 namespace mozilla {
 namespace dom {
 class AudioContext;
 class DocGroup;
 class TabGroup;
 class Element;
 class Performance;
 class ServiceWorkerRegistration;
+class Timeout;
 class CustomElementRegistry;
 } // namespace dom
 } // namespace mozilla
 
 // Popup control state enum. The values in this enum must go from most
 // permissive to least permissive so that it's safe to push state in
 // all situations. Pushing popup state onto the stack never makes the
 // current popup state less permissive (see
@@ -622,17 +622,17 @@ protected:
   typedef nsRefPtrHashtable<nsStringHashKey,
                             mozilla::dom::ServiceWorkerRegistration>
           ServiceWorkerRegistrationTable;
   ServiceWorkerRegistrationTable mServiceWorkerRegistrationTable;
 
   uint32_t               mModalStateDepth;
 
   // These variables are only used on inner windows.
-  nsTimeout             *mRunningTimeout;
+  mozilla::dom::Timeout *mRunningTimeout;
 
   uint32_t               mMutationBits;
 
   bool                   mIsDocumentLoaded;
   bool                   mIsHandlingResizeEvent;
   bool                   mIsInnerWindow;
   bool                   mMayHavePaintEventListener;
   bool                   mMayHaveTouchEventListener;
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -1409,16 +1409,19 @@ DOMInterfaces = {
     'concrete': False,
 },
 
 'Window': {
     'nativeType': 'nsGlobalWindow',
     'binaryNames': {
         'postMessage': 'postMessageMoz',
     },
+    'implicitJSContext': [
+        'requestIdleCallback'
+    ],
 },
 
 'WindowProxy': {
     'nativeType': 'nsPIDOMWindowOuter',
     'headerFile': 'nsPIDOMWindow.h',
     'concrete': False
 },
 
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -1158,28 +1158,29 @@ static nsresult FireEventForAccessibilit
                                           const nsAString& aEventType);
 #endif
 
 //
 // construction, destruction
 //
 
 HTMLInputElement::HTMLInputElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
-                                   FromParser aFromParser)
+                                   FromParser aFromParser, FromClone aFromClone)
   : nsGenericHTMLFormElementWithState(aNodeInfo)
   , mType(kInputDefaultType->value)
   , mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown)
   , mDisabledChanged(false)
   , mValueChanged(false)
   , mLastValueChangeWasInteractive(false)
   , mCheckedChanged(false)
   , mChecked(false)
   , mHandlingSelectEvent(false)
   , mShouldInitChecked(false)
-  , mParserCreating(aFromParser != NOT_FROM_PARSER)
+  , mDoneCreating(aFromParser == NOT_FROM_PARSER &&
+                  aFromClone == FromClone::no)
   , mInInternalActivate(false)
   , mCheckedIsToggled(false)
   , mIndeterminate(false)
   , mInhibitRestoration(aFromParser & FROM_PARSER_FRAGMENT)
   , mCanShowValidUI(true)
   , mCanShowInvalidUI(true)
   , mHasRange(false)
   , mIsDraggingRange(false)
@@ -1304,17 +1305,18 @@ NS_IMPL_NSICONSTRAINTVALIDATION_EXCEPT_S
 // nsIDOMNode
 
 nsresult
 HTMLInputElement::Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const
 {
   *aResult = nullptr;
 
   already_AddRefed<mozilla::dom::NodeInfo> ni = RefPtr<mozilla::dom::NodeInfo>(aNodeInfo).forget();
-  RefPtr<HTMLInputElement> it = new HTMLInputElement(ni, NOT_FROM_PARSER);
+  RefPtr<HTMLInputElement> it = new HTMLInputElement(ni, NOT_FROM_PARSER,
+                                                     FromClone::yes);
 
   nsresult rv = const_cast<HTMLInputElement*>(this)->CopyInnerTo(it);
   NS_ENSURE_SUCCESS(rv, rv);
 
   switch (GetValueMode()) {
     case VALUE_MODE_VALUE:
       if (mValueChanged) {
         // We don't have our default value anymore.  Set our value on
@@ -1346,36 +1348,38 @@ HTMLInputElement::Clone(mozilla::dom::No
       break;
     case VALUE_MODE_DEFAULT:
       if (mType == NS_FORM_INPUT_IMAGE && it->OwnerDoc()->IsStaticDocument()) {
         CreateStaticImageClone(it);
       }
       break;
   }
 
+  it->DoneCreatingElement();
+
   it->mLastValueChangeWasInteractive = mLastValueChangeWasInteractive;
   it.forget(aResult);
   return NS_OK;
 }
 
 nsresult
 HTMLInputElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                 nsAttrValueOrString* aValue,
                                 bool aNotify)
 {
   if (aNameSpaceID == kNameSpaceID_None) {
     //
     // When name or type changes, radio should be removed from radio group.
     // (type changes are handled in the form itself currently)
-    // If the parser is not done creating the radio, we also should not do it.
+    // If we are not done creating the radio, we also should not do it.
     //
     if ((aName == nsGkAtoms::name ||
          (aName == nsGkAtoms::type && !mForm)) &&
         mType == NS_FORM_INPUT_RADIO &&
-        (mForm || !mParserCreating)) {
+        (mForm || mDoneCreating)) {
       WillRemoveFromRadioGroup();
     } else if (aNotify && aName == nsGkAtoms::src &&
                mType == NS_FORM_INPUT_IMAGE) {
       if (aValue) {
         LoadImage(aValue->String(), true, aNotify, eImageLoadType_Normal);
       } else {
         // Null value means the attr got unset; drop the image
         CancelImageRequests(aNotify);
@@ -1410,22 +1414,22 @@ HTMLInputElement::BeforeSetAttr(int32_t 
 nsresult
 HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                const nsAttrValue* aValue, bool aNotify)
 {
   if (aNameSpaceID == kNameSpaceID_None) {
     //
     // When name or type changes, radio should be added to radio group.
     // (type changes are handled in the form itself currently)
-    // If the parser is not done creating the radio, we also should not do it.
+    // If we are not done creating the radio, we also should not do it.
     //
     if ((aName == nsGkAtoms::name ||
          (aName == nsGkAtoms::type && !mForm)) &&
         mType == NS_FORM_INPUT_RADIO &&
-        (mForm || !mParserCreating)) {
+        (mForm || mDoneCreating)) {
       AddedToRadioGroup();
       UpdateValueMissingValidityStateForRadio(false);
     }
 
     // If @value is changed and BF_VALUE_CHANGED is false, @value is the value
     // of the element so, if the value of the element is different than @value,
     // we have to re-set it. This is only the case when GetValueMode() returns
     // VALUE_MODE_VALUE.
@@ -1433,19 +1437,19 @@ HTMLInputElement::AfterSetAttr(int32_t a
         !mValueChanged && GetValueMode() == VALUE_MODE_VALUE) {
       SetDefaultValueAsValue();
     }
 
     //
     // Checked must be set no matter what type of control it is, since
     // mChecked must reflect the new value
     if (aName == nsGkAtoms::checked && !mCheckedChanged) {
-      // Delay setting checked if the parser is creating this element (wait
+      // Delay setting checked if we are creating this element (wait
       // until everything is set)
-      if (mParserCreating) {
+      if (!mDoneCreating) {
         mShouldInitChecked = true;
       } else {
         DoSetChecked(DefaultChecked(), true, true);
         SetCheckedChanged(false);
       }
     }
 
     if (aName == nsGkAtoms::type) {
@@ -1489,17 +1493,17 @@ HTMLInputElement::AfterSetAttr(int32_t a
       // This *has* to be called *after* validity has changed.
       if (aName == nsGkAtoms::readonly || aName == nsGkAtoms::disabled) {
         UpdateBarredFromConstraintValidation();
       }
     } else if (MinOrMaxLengthApplies() && aName == nsGkAtoms::maxlength) {
       UpdateTooLongValidityState();
     } else if (MinOrMaxLengthApplies() && aName == nsGkAtoms::minlength) {
       UpdateTooShortValidityState();
-    } else if (aName == nsGkAtoms::pattern && !mParserCreating) {
+    } else if (aName == nsGkAtoms::pattern && mDoneCreating) {
       UpdatePatternMismatchValidityState();
     } else if (aName == nsGkAtoms::multiple) {
       UpdateTypeMismatchValidityState();
     } else if (aName == nsGkAtoms::max) {
       UpdateHasRange();
       if (mType == NS_FORM_INPUT_RANGE) {
         // The value may need to change when @max changes since the value may
         // have been invalid and can now change to a valid value, or vice
@@ -1515,52 +1519,52 @@ HTMLInputElement::AfterSetAttr(int32_t a
         nsAutoString value;
         GetValue(value);
         nsresult rv =
           SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
         NS_ENSURE_SUCCESS(rv, rv);
       }
       // Validity state must be updated *after* the SetValueInternal call above
       // or else the following assert will not be valid.
-      // We don't assert the state of underflow during parsing since
+      // We don't assert the state of underflow during creation since
       // DoneCreatingElement sanitizes.
       UpdateRangeOverflowValidityState();
-      MOZ_ASSERT(mParserCreating ||
+      MOZ_ASSERT(!mDoneCreating ||
                  mType != NS_FORM_INPUT_RANGE ||
                  !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
                  "HTML5 spec does not allow underflow for type=range");
     } else if (aName == nsGkAtoms::min) {
       UpdateHasRange();
       if (mType == NS_FORM_INPUT_RANGE) {
         // See @max comment
         nsAutoString value;
         GetValue(value);
         nsresult rv =
           SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
         NS_ENSURE_SUCCESS(rv, rv);
       }
       // See corresponding @max comment
       UpdateRangeUnderflowValidityState();
       UpdateStepMismatchValidityState();
-      MOZ_ASSERT(mParserCreating ||
+      MOZ_ASSERT(!mDoneCreating ||
                  mType != NS_FORM_INPUT_RANGE ||
                  !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
                  "HTML5 spec does not allow underflow for type=range");
     } else if (aName == nsGkAtoms::step) {
       if (mType == NS_FORM_INPUT_RANGE) {
         // See @max comment
         nsAutoString value;
         GetValue(value);
         nsresult rv =
           SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
         NS_ENSURE_SUCCESS(rv, rv);
       }
       // See corresponding @max comment
       UpdateStepMismatchValidityState();
-      MOZ_ASSERT(mParserCreating ||
+      MOZ_ASSERT(!mDoneCreating ||
                  mType != NS_FORM_INPUT_RANGE ||
                  !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
                  "HTML5 spec does not allow underflow for type=range");
     } else if (aName == nsGkAtoms::dir &&
                aValue && aValue->Equals(nsGkAtoms::_auto, eIgnoreCase)) {
       SetDirectionIfAuto(true, aNotify);
     } else if (aName == nsGkAtoms::lang) {
       if (mType == NS_FORM_INPUT_NUMBER) {
@@ -3244,32 +3248,32 @@ HTMLInputElement::SetValueInternal(const
   switch (GetValueMode()) {
     case VALUE_MODE_VALUE:
     {
       // At the moment, only single line text control have to sanitize their value
       // Because we have to create a new string for that, we should prevent doing
       // it if it's useless.
       nsAutoString value(aValue);
 
-      if (!mParserCreating) {
+      if (mDoneCreating) {
         SanitizeValue(value);
       }
-      // else DoneCreatingElement calls us again once mParserCreating is false
+      // else DoneCreatingElement calls us again once mDoneCreating is true
 
       bool setValueChanged = !!(aFlags & nsTextEditorState::eSetValue_Notify);
       if (setValueChanged) {
         SetValueChanged(true);
       }
 
       if (IsSingleLineTextControl(false)) {
         if (!mInputData.mState->SetValue(value, aFlags)) {
           return NS_ERROR_OUT_OF_MEMORY;
         }
         if (mType == NS_FORM_INPUT_EMAIL) {
-          UpdateAllValidityStates(mParserCreating);
+          UpdateAllValidityStates(!mDoneCreating);
         }
       } else {
         free(mInputData.mValue);
         mInputData.mValue = ToNewUnicode(value);
         if (setValueChanged) {
           SetValueChanged(true);
         }
         if (mType == NS_FORM_INPUT_NUMBER) {
@@ -3287,21 +3291,21 @@ HTMLInputElement::SetValueInternal(const
           }
         } else if (mType == NS_FORM_INPUT_TIME &&
                    !IsExperimentalMobileType(mType)) {
           nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
           if (frame) {
             frame->UpdateInputBoxValue();
           }
         }
-        if (!mParserCreating) {
+        if (mDoneCreating) {
           OnValueChanged(/* aNotify = */ true,
                          /* aWasInteractiveUserChange = */ false);
         }
-        // else DoneCreatingElement calls us again once mParserCreating is false
+        // else DoneCreatingElement calls us again once mDoneCreating is true
       }
 
       if (mType == NS_FORM_INPUT_COLOR) {
         // Update color frame, to reflect color changes
         nsColorControlFrame* colorControlFrame = do_QueryFrame(GetPrimaryFrame());
         if (colorControlFrame) {
           colorControlFrame->UpdateColor();
         }
@@ -5159,17 +5163,17 @@ HTMLInputElement::HandleTypeChange(uint8
   UpdateAllValidityStates(false);
 
   UpdateApzAwareFlag();
 }
 
 void
 HTMLInputElement::SanitizeValue(nsAString& aValue)
 {
-  NS_ASSERTION(!mParserCreating, "The element parsing should be finished!");
+  NS_ASSERTION(mDoneCreating, "The element creation should be finished!");
 
   switch (mType) {
     case NS_FORM_INPUT_TEXT:
     case NS_FORM_INPUT_SEARCH:
     case NS_FORM_INPUT_TEL:
     case NS_FORM_INPUT_PASSWORD:
       {
         char16_t crlf[] = { char16_t('\r'), char16_t('\n'), 0 };
@@ -6707,17 +6711,17 @@ HTMLInputElement::SaveState()
   }
 
   return NS_OK;
 }
 
 void
 HTMLInputElement::DoneCreatingElement()
 {
-  mParserCreating = false;
+  mDoneCreating = true;
 
   //
   // Restore state as needed.  Note that disabled state applies to all control
   // types.
   //
   bool restoredCheckedState =
     !mInhibitRestoration && NS_SUCCEEDED(GenerateStateKey()) && RestoreFormControlState();
 
@@ -6943,18 +6947,18 @@ void
 HTMLInputElement::AddedToRadioGroup()
 {
   // If the element is neither in a form nor a document, there is no group so we
   // should just stop here.
   if (!mForm && !IsInUncomposedDoc()) {
     return;
   }
 
-  // Make sure not to notify if we're still being created by the parser
-  bool notify = !mParserCreating;
+  // Make sure not to notify if we're still being created
+  bool notify = mDoneCreating;
 
   //
   // If the input element is checked, and we add it to the group, it will
   // deselect whatever is currently selected in that group
   //
   if (mChecked) {
     //
     // If it is checked, call "RadioSetChecked" to perform the selection/
@@ -7705,17 +7709,17 @@ void
 HTMLInputElement::UpdateTooShortValidityState()
 {
   SetValidityState(VALIDITY_STATE_TOO_SHORT, IsTooShort());
 }
 
 void
 HTMLInputElement::UpdateValueMissingValidityStateForRadio(bool aIgnoreSelf)
 {
-  bool notify = !mParserCreating;
+  bool notify = mDoneCreating;
   nsCOMPtr<nsIDOMHTMLInputElement> selection = GetSelectedRadioButton();
 
   aIgnoreSelf = aIgnoreSelf || !IsMutable();
 
   // If there is no selection, that might mean the radio is not in a group.
   // In that case, we can look for the checked state of the radio.
   bool selected = selection || (!aIgnoreSelf && mChecked);
   bool required = !aIgnoreSelf && HasAttr(kNameSpaceID_None, nsGkAtoms::required);
@@ -8228,17 +8232,17 @@ HTMLInputElement::GetRows()
 NS_IMETHODIMP_(void)
 HTMLInputElement::GetDefaultValueFromContent(nsAString& aValue)
 {
   nsTextEditorState *state = GetEditorState();
   if (state) {
     GetDefaultValue(aValue);
     // This is called by the frame to show the value.
     // We have to sanitize it when needed.
-    if (!mParserCreating) {
+    if (mDoneCreating) {
       SanitizeValue(aValue);
     }
   }
 }
 
 NS_IMETHODIMP_(bool)
 HTMLInputElement::ValueChanged() const
 {
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -116,18 +116,21 @@ class HTMLInputElement final : public ns
 public:
   using nsIConstraintValidation::GetValidationMessage;
   using nsIConstraintValidation::CheckValidity;
   using nsIConstraintValidation::ReportValidity;
   using nsIConstraintValidation::WillValidate;
   using nsIConstraintValidation::Validity;
   using nsGenericHTMLFormElementWithState::GetForm;
 
+  enum class FromClone { no, yes };
+
   HTMLInputElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
-                   mozilla::dom::FromParser aFromParser);
+                   mozilla::dom::FromParser aFromParser,
+                   FromClone aFromClone = FromClone::no);
 
   NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLInputElement, input)
 
   // nsISupports
   NS_DECL_ISUPPORTS_INHERITED
 
   virtual int32_t TabIndexDefault() override;
   using nsGenericHTMLElement::Focus;
@@ -1553,17 +1556,17 @@ protected:
   nsContentUtils::AutocompleteAttrState mAutocompleteAttrState;
   bool                     mDisabledChanged     : 1;
   bool                     mValueChanged        : 1;
   bool                     mLastValueChangeWasInteractive : 1;
   bool                     mCheckedChanged      : 1;
   bool                     mChecked             : 1;
   bool                     mHandlingSelectEvent : 1;
   bool                     mShouldInitChecked   : 1;
-  bool                     mParserCreating      : 1;
+  bool                     mDoneCreating        : 1;
   bool                     mInInternalActivate  : 1;
   bool                     mCheckedIsToggled    : 1;
   bool                     mIndeterminate       : 1;
   bool                     mInhibitRestoration  : 1;
   bool                     mCanShowValidUI      : 1;
   bool                     mCanShowInvalidUI    : 1;
   bool                     mHasRange            : 1;
   bool                     mIsDraggingRange     : 1;
--- a/dom/html/nsGenericHTMLElement.cpp
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -1259,21 +1259,21 @@ nsGenericHTMLElement::MapCommonAttribute
   if (aData->mSIDs & NS_STYLE_INHERIT_BIT(UserInterface)) {
     nsCSSValue* userModify = aData->ValueForUserModify();
     if (userModify->GetUnit() == eCSSUnit_Null) {
       const nsAttrValue* value =
         aAttributes->GetAttr(nsGkAtoms::contenteditable);
       if (value) {
         if (value->Equals(nsGkAtoms::_empty, eCaseMatters) ||
             value->Equals(nsGkAtoms::_true, eIgnoreCase)) {
-          userModify->SetIntValue(NS_STYLE_USER_MODIFY_READ_WRITE,
+          userModify->SetIntValue(StyleUserModify::ReadWrite,
                                   eCSSUnit_Enumerated);
         }
         else if (value->Equals(nsGkAtoms::_false, eIgnoreCase)) {
-            userModify->SetIntValue(NS_STYLE_USER_MODIFY_READ_ONLY,
+            userModify->SetIntValue(StyleUserModify::ReadOnly,
                                     eCSSUnit_Enumerated);
         }
       }
     }
   }
 
   MapLangAttributeInto(aAttributes, aData);
 }
--- a/dom/html/test/mochitest.ini
+++ b/dom/html/test/mochitest.ini
@@ -625,8 +625,9 @@ skip-if = (os == 'android' || os == 'mac
 [test_bug1261674-2.html]
 skip-if = (os == 'android' || os == 'mac')
 [test_bug1260704.html]
 [test_allowMedia.html]
 [test_bug1292522_same_domain_with_different_port_number.html]
 [test_bug1295719_event_sequence_for_arrow_keys.html]
 skip-if = os == "android" || appname == "b2g" # up/down arrow keys not supported on android/b2g
 [test_bug1295719_event_sequence_for_number_keys.html]
+[test_bug1310865.html]
new file mode 100644
--- /dev/null
+++ b/dom/html/test/test_bug1310865.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>Test for Bug 1310865</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<input value="a
+b" type="hidden">
+<input type="hidden" value="a
+b">
+<script>
+var input1 = document.querySelector("input");
+var input2 = document.querySelector("input + input");
+var clone1 = input1.cloneNode(false);
+var clone2 = input2.cloneNode(false);
+// Newlines must not be stripped
+is(clone1.value, "a\nb");
+is(clone2.value, "a\nb");
+</script>
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -78,16 +78,17 @@
 #endif
 
 #include "mozilla/Unused.h"
 
 #include "mozInlineSpellChecker.h"
 #include "nsDocShell.h"
 #include "nsIConsoleListener.h"
 #include "nsICycleCollectorListener.h"
+#include "nsIIdlePeriod.h"
 #include "nsIDragService.h"
 #include "nsIIPCBackgroundChildCreateCallback.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIMemoryReporter.h"
 #include "nsIMemoryInfoDumper.h"
 #include "nsIMutable.h"
 #include "nsIObserverService.h"
 #include "nsIScriptSecurityManager.h"
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -560,16 +560,18 @@ var interfaceNamesInGlobalScope =
     "HTMLTrackElement",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "HTMLUListElement",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "HTMLUnknownElement",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "HTMLVideoElement",
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    "IdleDeadline",
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "IDBCursor",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "IDBCursorWithValue",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "IDBDatabase",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "IDBFactory",
 // IMPORTANT: Do not change this list without review from a DOM peer!
new file mode 100644
--- /dev/null
+++ b/dom/webidl/IdleDeadline.webidl
@@ -0,0 +1,14 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ *
+ * The origin of this IDL file is:
+ * https://w3c.github.io/requestidlecallback/
+ */
+
+[Pref="dom.requestIdleCallback.enabled"]
+interface IdleDeadline {
+  DOMHighResTimeStamp timeRemaining();
+  readonly attribute boolean didTimeout;
+};
--- a/dom/webidl/Window.webidl
+++ b/dom/webidl/Window.webidl
@@ -9,16 +9,17 @@
  * https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html
  * http://dev.w3.org/csswg/cssom/
  * http://dev.w3.org/csswg/cssom-view/
  * https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/RequestAnimationFrame/Overview.html
  * https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/NavigationTiming/Overview.html
  * https://dvcs.w3.org/hg/webcrypto-api/raw-file/tip/spec/Overview.html
  * http://dvcs.w3.org/hg/speech-api/raw-file/tip/speechapi.html
  * https://w3c.github.io/webappsec-secure-contexts/#monkey-patching-global-object
+ * https://w3c.github.io/requestidlecallback/
  */
 
 interface ApplicationCache;
 interface IID;
 interface nsIBrowserDOMWindow;
 interface nsIMessageBroadcaster;
 interface nsIDOMCrypto;
 typedef any Transferable;
@@ -485,8 +486,22 @@ partial interface Window {
   [Pref="dom.vr.enabled"]
   attribute EventHandler onvrdisplaydisconnect;
   [Pref="dom.vr.enabled"]
   attribute EventHandler onvrdisplaypresentchange;
 };
 
 Window implements ChromeWindow;
 Window implements WindowOrWorkerGlobalScope;
+
+partial interface Window {
+  [Throws, Pref="dom.requestIdleCallback.enabled"]
+  unsigned long requestIdleCallback(IdleRequestCallback callback,
+                                    optional IdleRequestOptions options);
+  [Pref="dom.requestIdleCallback.enabled"]
+  void          cancelIdleCallback(unsigned long handle);
+};
+
+dictionary IdleRequestOptions {
+  unsigned long timeout;
+};
+
+callback IdleRequestCallback = void (IdleDeadline deadline);
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -264,16 +264,17 @@ WEBIDL_FILES = [
     'IDBIndex.webidl',
     'IDBKeyRange.webidl',
     'IDBMutableFile.webidl',
     'IDBObjectStore.webidl',
     'IDBOpenDBRequest.webidl',
     'IDBRequest.webidl',
     'IDBTransaction.webidl',
     'IDBVersionChangeEvent.webidl',
+    'IdleDeadline.webidl',
     'IIRFilterNode.webidl',
     'ImageBitmap.webidl',
     'ImageBitmapRenderingContext.webidl',
     'ImageCapture.webidl',
     'ImageData.webidl',
     'ImageDocument.webidl',
     'InputEvent.webidl',
     'InputMethod.webidl',
--- a/gfx/cairo/cairo/src/cairo-dwrite-font.cpp
+++ b/gfx/cairo/cairo/src/cairo-dwrite-font.cpp
@@ -1359,17 +1359,17 @@ cairo_int_status_t
      */
     RECT fontArea;
     fontArea.left = (INT32)(smallestX - scaled_font->font_matrix.xx);
     fontArea.right = (INT32)(largestX + scaled_font->font_matrix.xx * 2);
     fontArea.top = (INT32)(smallestY - scaled_font->font_matrix.yy);
     fontArea.bottom = (INT32)(largestY + scaled_font->font_matrix.yy * 2);
     if (fontArea.left < 0)
 	fontArea.left = 0;
-    if (fontArea.top > 0)
+    if (fontArea.top < 0)
 	fontArea.top = 0;
     if (fontArea.bottom > dst->extents.height) {
 	fontArea.bottom = dst->extents.height;
     }
     if (fontArea.right > dst->extents.width) {
 	fontArea.right = dst->extents.width;
     }
     if (fontArea.right <= fontArea.left ||
--- a/layout/base/nsCaret.cpp
+++ b/layout/base/nsCaret.cpp
@@ -509,17 +509,17 @@ nsCaret::GetPaintGeometry(nsRect* aRect)
       mOverrideContent, mOverrideOffset, &frameOffset);
   if (!frame) {
     return nullptr;
   }
 
   // now we have a frame, check whether it's appropriate to show the caret here
   const nsStyleUserInterface* userinterface = frame->StyleUserInterface();
   if ((!mIgnoreUserModify &&
-       userinterface->mUserModify == NS_STYLE_USER_MODIFY_READ_ONLY) ||
+       userinterface->mUserModify == StyleUserModify::ReadOnly) ||
       userinterface->mUserInput == StyleUserInput::None ||
       userinterface->mUserInput == StyleUserInput::Disabled) {
     return nullptr;
   }
 
   // If the offset falls outside of the frame, then don't paint the caret.
   int32_t startOffset, endOffset;
   if (frame->GetType() == nsGkAtoms::textFrame &&
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -1302,17 +1302,17 @@ nsDisplayListBuilder::RecomputeCurrentAn
 void
 nsDisplayListBuilder::AdjustWindowDraggingRegion(nsIFrame* aFrame)
 {
   if (!mWindowDraggingAllowed || !IsForPainting()) {
     return;
   }
 
   const nsStyleUIReset* styleUI = aFrame->StyleUIReset();
-  if (styleUI->mWindowDragging == NS_STYLE_WINDOW_DRAGGING_DEFAULT) {
+  if (styleUI->mWindowDragging == StyleWindowDragging::Default) {
     // This frame has the default value and doesn't influence the window
     // dragging region.
     return;
   }
 
   LayoutDeviceToLayoutDeviceMatrix4x4 referenceFrameToRootReferenceFrame;
 
   // The const_cast is for nsLayoutUtils::GetTransformToAncestor.
@@ -1357,17 +1357,17 @@ nsDisplayListBuilder::AdjustWindowDraggi
   if (!borderBox.IsEmpty()) {
     LayoutDeviceRect devPixelBorderBox =
       LayoutDevicePixel::FromAppUnits(borderBox, aFrame->PresContext()->AppUnitsPerDevPixel());
     LayoutDeviceRect transformedDevPixelBorderBox =
       TransformBy(referenceFrameToRootReferenceFrame, devPixelBorderBox);
     transformedDevPixelBorderBox.Round();
     LayoutDeviceIntRect transformedDevPixelBorderBoxInt;
     if (transformedDevPixelBorderBox.ToIntRect(&transformedDevPixelBorderBoxInt)) {
-      if (styleUI->mWindowDragging == NS_STYLE_WINDOW_DRAGGING_DRAG) {
+      if (styleUI->mWindowDragging == StyleWindowDragging::Drag) {
         mWindowDraggingRegion.OrWith(transformedDevPixelBorderBoxInt);
       } else {
         mWindowNoDraggingRegion.OrWith(transformedDevPixelBorderBoxInt);
       }
     }
   }
 }
 
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -146,16 +146,23 @@ using namespace mozilla::gfx;
 #define GRID_ENABLED_PREF_NAME "layout.css.grid.enabled"
 #define GRID_TEMPLATE_SUBGRID_ENABLED_PREF_NAME "layout.css.grid-template-subgrid-value.enabled"
 #define WEBKIT_PREFIXES_ENABLED_PREF_NAME "layout.css.prefixes.webkit"
 #define DISPLAY_CONTENTS_ENABLED_PREF_NAME "layout.css.display-contents.enabled"
 #define TEXT_ALIGN_UNSAFE_ENABLED_PREF_NAME "layout.css.text-align-unsafe-value.enabled"
 #define FLOAT_LOGICAL_VALUES_ENABLED_PREF_NAME "layout.css.float-logical-values.enabled"
 #define BG_CLIP_TEXT_ENABLED_PREF_NAME "layout.css.background-clip-text.enabled"
 
+// The time in number of frames that we estimate for a refresh driver
+// to be quiescent
+#define DEFAULT_QUIESCENT_FRAMES 2
+// The time (milliseconds) we estimate is needed between the end of an
+// idle time and the next Tick.
+#define DEFAULT_IDLE_PERIOD_TIME_LIMIT 3.0f
+
 #ifdef DEBUG
 // TODO: remove, see bug 598468.
 bool nsLayoutUtils::gPreventAssertInCompareTreePosition = false;
 #endif // DEBUG
 
 typedef FrameMetrics::ViewID ViewID;
 typedef nsStyleTransformMatrix::TransformReferenceBox TransformReferenceBox;
 
@@ -169,16 +176,18 @@ typedef nsStyleTransformMatrix::Transfor
 /* static */ bool nsLayoutUtils::sInvalidationDebuggingIsEnabled;
 /* static */ bool nsLayoutUtils::sCSSVariablesEnabled;
 /* static */ bool nsLayoutUtils::sInterruptibleReflowEnabled;
 /* static */ bool nsLayoutUtils::sSVGTransformBoxEnabled;
 /* static */ bool nsLayoutUtils::sTextCombineUprightDigitsEnabled;
 #ifdef MOZ_STYLO
 /* static */ bool nsLayoutUtils::sStyloEnabled;
 #endif
+/* static */ uint32_t nsLayoutUtils::sIdlePeriodDeadlineLimit;
+/* static */ uint32_t nsLayoutUtils::sQuiescentFramesBeforeIdlePeriod;
 
 static ViewID sScrollIdCounter = FrameMetrics::START_SCROLL_ID;
 
 typedef nsDataHashtable<nsUint64HashKey, nsIContent*> ContentMap;
 static ContentMap* sContentMap = nullptr;
 static ContentMap& GetContentMap() {
   if (!sContentMap) {
     sContentMap = new ContentMap();
@@ -7866,16 +7875,22 @@ nsLayoutUtils::Initialize()
   Preferences::AddBoolVarCache(&sSVGTransformBoxEnabled,
                                "svg.transform-box.enabled");
   Preferences::AddBoolVarCache(&sTextCombineUprightDigitsEnabled,
                                "layout.css.text-combine-upright-digits.enabled");
 #ifdef MOZ_STYLO
   Preferences::AddBoolVarCache(&sStyloEnabled,
                                "layout.css.servo.enabled");
 #endif
+  Preferences::AddUintVarCache(&sIdlePeriodDeadlineLimit,
+                               "layout.idle_period.time_limit",
+                               DEFAULT_IDLE_PERIOD_TIME_LIMIT);
+  Preferences::AddUintVarCache(&sQuiescentFramesBeforeIdlePeriod,
+                               "layout.idle_period.required_quiescent_frames",
+                               DEFAULT_QUIESCENT_FRAMES);
 
   for (auto& callback : kPrefCallbacks) {
     Preferences::RegisterCallbackAndCall(callback.func, callback.name);
   }
   nsComputedDOMStyle::RegisterPrefChangeCallbacks();
 }
 
 /* static */
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -2424,16 +2424,24 @@ public:
   static bool StyloEnabled() {
 #ifdef MOZ_STYLO
     return sStyloEnabled;
 #else
     return false;
 #endif
   }
 
+  static uint32_t IdlePeriodDeadlineLimit() {
+    return sIdlePeriodDeadlineLimit;
+  }
+
+  static uint32_t QuiescentFramesBeforeIdlePeriod() {
+    return sQuiescentFramesBeforeIdlePeriod;
+  }
+
   /**
    * See comment above "font.size.inflation.mappingIntercept" in
    * modules/libpref/src/init/all.js .
    */
   static int32_t FontSizeInflationMappingIntercept() {
     return sFontSizeInflationMappingIntercept;
   }
 
@@ -2880,16 +2888,18 @@ private:
   static bool sInvalidationDebuggingIsEnabled;
   static bool sCSSVariablesEnabled;
   static bool sInterruptibleReflowEnabled;
   static bool sSVGTransformBoxEnabled;
   static bool sTextCombineUprightDigitsEnabled;
 #ifdef MOZ_STYLO
   static bool sStyloEnabled;
 #endif
+  static uint32_t sIdlePeriodDeadlineLimit;
+  static uint32_t sQuiescentFramesBeforeIdlePeriod;
 
   /**
    * Helper function for LogTestDataForPaint().
    */
   static void DoLogTestDataForPaint(mozilla::layers::LayerManager* aManager,
                                     ViewID aScrollId,
                                     const std::string& aKey,
                                     const std::string& aValue);
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -133,16 +133,17 @@ namespace mozilla {
  * start/stop whatever timer mechanism is in use, and ScheduleNextTick
  * is called at the start of the Tick() implementation to set a time
  * for the next tick.
  */
 class RefreshDriverTimer {
 public:
   RefreshDriverTimer()
     : mLastFireEpoch(0)
+    , mLastFireSkipped(false)
   {
   }
 
   virtual ~RefreshDriverTimer()
   {
     MOZ_ASSERT(mContentRefreshDrivers.Length() == 0, "Should have removed all content refresh drivers from here by now!");
     MOZ_ASSERT(mRootRefreshDrivers.Length() == 0, "Should have removed all root refresh drivers from here by now!");
   }
@@ -216,16 +217,45 @@ public:
       driver->mActiveTimer = aNewTimer;
     }
     mRootRefreshDrivers.Clear();
 
     aNewTimer->mLastFireEpoch = mLastFireEpoch;
     aNewTimer->mLastFireTime = mLastFireTime;
   }
 
+  virtual TimeDuration GetTimerRate() = 0;
+
+  bool LastTickSkippedAnyPaints() const
+  {
+    return mLastFireSkipped;
+  }
+
+  Maybe<TimeStamp> GetIdleDeadlineHint()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    if (LastTickSkippedAnyPaints()) {
+      return Some(TimeStamp());
+    }
+
+    TimeStamp mostRecentRefresh = MostRecentRefresh();
+    TimeDuration refreshRate = GetTimerRate();
+    TimeStamp idleEnd = mostRecentRefresh + refreshRate;
+
+    if (idleEnd +
+          refreshRate * nsLayoutUtils::QuiescentFramesBeforeIdlePeriod() <
+        TimeStamp::Now()) {
+      return Nothing();
+    }
+
+    return Some(idleEnd - TimeDuration::FromMilliseconds(
+                            nsLayoutUtils::IdlePeriodDeadlineLimit()));
+  }
+
 protected:
   virtual void StartTimer() = 0;
   virtual void StopTimer() = 0;
   virtual void ScheduleNextTick(TimeStamp aNowTime) = 0;
 
   bool IsRootRefreshDriver(nsRefreshDriver* aDriver)
   {
     nsPresContext* pc = aDriver->GetPresContext();
@@ -257,28 +287,31 @@ protected:
     nsTArray<RefPtr<nsRefreshDriver> > drivers(aDrivers);
     for (nsRefreshDriver* driver : drivers) {
       // don't poke this driver if it's in test mode
       if (driver->IsTestControllingRefreshesEnabled()) {
         continue;
       }
 
       TickDriver(driver, aJsNow, aNow);
+
+      mLastFireSkipped = mLastFireSkipped || driver->mSkippedPaints;
     }
   }
 
   /*
    * Tick the refresh drivers based on the given timestamp.
    */
   void Tick(int64_t jsnow, TimeStamp now)
   {
     ScheduleNextTick(now);
 
     mLastFireEpoch = jsnow;
     mLastFireTime = now;
+    mLastFireSkipped = false;
 
     LOG("[%p] ticking drivers...", this);
     // RD is short for RefreshDriver
     profiler_tracing("Paint", "RD", TRACING_INTERVAL_START);
 
     TickRefreshDrivers(jsnow, now, mContentRefreshDrivers);
     TickRefreshDrivers(jsnow, now, mRootRefreshDrivers);
 
@@ -288,16 +321,17 @@ protected:
 
   static void TickDriver(nsRefreshDriver* driver, int64_t jsnow, TimeStamp now)
   {
     LOG(">> TickDriver: %p (jsnow: %lld)", driver, jsnow);
     driver->Tick(jsnow, now);
   }
 
   int64_t mLastFireEpoch;
+  bool mLastFireSkipped;
   TimeStamp mLastFireTime;
   TimeStamp mTargetTime;
 
   nsTArray<RefPtr<nsRefreshDriver> > mContentRefreshDrivers;
   nsTArray<RefPtr<nsRefreshDriver> > mRootRefreshDrivers;
 
   // useful callback for nsITimer-based derived classes, here
   // bacause of c++ protected shenanigans
@@ -340,31 +374,36 @@ public:
     mRateDuration = TimeDuration::FromMilliseconds(mRateMilliseconds);
   }
 
   double GetRate() const
   {
     return mRateMilliseconds;
   }
 
+  virtual TimeDuration GetTimerRate() override
+  {
+    return mRateDuration;
+  }
+
 protected:
 
-  virtual void StartTimer()
+  virtual void StartTimer() override
   {
     // pretend we just fired, and we schedule the next tick normally
     mLastFireEpoch = JS_Now();
     mLastFireTime = TimeStamp::Now();
 
     mTargetTime = mLastFireTime + mRateDuration;
 
     uint32_t delay = static_cast<uint32_t>(mRateMilliseconds);
     mTimer->InitWithFuncCallback(TimerTick, this, delay, nsITimer::TYPE_ONE_SHOT);
   }
 
-  virtual void StopTimer()
+  virtual void StopTimer() override
   {
     mTimer->Cancel();
   }
 
   double mRateMilliseconds;
   TimeDuration mRateDuration;
   RefPtr<nsITimer> mTimer;
 };
@@ -381,26 +420,44 @@ public:
     : mVsyncChild(nullptr)
   {
     MOZ_ASSERT(XRE_IsParentProcess());
     MOZ_ASSERT(NS_IsMainThread());
     mVsyncObserver = new RefreshDriverVsyncObserver(this);
     RefPtr<mozilla::gfx::VsyncSource> vsyncSource = gfxPlatform::GetPlatform()->GetHardwareVsync();
     MOZ_ALWAYS_TRUE(mVsyncDispatcher = vsyncSource->GetRefreshTimerVsyncDispatcher());
     mVsyncDispatcher->SetParentRefreshTimer(mVsyncObserver);
+    mVsyncRate = vsyncSource->GetGlobalDisplay().GetVsyncRate();
   }
 
   explicit VsyncRefreshDriverTimer(VsyncChild* aVsyncChild)
     : mVsyncChild(aVsyncChild)
   {
     MOZ_ASSERT(!XRE_IsParentProcess());
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(mVsyncChild);
     mVsyncObserver = new RefreshDriverVsyncObserver(this);
     mVsyncChild->SetVsyncObserver(mVsyncObserver);
+    mVsyncRate = mVsyncChild->GetVsyncRate();
+  }
+
+  virtual TimeDuration GetTimerRate() override
+  {
+    if (mVsyncRate != TimeDuration::Forever()) {
+      return mVsyncRate;
+    }
+
+    if (mVsyncChild) {
+      mVsyncRate = mVsyncChild->GetVsyncRate();
+    }
+
+    // If hardware queries fail / are unsupported, we have to just guess.
+    return mVsyncRate != TimeDuration::Forever()
+             ? mVsyncRate
+             : TimeDuration::FromMilliseconds(1000.0 / 60.0);
   }
 
 private:
   // Since VsyncObservers are refCounted, but the RefreshDriverTimer are
   // explicitly shutdown. We create an inner class that has the VsyncObserver
   // and is shutdown when the RefreshDriverTimer is deleted. The alternative is
   // to (a) make all RefreshDriverTimer RefCounted or (b) use different
   // VsyncObserver types.
@@ -453,17 +510,16 @@ private:
     }
 
     void OnTimerStart()
     {
       if (!XRE_IsParentProcess()) {
         mLastChildTick = TimeStamp::Now();
       }
     }
-
   private:
     virtual ~RefreshDriverVsyncObserver() {}
 
     void RecordTelemetryProbes(TimeStamp aVsyncTimestamp)
     {
       MOZ_ASSERT(NS_IsMainThread());
     #ifndef ANDROID  /* bug 1142079 */
       if (XRE_IsParentProcess()) {
@@ -608,16 +664,17 @@ private:
 
   RefPtr<RefreshDriverVsyncObserver> mVsyncObserver;
   // Used for parent process.
   RefPtr<RefreshTimerVsyncDispatcher> mVsyncDispatcher;
   // Used for child process.
   // The mVsyncChild will be always available before VsncChild::ActorDestroy().
   // After ActorDestroy(), StartTimer() and StopTimer() calls will be non-op.
   RefPtr<VsyncChild> mVsyncChild;
+  TimeDuration mVsyncRate;
 }; // VsyncRefreshDriverTimer
 
 /**
  * Since the content process takes some time to setup
  * the vsync IPC connection, this timer is used
  * during the intial startup process.
  * During initial startup, the refresh drivers
  * are ticked off this timer, and are swapped out once content
@@ -672,17 +729,17 @@ public:
   InactiveRefreshDriverTimer(double aRate, double aDisableAfterMilliseconds)
     : SimpleTimerBasedRefreshDriverTimer(aRate),
       mNextTickDuration(aRate),
       mDisableAfterMilliseconds(aDisableAfterMilliseconds),
       mNextDriverIndex(0)
   {
   }
 
-  virtual void AddRefreshDriver(nsRefreshDriver* aDriver)
+  virtual void AddRefreshDriver(nsRefreshDriver* aDriver) override
   {
     RefreshDriverTimer::AddRefreshDriver(aDriver);
 
     LOG("[%p] inactive timer got new refresh driver %p, resetting rate",
         this, aDriver);
 
     // reset the timer, and start with the newly added one next time.
     mNextTickDuration = mRateMilliseconds;
@@ -690,39 +747,44 @@ public:
     // we don't really have to start with the newly added one, but we may as well
     // not tick the old ones at the fastest rate any more than we need to.
     mNextDriverIndex = GetRefreshDriverCount() - 1;
 
     StopTimer();
     StartTimer();
   }
 
+  virtual TimeDuration GetTimerRate() override
+  {
+    return TimeDuration::FromMilliseconds(mNextTickDuration);
+  }
+
 protected:
   uint32_t GetRefreshDriverCount()
   {
     return mContentRefreshDrivers.Length() + mRootRefreshDrivers.Length();
   }
 
-  virtual void StartTimer()
+  virtual void StartTimer() override
   {
     mLastFireEpoch = JS_Now();
     mLastFireTime = TimeStamp::Now();
 
     mTargetTime = mLastFireTime + mRateDuration;
 
     uint32_t delay = static_cast<uint32_t>(mRateMilliseconds);
     mTimer->InitWithFuncCallback(TimerTickOne, this, delay, nsITimer::TYPE_ONE_SHOT);
   }
 
-  virtual void StopTimer()
+  virtual void StopTimer() override
   {
     mTimer->Cancel();
   }
 
-  virtual void ScheduleNextTick(TimeStamp aNowTime)
+  virtual void ScheduleNextTick(TimeStamp aNowTime) override
   {
     if (mDisableAfterMilliseconds > 0.0 &&
         mNextTickDuration > mDisableAfterMilliseconds)
     {
       // We hit the time after which we should disable
       // inactive window refreshes; don't schedule anything
       // until we get kicked by an AddRefreshDriver call.
       return;
@@ -747,24 +809,27 @@ protected:
   {
     int64_t jsnow = JS_Now();
     TimeStamp now = TimeStamp::Now();
 
     ScheduleNextTick(now);
 
     mLastFireEpoch = jsnow;
     mLastFireTime = now;
+    mLastFireSkipped = false;
 
     nsTArray<RefPtr<nsRefreshDriver> > drivers(mContentRefreshDrivers);
     drivers.AppendElements(mRootRefreshDrivers);
+    size_t index = mNextDriverIndex;
 
-    if (mNextDriverIndex < drivers.Length() &&
-        !drivers[mNextDriverIndex]->IsTestControllingRefreshesEnabled())
+    if (index < drivers.Length() &&
+        !drivers[index]->IsTestControllingRefreshesEnabled())
     {
-      TickDriver(drivers[mNextDriverIndex], jsnow, now);
+      TickDriver(drivers[index], jsnow, now);
+      mLastFireSkipped = mLastFireSkipped || drivers[index]->SkippedPaints();
     }
 
     mNextDriverIndex++;
   }
 
   static void TimerTickOne(nsITimer* aTimer, void* aClosure)
   {
     InactiveRefreshDriverTimer *timer = static_cast<InactiveRefreshDriverTimer*>(aClosure);
@@ -2223,16 +2288,34 @@ nsRefreshDriver::CancelPendingEvents(nsI
 {
   for (auto i : Reversed(MakeRange(mPendingEvents.Length()))) {
     if (mPendingEvents[i].mTarget->OwnerDoc() == aDocument) {
       mPendingEvents.RemoveElementAt(i);
     }
   }
 }
 
+/* static */ Maybe<TimeStamp>
+nsRefreshDriver::GetIdleDeadlineHint()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!sRegularRateTimer) {
+    return Nothing();
+  }
+
+  // For computing idleness of refresh drivers we only care about
+  // sRegularRateTimer, since we consider refresh drivers attached to
+  // sThrottledRateTimer to be inactive. This implies that tasks
+  // resulting from a tick on the sRegularRateTimer counts as being
+  // busy but tasks resulting from a tick on sThrottledRateTimer
+  // counts as being idle.
+  return sRegularRateTimer->GetIdleDeadlineHint();
+}
+
 void
 nsRefreshDriver::Disconnect()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   StopTimer();
 
   if (mPresContext) {
--- a/layout/base/nsRefreshDriver.h
+++ b/layout/base/nsRefreshDriver.h
@@ -324,33 +324,73 @@ public:
 
   bool IsWaitingForPaint(mozilla::TimeStamp aTime);
 
   // nsARefreshObserver
   NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override { return TransactionIdAllocator::AddRef(); }
   NS_IMETHOD_(MozExternalRefCountType) Release(void) override { return TransactionIdAllocator::Release(); }
   virtual void WillRefresh(mozilla::TimeStamp aTime) override;
 
+  /**
+   * Compute the time when the currently active refresh driver timer
+   * will start its next tick.
+   *
+   * Returns 'Nothing' if the refresh driver timer hasn't been
+   * initialized or if we can't tell when the next tick will happen.
+   *
+   * Returns Some(TimeStamp()), i.e. the null time, if the next tick is late.
+   *
+   * Otherwise returns Some(TimeStamp(t)), where t is the time of the next tick.
+   *
+   * Using these three types of return values it is possible to
+   * estimate three different things about the idleness of the
+   * currently active group of refresh drivers. This information is
+   * used by nsThread to schedule lower priority "idle tasks".
+   *
+   * The 'Nothing' return value indicates to nsThread that the
+   * currently active refresh drivers will be idle for a time
+   * significantly longer than the current refresh rate and that it is
+   * free to schedule longer periods for executing idle tasks. This is the
+   * expected result when we aren't animating.
+
+   * Returning the null time indicates to nsThread that we are very
+   * busy and that it should definitely not schedule idle tasks at
+   * all. This is the expected result when we are animating, but
+   * aren't able to keep up with the animation and hence need to skip
+   * paints. Since catching up to missed paints will happen as soon as
+   * possible, this is the expected result if any of the refresh
+   * drivers attached to the current refresh driver misses a paint.
+   *
+   * Returning Some(TimeStamp(t)) indicates to nsThread that we will
+   * be idle until. This is usually the case when we're animating
+   * without skipping paints.
+   */
+  static mozilla::Maybe<mozilla::TimeStamp> GetIdleDeadlineHint();
+
+  bool SkippedPaints() const
+  {
+    return mSkippedPaints;
+  }
+
 private:
   typedef nsTObserverArray<nsARefreshObserver*> ObserverArray;
   typedef nsTHashtable<nsISupportsHashKey> RequestTable;
   struct ImageStartData {
     ImageStartData()
     {
     }
 
     mozilla::Maybe<mozilla::TimeStamp> mStartTime;
     RequestTable mEntries;
   };
   typedef nsClassHashtable<nsUint32HashKey, ImageStartData> ImageStartTable;
 
   void DispatchPendingEvents();
   void DispatchAnimationEvents();
   void RunFrameRequestCallbacks(mozilla::TimeStamp aNowTime);
-
   void Tick(int64_t aNowEpoch, mozilla::TimeStamp aNowTime);
 
   enum EnsureTimerStartedFlags {
     eNone = 0,
     eForceAdjustTimer = 1 << 0,
     eAllowTimeToGoBackwards = 1 << 1,
     eNeverAdjustTimer = 1 << 2,
   };
--- a/layout/generic/nsContainerFrame.cpp
+++ b/layout/generic/nsContainerFrame.cpp
@@ -1778,25 +1778,25 @@ nsContainerFrame::PullNextInFlowChild(Co
     nsContainerFrame::ReparentFrameView(frame, nextInFlow, this);
   }
   return frame;
 }
 
 bool
 nsContainerFrame::ResolvedOrientationIsVertical()
 {
-  uint8_t orient = StyleDisplay()->mOrient;
+  StyleOrient orient = StyleDisplay()->mOrient;
   switch (orient) {
-    case NS_STYLE_ORIENT_HORIZONTAL:
+    case StyleOrient::Horizontal:
       return false;
-    case NS_STYLE_ORIENT_VERTICAL:
+    case StyleOrient::Vertical:
       return true;
-    case NS_STYLE_ORIENT_INLINE:
+    case StyleOrient::Inline:
       return GetWritingMode().IsVertical();
-    case NS_STYLE_ORIENT_BLOCK:
+    case StyleOrient::Block:
       return !GetWritingMode().IsVertical();
   }
   NS_NOTREACHED("unexpected -moz-orient value");
   return false;
 }
 
 // static
 bool
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -1781,20 +1781,20 @@ const KTableEntry nsCSSProps::kObjectFit
   { eCSSKeyword_contain,    NS_STYLE_OBJECT_FIT_CONTAIN },
   { eCSSKeyword_cover,      NS_STYLE_OBJECT_FIT_COVER },
   { eCSSKeyword_none,       NS_STYLE_OBJECT_FIT_NONE },
   { eCSSKeyword_scale_down, NS_STYLE_OBJECT_FIT_SCALE_DOWN },
   { eCSSKeyword_UNKNOWN,    -1 }
 };
 
 const KTableEntry nsCSSProps::kOrientKTable[] = {
-  { eCSSKeyword_inline,     NS_STYLE_ORIENT_INLINE },
-  { eCSSKeyword_block,      NS_STYLE_ORIENT_BLOCK },
-  { eCSSKeyword_horizontal, NS_STYLE_ORIENT_HORIZONTAL },
-  { eCSSKeyword_vertical,   NS_STYLE_ORIENT_VERTICAL },
+  { eCSSKeyword_inline,     StyleOrient::Inline },
+  { eCSSKeyword_block,      StyleOrient::Block },
+  { eCSSKeyword_horizontal, StyleOrient::Horizontal },
+  { eCSSKeyword_vertical,   StyleOrient::Vertical },
   { eCSSKeyword_UNKNOWN,    -1 }
 };
 
 // Same as kBorderStyleKTable except 'hidden'.
 const KTableEntry nsCSSProps::kOutlineStyleKTable[] = {
   { eCSSKeyword_none,   NS_STYLE_BORDER_STYLE_NONE },
   { eCSSKeyword_auto,   NS_STYLE_BORDER_STYLE_AUTO },
   { eCSSKeyword_dotted, NS_STYLE_BORDER_STYLE_DOTTED },
@@ -2124,19 +2124,19 @@ const KTableEntry nsCSSProps::kUserInput
   { eCSSKeyword_none,     StyleUserInput::None },
   { eCSSKeyword_enabled,  StyleUserInput::Enabled },
   { eCSSKeyword_disabled, StyleUserInput::Disabled },
   { eCSSKeyword_auto,     StyleUserInput::Auto },
   { eCSSKeyword_UNKNOWN,  -1 }
 };
 
 const KTableEntry nsCSSProps::kUserModifyKTable[] = {
-  { eCSSKeyword_read_only,  NS_STYLE_USER_MODIFY_READ_ONLY },
-  { eCSSKeyword_read_write, NS_STYLE_USER_MODIFY_READ_WRITE },
-  { eCSSKeyword_write_only, NS_STYLE_USER_MODIFY_WRITE_ONLY },
+  { eCSSKeyword_read_only,  StyleUserModify::ReadOnly },
+  { eCSSKeyword_read_write, StyleUserModify::ReadWrite },
+  { eCSSKeyword_write_only, StyleUserModify::WriteOnly },
   { eCSSKeyword_UNKNOWN,    -1 }
 };
 
 const KTableEntry nsCSSProps::kUserSelectKTable[] = {
   { eCSSKeyword_none,       StyleUserSelect::None },
   { eCSSKeyword_auto,       StyleUserSelect::Auto },
   { eCSSKeyword_text,       StyleUserSelect::Text },
   { eCSSKeyword_element,    StyleUserSelect::Element },
@@ -2184,19 +2184,19 @@ const KTableEntry nsCSSProps::kWidthKTab
   { eCSSKeyword__moz_max_content, NS_STYLE_WIDTH_MAX_CONTENT },
   { eCSSKeyword__moz_min_content, NS_STYLE_WIDTH_MIN_CONTENT },
   { eCSSKeyword__moz_fit_content, NS_STYLE_WIDTH_FIT_CONTENT },
   { eCSSKeyword__moz_available, NS_STYLE_WIDTH_AVAILABLE },
   { eCSSKeyword_UNKNOWN, -1 }
 };
 
 const KTableEntry nsCSSProps::kWindowDraggingKTable[] = {
-  { eCSSKeyword_default, NS_STYLE_WINDOW_DRAGGING_DEFAULT },
-  { eCSSKeyword_drag, NS_STYLE_WINDOW_DRAGGING_DRAG },
-  { eCSSKeyword_no_drag, NS_STYLE_WINDOW_DRAGGING_NO_DRAG },
+  { eCSSKeyword_default, StyleWindowDragging::Default },
+  { eCSSKeyword_drag, StyleWindowDragging::Drag },
+  { eCSSKeyword_no_drag, StyleWindowDragging::NoDrag },
   { eCSSKeyword_UNKNOWN, -1 }
 };
 
 const KTableEntry nsCSSProps::kWindowShadowKTable[] = {
   { eCSSKeyword_none, NS_STYLE_WINDOW_SHADOW_NONE },
   { eCSSKeyword_default, NS_STYLE_WINDOW_SHADOW_DEFAULT },
   { eCSSKeyword_menu, NS_STYLE_WINDOW_SHADOW_MENU },
   { eCSSKeyword_tooltip, NS_STYLE_WINDOW_SHADOW_TOOLTIP },
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -1413,16 +1413,19 @@ struct SetEnumValueHelper
   DEFINE_ENUM_CLASS_SETTER(StyleBoxSizing, Content, Border)
   DEFINE_ENUM_CLASS_SETTER(StyleClear, None, Both)
   DEFINE_ENUM_CLASS_SETTER(StyleFillRule, Nonzero, Evenodd)
   DEFINE_ENUM_CLASS_SETTER(StyleFloat, None, InlineEnd)
   DEFINE_ENUM_CLASS_SETTER(StyleFloatEdge, ContentBox, MarginBox)
   DEFINE_ENUM_CLASS_SETTER(StyleUserFocus, None, SelectMenu)
   DEFINE_ENUM_CLASS_SETTER(StyleUserSelect, None, MozText)
   DEFINE_ENUM_CLASS_SETTER(StyleUserInput, None, Auto)
+  DEFINE_ENUM_CLASS_SETTER(StyleUserModify, ReadOnly, WriteOnly)
+  DEFINE_ENUM_CLASS_SETTER(StyleWindowDragging, Default, NoDrag)
+  DEFINE_ENUM_CLASS_SETTER(StyleOrient, Inline, Vertical)
 #ifdef MOZ_XUL
   DEFINE_ENUM_CLASS_SETTER(StyleDisplay, None, Popup)
 #else
   DEFINE_ENUM_CLASS_SETTER(StyleDisplay, None, InlineBox)
 #endif
 
 #undef DEF_SET_ENUMERATED_VALUE
 };
@@ -5115,17 +5118,17 @@ nsRuleNode::ComputeUserInterfaceData(voi
            parentUI->mUserInput,
            StyleUserInput::Auto);
 
   // user-modify: enum, inherit, initial
   SetValue(*aRuleData->ValueForUserModify(),
            ui->mUserModify, conditions,
            SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
            parentUI->mUserModify,
-           NS_STYLE_USER_MODIFY_READ_ONLY);
+           StyleUserModify::ReadOnly);
 
   // user-focus: enum, inherit, initial
   SetValue(*aRuleData->ValueForUserFocus(),
            ui->mUserFocus, conditions,
            SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
            parentUI->mUserFocus,
            StyleUserFocus::None);
 
@@ -5170,17 +5173,17 @@ nsRuleNode::ComputeUIResetData(void* aSt
            SETVAL_INTEGER | SETVAL_UNSET_INITIAL,
            parentUI->mForceBrokenImageIcon, 0);
 
   // -moz-window-dragging: enum, inherit, initial
   SetValue(*aRuleData->ValueForWindowDragging(),
            ui->mWindowDragging, conditions,
            SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
            parentUI->mWindowDragging,
-           NS_STYLE_WINDOW_DRAGGING_DEFAULT);
+           StyleWindowDragging::Default);
 
   // -moz-window-shadow: enum, inherit, initial
   SetValue(*aRuleData->ValueForWindowShadow(),
            ui->mWindowShadow, conditions,
            SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
            parentUI->mWindowShadow,
            NS_STYLE_WINDOW_SHADOW_DEFAULT);
 
@@ -6447,17 +6450,17 @@ nsRuleNode::ComputeDisplayData(void* aSt
            parentDisplay->mTransformBox,
            NS_STYLE_TRANSFORM_BOX_BORDER_BOX);
 
   // orient: enum, inherit, initial
   SetValue(*aRuleData->ValueForOrient(),
            display->mOrient, conditions,
            SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
            parentDisplay->mOrient,
-           NS_STYLE_ORIENT_INLINE);
+           StyleOrient::Inline);
 
   // shape-outside: none | [ <basic-shape> || <shape-box> ] | <image>
   const nsCSSValue* shapeOutsideValue = aRuleData->ValueForShapeOutside();
   switch (shapeOutsideValue->GetUnit()) {
     case eCSSUnit_Null:
       break;
     case eCSSUnit_None:
     case eCSSUnit_Initial:
--- a/layout/style/nsStyleConsts.h
+++ b/layout/style/nsStyleConsts.h
@@ -215,30 +215,36 @@ enum class StyleUserSelect : uint8_t {
 enum class StyleUserInput : uint8_t {
   None,
   Enabled,
   Disabled,
   Auto,
 };
 
 // user-modify
-#define NS_STYLE_USER_MODIFY_READ_ONLY   0
-#define NS_STYLE_USER_MODIFY_READ_WRITE  1
-#define NS_STYLE_USER_MODIFY_WRITE_ONLY  2
+enum class StyleUserModify : uint8_t {
+  ReadOnly,
+  ReadWrite,
+  WriteOnly,
+};
 
 // -moz-window-dragging
-#define NS_STYLE_WINDOW_DRAGGING_DEFAULT 0
-#define NS_STYLE_WINDOW_DRAGGING_DRAG    1
-#define NS_STYLE_WINDOW_DRAGGING_NO_DRAG 2
+enum class StyleWindowDragging : uint8_t {
+  Default,
+  Drag,
+  NoDrag,
+};
 
 // orient
-#define NS_STYLE_ORIENT_INLINE     0
-#define NS_STYLE_ORIENT_BLOCK      1
-#define NS_STYLE_ORIENT_HORIZONTAL 2
-#define NS_STYLE_ORIENT_VERTICAL   3
+enum class StyleOrient : uint8_t {
+  Inline,
+  Block,
+  Horizontal,
+  Vertical,
+};
 
 #define NS_RADIUS_FARTHEST_SIDE 0
 #define NS_RADIUS_CLOSEST_SIDE  1
 
 // stack-sizing
 #define NS_STYLE_STACK_SIZING_IGNORE         0
 #define NS_STYLE_STACK_SIZING_STRETCH_TO_FIT 1
 
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -3128,17 +3128,17 @@ nsStyleDisplay::nsStyleDisplay(StyleStru
   , mBreakType(StyleClear::None)
   , mBreakInside(NS_STYLE_PAGE_BREAK_AUTO)
   , mBreakBefore(false)
   , mBreakAfter(false)
   , mOverflowX(NS_STYLE_OVERFLOW_VISIBLE)
   , mOverflowY(NS_STYLE_OVERFLOW_VISIBLE)
   , mOverflowClipBox(NS_STYLE_OVERFLOW_CLIP_BOX_PADDING_BOX)
   , mResize(NS_STYLE_RESIZE_NONE)
-  , mOrient(NS_STYLE_ORIENT_INLINE)
+  , mOrient(StyleOrient::Inline)
   , mIsolation(NS_STYLE_ISOLATION_AUTO)
   , mTopLayer(NS_STYLE_TOP_LAYER_NONE)
   , mWillChangeBitField(0)
   , mTouchAction(NS_STYLE_TOUCH_ACTION_AUTO)
   , mScrollBehavior(NS_STYLE_SCROLL_BEHAVIOR_AUTO)
   , mScrollSnapTypeX(NS_STYLE_SCROLL_SNAP_TYPE_NONE)
   , mScrollSnapTypeY(NS_STYLE_SCROLL_SNAP_TYPE_NONE)
   , mScrollSnapPointsX(eStyleUnit_None)
@@ -4005,17 +4005,17 @@ nsCursorImage::operator==(const nsCursor
   return mHaveHotspot == aOther.mHaveHotspot &&
          mHotspotX == aOther.mHotspotX &&
          mHotspotY == aOther.mHotspotY &&
          EqualImages(mImage, aOther.mImage);
 }
 
 nsStyleUserInterface::nsStyleUserInterface(StyleStructContext aContext)
   : mUserInput(StyleUserInput::Auto)
-  , mUserModify(NS_STYLE_USER_MODIFY_READ_ONLY)
+  , mUserModify(StyleUserModify::ReadOnly)
   , mUserFocus(StyleUserFocus::None)
   , mPointerEvents(NS_STYLE_POINTER_EVENTS_AUTO)
   , mCursor(NS_STYLE_CURSOR_AUTO)
 {
   MOZ_COUNT_CTOR(nsStyleUserInterface);
 }
 
 nsStyleUserInterface::nsStyleUserInterface(const nsStyleUserInterface& aSource)
@@ -4079,17 +4079,17 @@ nsStyleUserInterface::CalcDifference(con
 //-----------------------
 // nsStyleUIReset
 //
 
 nsStyleUIReset::nsStyleUIReset(StyleStructContext aContext)
   : mUserSelect(StyleUserSelect::Auto)
   , mForceBrokenImageIcon(0)
   , mIMEMode(NS_STYLE_IME_MODE_AUTO)
-  , mWindowDragging(NS_STYLE_WINDOW_DRAGGING_DEFAULT)
+  , mWindowDragging(StyleWindowDragging::Default)
   , mWindowShadow(NS_STYLE_WINDOW_SHADOW_DEFAULT)
 {
   MOZ_COUNT_CTOR(nsStyleUIReset);
 }
 
 nsStyleUIReset::nsStyleUIReset(const nsStyleUIReset& aSource)
   : mUserSelect(aSource.mUserSelect)
   , mForceBrokenImageIcon(aSource.mForceBrokenImageIcon)
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -2815,17 +2815,17 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
   mozilla::StyleClear mBreakType;  // [reset]
   uint8_t mBreakInside;         // [reset] NS_STYLE_PAGE_BREAK_AUTO/AVOID
   bool mBreakBefore;    // [reset]
   bool mBreakAfter;     // [reset]
   uint8_t mOverflowX;           // [reset] see nsStyleConsts.h
   uint8_t mOverflowY;           // [reset] see nsStyleConsts.h
   uint8_t mOverflowClipBox;     // [reset] see nsStyleConsts.h
   uint8_t mResize;              // [reset] see nsStyleConsts.h
-  uint8_t mOrient;              // [reset] see nsStyleConsts.h
+  mozilla::StyleOrient mOrient; // [reset] see nsStyleConsts.h
   uint8_t mIsolation;           // [reset] see nsStyleConsts.h
   uint8_t mTopLayer;            // [reset] see nsStyleConsts.h
   uint8_t mWillChangeBitField;  // [reset] see nsStyleConsts.h. Stores a
                                 // bitfield representation of the properties
                                 // that are frequently queried. This should
                                 // match mWillChange. Also tracks if any of the
                                 // properties in the will-change list require
                                 // a stacking context.
@@ -3317,21 +3317,21 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
   static nsChangeHint DifferenceAlwaysHandledForDescendants() {
     // CalcDifference never returns the reflow hints that are sometimes
     // handled for descendants as hints not handled for descendants.
     return nsChangeHint_NeedReflow |
            nsChangeHint_ReflowChangesSizeOrPosition |
            nsChangeHint_ClearAncestorIntrinsics;
   }
 
-  mozilla::StyleUserSelect mUserSelect;   // [reset] (selection-style)
-  uint8_t   mForceBrokenImageIcon; // [reset]  (0 if not forcing, otherwise forcing)
-  uint8_t   mIMEMode;         // [reset]
-  uint8_t   mWindowDragging;  // [reset]
-  uint8_t   mWindowShadow;    // [reset]
+  mozilla::StyleUserSelect     mUserSelect;     // [reset](selection-style)
+  uint8_t mForceBrokenImageIcon; // [reset] (0 if not forcing, otherwise forcing)
+  uint8_t                      mIMEMode;        // [reset]
+  mozilla::StyleWindowDragging mWindowDragging; // [reset]
+  uint8_t                      mWindowShadow;   // [reset]
 };
 
 struct nsCursorImage
 {
   bool mHaveHotspot;
   float mHotspotX, mHotspotY;
 
   nsCursorImage();
@@ -3393,17 +3393,17 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
   }
   static nsChangeHint DifferenceAlwaysHandledForDescendants() {
     // CalcDifference never returns the reflow hints that are sometimes
     // handled for descendants as hints not handled for descendants.
     return nsChangeHint_NeedReflow;
   }
 
   mozilla::StyleUserInput   mUserInput;       // [inherited]
-  uint8_t                   mUserModify;      // [inherited] (modify-content)
+  mozilla::StyleUserModify  mUserModify;      // [inherited] (modify-content)
   mozilla::StyleUserFocus   mUserFocus;       // [inherited] (auto-select)
   uint8_t                   mPointerEvents;   // [inherited] see nsStyleConsts.h
 
   uint8_t mCursor;                            // [inherited] See nsStyleConsts.h
   nsTArray<nsCursorImage> mCursorImages;      // [inherited] images and coords
 
   inline uint8_t GetEffectivePointerEvents(nsIFrame* aFrame) const;
 };
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -183,16 +183,23 @@ pref("dom.permissions.revoke.enable", fa
 
 // Enable Performance Observer API
 #ifdef NIGHTLY_BUILD
 pref("dom.enable_performance_observer", true);
 #else
 pref("dom.enable_performance_observer", false);
 #endif
 
+// Enable requestIdleCallback API
+#ifdef NIGHTLY_BUILD
+pref("dom.requestIdleCallback.enabled", true);
+#else
+pref("dom.requestIdleCallback.enabled", false);
+#endif
+
 // Whether the Gamepad API is enabled
 pref("dom.gamepad.enabled", true);
 pref("dom.gamepad.test.enabled", false);
 #ifdef RELEASE_OR_BETA
 pref("dom.gamepad.non_standard_events.enabled", false);
 #else
 pref("dom.gamepad.non_standard_events.enabled", true);
 #endif
@@ -2687,16 +2694,24 @@ pref("layout.display-list.dump-content",
 pref("layout.frame_rate.precise", false);
 
 // pref to control whether layout warnings that are hit quite often are enabled
 pref("layout.spammy_warnings.enabled", false);
 
 // Should we fragment floats inside CSS column layout?
 pref("layout.float-fragments-inside-column.enabled", true);
 
+// The number of frames times the frame rate is the time required to
+// pass without painting used to guess that we'll not paint again soon
+pref("layout.idle_period.required_quiescent_frames", 2);
+
+// The amount of time (milliseconds) needed between an idle period's
+// end and the start of the next tick to avoid jank.
+pref("layout.idle_period.time_limit", 3);
+
 // Is support for the Web Animations API enabled?
 // Before enabling this by default, make sure also CSSPseudoElement interface
 // has been spec'ed properly, or we should add a separate pref for
 // CSSPseudoElement interface. See Bug 1174575 for further details.
 #ifdef RELEASE_OR_BETA
 pref("dom.animations-api.core.enabled", false);
 #else
 pref("dom.animations-api.core.enabled", true);
@@ -2736,16 +2751,22 @@ pref("dom.max_chrome_script_run_time", 2
 pref("dom.max_script_run_time", 10);
 
 // Stop all scripts in a compartment when the "stop script" dialog is used.
 pref("dom.global_stop_script", true);
 
 // If true, ArchiveReader will be enabled
 pref("dom.archivereader.enabled", false);
 
+// Time (milliseconds) between throttled idle callbacks.
+pref("dom.idle_period.throttled_length", 10000);
+
+// The amount of idle time (milliseconds) reserved for a long idle period
+pref("idle_queue.long_period", 50);
+
 // Hang monitor timeout after which we kill the browser, in seconds
 // (0 is disabled)
 // Disabled on all platforms per bug 705748 until the found issues are
 // resolved.
 pref("hangmonitor.timeout", 0);
 
 pref("plugins.load_appdir_plugins", false);
 // If true, plugins will be click to play
--- a/security/manager/locales/en-US/chrome/pippki/pippki.properties
+++ b/security/manager/locales/en-US/chrome/pippki/pippki.properties
@@ -4,16 +4,18 @@
 
 CertPassPrompt=Please enter the Personal Security Password for the PSM Private Keys security device.
 
 # LOCALIZATION NOTE(certWithSerial): Used for semi-uniquely representing a cert.
 # %1$S is the serial number of the cert in AA:BB:CC hex format.
 certWithSerial=Certificate with serial number: %1$S
 
 # Download Cert dialog
+# LOCALIZATION NOTE(newCAMessage1):
+# %S is a string representative of the certificate being downloaded/imported.
 newCAMessage1=Do you want to trust ā€œ%Sā€ for the following purposes?
 unnamedCA=Certificate Authority (unnamed)
 
 # For editing cert trust
 editTrustCA=The certificate ā€œ%Sā€ represents a Certificate Authority.
 
 # For Deleting Certificates
 deleteSslCertConfirm3=Are you sure you want to delete these server exceptions?
--- a/security/manager/pki/nsNSSDialogs.cpp
+++ b/security/manager/pki/nsNSSDialogs.cpp
@@ -86,71 +86,87 @@ nsNSSDialogs::SetPassword(nsIInterfaceRe
   rv = block->GetInt(1, &status);
   if (NS_FAILED(rv)) return rv;
 
   *_canceled = (status == 0)?true:false;
 
   return rv;
 }
 
-NS_IMETHODIMP 
-nsNSSDialogs::ConfirmDownloadCACert(nsIInterfaceRequestor *ctx, 
-                                    nsIX509Cert *cert,
-                                    uint32_t *_trust,
-                                    bool *_retval)
+NS_IMETHODIMP
+nsNSSDialogs::ConfirmDownloadCACert(nsIInterfaceRequestor* ctx,
+                                    nsIX509Cert* cert,
+                            /*out*/ uint32_t* trust,
+                            /*out*/ bool* importConfirmed)
 {
-  nsresult rv;
+  // |ctx| is allowed to be null.
+  NS_ENSURE_ARG(cert);
+  NS_ENSURE_ARG(trust);
+  NS_ENSURE_ARG(importConfirmed);
 
-  nsCOMPtr<nsIMutableArray> dlgArray = nsArrayBase::Create();
-  if (!dlgArray) {
+  nsCOMPtr<nsIMutableArray> argArray = nsArrayBase::Create();
+  if (!argArray) {
     return NS_ERROR_FAILURE;
   }
-  rv = dlgArray->AppendElement(cert, false);
+
+  nsresult rv = argArray->AppendElement(cert, false);
   if (NS_FAILED(rv)) {
     return rv;
   }
-  nsCOMPtr<nsIDialogParamBlock> dlgParamBlock(
-    do_CreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID));
-  if (!dlgParamBlock) {
-    return NS_ERROR_FAILURE;
-  }
-  rv = dlgParamBlock->SetObjects(dlgArray);
+
+  nsCOMPtr<nsIWritablePropertyBag2> retVals = new nsHashPropertyBag();
+  rv = argArray->AppendElement(retVals, false);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   // Get the parent window for the dialog
   nsCOMPtr<mozIDOMWindowProxy> parent = do_GetInterface(ctx);
-  rv = nsNSSDialogHelper::openDialog(parent, 
+  rv = nsNSSDialogHelper::openDialog(parent,
                                      "chrome://pippki/content/downloadcert.xul",
-                                     dlgParamBlock);
+                                     argArray);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  rv = retVals->GetPropertyAsBool(NS_LITERAL_STRING("importConfirmed"),
+                                  importConfirmed);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  int32_t status;
-  int32_t ssl, email, objsign;
+  *trust = nsIX509CertDB::UNTRUSTED;
+  if (!*importConfirmed) {
+    return NS_OK;
+  }
 
-  rv = dlgParamBlock->GetInt(1, &status);
-  if (NS_FAILED(rv)) return rv;
-  rv = dlgParamBlock->GetInt(2, &ssl);
-  if (NS_FAILED(rv)) return rv;
-  rv = dlgParamBlock->GetInt(3, &email);
-  if (NS_FAILED(rv)) return rv;
-  rv = dlgParamBlock->GetInt(4, &objsign);
-  if (NS_FAILED(rv)) return rv;
- 
-  *_trust = nsIX509CertDB::UNTRUSTED;
-  *_trust |= (ssl) ? nsIX509CertDB::TRUSTED_SSL : 0;
-  *_trust |= (email) ? nsIX509CertDB::TRUSTED_EMAIL : 0;
-  *_trust |= (objsign) ? nsIX509CertDB::TRUSTED_OBJSIGN : 0;
+  bool trustForSSL = false;
+  rv = retVals->GetPropertyAsBool(NS_LITERAL_STRING("trustForSSL"),
+                                  &trustForSSL);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  bool trustForEmail = false;
+  rv = retVals->GetPropertyAsBool(NS_LITERAL_STRING("trustForEmail"),
+                                  &trustForEmail);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  bool trustForObjSign = false;
+  rv = retVals->GetPropertyAsBool(NS_LITERAL_STRING("trustForObjSign"),
+                                  &trustForObjSign);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
 
-  *_retval = (status != 0);
+  *trust |= trustForSSL ? nsIX509CertDB::TRUSTED_SSL : 0;
+  *trust |= trustForEmail ? nsIX509CertDB::TRUSTED_EMAIL : 0;
+  *trust |= trustForObjSign ? nsIX509CertDB::TRUSTED_OBJSIGN : 0;
 
-  return rv;
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNSSDialogs::ChooseCertificate(nsIInterfaceRequestor* ctx,
                                 const nsACString& hostname,
                                 int32_t port,
                                 const nsACString& organization,
                                 const nsACString& issuerOrg,
--- a/security/manager/pki/resources/content/downloadcert.js
+++ b/security/manager/pki/resources/content/downloadcert.js
@@ -1,55 +1,92 @@
 /* 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/. */
 /* import-globals-from pippki.js */
 "use strict";
 
-const nsIDialogParamBlock = Components.interfaces.nsIDialogParamBlock;
-const nsIX509Cert = Components.interfaces.nsIX509Cert;
-
-var params;
-var caName;
-var cert;
+/**
+ * @file Implements the functionality of downloadcert.xul: a dialog that allows
+ *       a user to confirm whether to import a certificate, and if so what trust
+ *       to give it.
+ * @argument {nsISupports} window.arguments[0]
+ *           Certificate to confirm import of, queryable to nsIX509Cert.
+ * @argument {nsISupports} window.arguments[1]
+ *           Object to set the return values of calling the dialog on, queryable
+ *           to the underlying type of DownloadCertReturnValues.
+ */
 
-function onLoad()
-{
-  params = window.arguments[0].QueryInterface(nsIDialogParamBlock);
-  cert = params.objects.queryElementAt(0, nsIX509Cert);
+/**
+ * @typedef DownloadCertReturnValues
+ * @type nsIWritablePropertyBag2
+ * @property {Boolean} importConfirmed
+ *           Set to true if the user confirmed import of the cert and accepted
+ *           the dialog, false otherwise.
+ * @property {Boolean} trustForSSL
+ *           Set to true if the cert should be trusted for SSL, false otherwise.
+ *           Undefined value if |importConfirmed| is not true.
+ * @property {Boolean} trustForEmail
+ *           Set to true if the cert should be trusted for e-mail, false
+ *           otherwise. Undefined value if |importConfirmed| is not true.
+ * @property {Boolean} trustForObjSign
+ *           Set to true if the cert should be trusted for object signing, false
+ *           otherwise. Undefined value if |importConfirmed| is not true.
+ */
 
-  var bundle = document.getElementById("pippki_bundle");
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+/**
+ * The cert to potentially import.
+ * @type nsIX509Cert
+ */
+var gCert;
 
-  caName = cert.commonName;
+/**
+ * onload() handler.
+ */
+function onLoad() {
+  gCert = window.arguments[0].QueryInterface(Ci.nsIX509Cert);
+
+  let bundle = document.getElementById("pippki_bundle");
+  let caName = gCert.commonName;
   if (caName.length == 0) {
     caName = bundle.getString("unnamedCA");
   }
 
-  var message2 = bundle.getFormattedString("newCAMessage1", [caName]);
-  setText("message2", message2);
+  setText("trustHeader", bundle.getFormattedString("newCAMessage1", [caName]));
 }
 
-function viewCert()
-{
-  viewCertHelper(window, cert);
+/**
+ * Handler for the "View Cert" button.
+ */
+function viewCert() {
+  viewCertHelper(window, gCert);
 }
 
-function doOK()
-{
+/**
+ * ondialogaccept() handler.
+ *
+ * @returns {Boolean} true to make the dialog close, false otherwise.
+ */
+function onDialogAccept() {
   let checkSSL = document.getElementById("trustSSL");
   let checkEmail = document.getElementById("trustEmail");
   let checkObjSign = document.getElementById("trustObjSign");
 
-  // Signal which trust bits the user wanted to enable.
-  params.SetInt(2, checkSSL.checked ? 1 : 0);
-  params.SetInt(3, checkEmail.checked ? 1 : 0);
-  params.SetInt(4, checkObjSign.checked ? 1 : 0);
-
-  // Signal that the user accepted.
-  params.SetInt(1, 1);
+  let retVals = window.arguments[1].QueryInterface(Ci.nsIWritablePropertyBag2);
+  retVals.setPropertyAsBool("importConfirmed", true);
+  retVals.setPropertyAsBool("trustForSSL", checkSSL.checked);
+  retVals.setPropertyAsBool("trustForEmail", checkEmail.checked);
+  retVals.setPropertyAsBool("trustForObjSign", checkObjSign.checked);
   return true;
 }
 
-function doCancel()
-{
-  params.SetInt(1, 0); // Signal that the user cancelled.
+/**
+ * ondialogcancel() handler.
+ *
+ * @returns {Boolean} true to make the dialog close, false otherwise.
+ */
+function onDialogCancel() {
+  let retVals = window.arguments[1].QueryInterface(Ci.nsIWritablePropertyBag2);
+  retVals.setPropertyAsBool("importConfirmed", false);
   return true;
 }
--- a/security/manager/pki/resources/content/downloadcert.xul
+++ b/security/manager/pki/resources/content/downloadcert.xul
@@ -2,23 +2,24 @@
 <!-- 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/. -->
 
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
 
 <!DOCTYPE dialog SYSTEM "chrome://pippki/locale/pippki.dtd">
 
-<dialog id="download_cert" title="&downloadCert.title;"
-  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"      
-  style="width: 46em;"
-  buttons="accept,cancel"
-  ondialogaccept="return doOK();"
-  ondialogcancel="return doCancel();"
-  onload="onLoad();">
+<dialog id="download_cert"
+        title="&downloadCert.title;"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        style="width: 46em;"
+        buttons="accept,cancel"
+        ondialogaccept="return onDialogAccept();"
+        ondialogcancel="return onDialogCancel();"
+        onload="onLoad();">
 
 <stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/>
 
 <script type="application/javascript" src="chrome://pippki/content/downloadcert.js"/>
 <script type="application/javascript" src="chrome://pippki/content/pippki.js"/>
 
 
   <!--  Let 'em know what they're doing -->
@@ -30,44 +31,39 @@
 
   <!--  checkboxes for trust bits
      -  "do you want to?"
      -  * trust for SSL
      -  * trust for email
      -  * trust for object signing
     -->
   <vbox>
-    <description id="message2"/>
+    <description id="trustHeader"/>
     <checkbox label="&downloadCert.trustSSL;"
               id="trustSSL"/>
     <checkbox label="&downloadCert.trustEmail;"
               id="trustEmail"/>
     <checkbox label="&downloadCert.trustObjSign;"
               id="trustObjSign"/>
   </vbox>
 
   <separator/>
 
-  <!--  buttons for viewing cert and policies
-     -  "suggested you view the following:"
-     -  <>  view cert
-     -  <>  view policy
-    -->
   <vbox>
     <description>&downloadCert.message3;</description>
     <separator/>
     <grid>
       <columns>
         <column/>
         <column/>
       </columns>
       <rows>
         <row>
           <button id="viewC-button"
                   label="&downloadCert.viewCert.label;"
-                  oncommand="viewCert();"/> 
+                  oncommand="viewCert();"/>
           <description style="margin: 4px;">&downloadCert.viewCert.text;</description>
         </row>
       </rows>
     </grid>
   </vbox>
 
 </dialog>
--- a/security/manager/ssl/tests/mochitest/browser/browser.ini
+++ b/security/manager/ssl/tests/mochitest/browser/browser.ini
@@ -5,12 +5,13 @@ support-files =
   *.pem
 
 [browser_bug627234_perwindowpb.js]
 [browser_certificateManagerLeak.js]
 [browser_certViewer.js]
 [browser_clientAuth_connection.js]
 [browser_clientAuth_ui.js]
 [browser_deleteCert_ui.js]
+[browser_downloadCert_ui.js]
 [browser_editCACertTrust.js]
 # An earlier attempting at landing this test resulted in frequent intermittent
 # failures, almost entirely on Linux.
 skip-if = os == "linux"
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/browser_downloadCert_ui.js
@@ -0,0 +1,150 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+"use strict";
+
+// Tests that the cert download/import UI correctly identifies the cert being
+// downloaded, and allows the trust of the cert to be specified.
+
+const { MockRegistrar } =
+  Cu.import("resource://testing-common/MockRegistrar.jsm", {});
+
+/**
+ * @typedef {TestCase}
+ * @type Object
+ * @property {String} certFilename
+ *           Filename of the cert for this test case.
+ * @property {String} expectedDisplayString
+ *           The string we expect the UI to display to represent the given cert.
+ * @property {nsIX509Cert} cert
+ *           Handle to the cert once read in setup().
+ */
+
+/**
+ * A list of test cases representing certs that get "downloaded".
+ * @type TestCase[]
+ */
+const TEST_CASES = [
+  { certFilename: "has-cn.pem",
+    expectedDisplayString: "Foo",
+    cert: null },
+  { certFilename: "has-empty-subject.pem",
+    expectedDisplayString: "Certificate Authority (unnamed)",
+    cert: null },
+];
+
+/**
+ * Opens the cert download dialog.
+ *
+ * @param {nsIX509Cert} cert
+ *        The cert to pass to the dialog for display.
+ * @returns {Promise}
+ *          A promise that resolves when the dialog has finished loading, with
+ *          an array consisting of:
+ *            1. The window of the opened dialog.
+ *            2. The return value nsIWritablePropertyBag2 passed to the dialog.
+ */
+function openCertDownloadDialog(cert) {
+  let returnVals = Cc["@mozilla.org/hash-property-bag;1"]
+                     .createInstance(Ci.nsIWritablePropertyBag2);
+  let win = window.openDialog("chrome://pippki/content/downloadcert.xul", "",
+                              "", cert, returnVals);
+  return new Promise((resolve, reject) => {
+    win.addEventListener("load", function onLoad() {
+      win.removeEventListener("load", onLoad);
+      resolve([win, returnVals]);
+    });
+  });
+}
+
+// Mock implementation of nsICertificateDialogs.
+const gCertificateDialogs = {
+  expectedCert: null,
+  viewCertCallCount: 0,
+  confirmDownloadCACert(ctx, cert, trust) {
+    Assert.ok(false, "confirmDownloadCACert() should not have been called");
+  },
+  setPKCS12FilePassword(ctx, password) {
+    Assert.ok(false, "setPKCS12FilePassword() should not have been called");
+  },
+  getPKCS12FilePassword(ctx, password) {
+    Assert.ok(false, "getPKCS12FilePassword() should not have been called");
+  },
+  viewCert(ctx, cert) {
+    this.viewCertCallCount++;
+    Assert.notEqual(cert, null, "Cert to view should not be null");
+    Assert.equal(cert, this.expectedCert,
+                 "Actual and expected cert should match");
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsICertificateDialogs])
+};
+
+add_task(function* setup() {
+  for (let testCase of TEST_CASES) {
+    testCase.cert = yield readCertificate(testCase.certFilename, ",,");
+    Assert.notEqual(testCase.cert, null,
+                    `'${testCase.certFilename}' should have been read`);
+  }
+
+  let certificateDialogsCID =
+    MockRegistrar.register("@mozilla.org/nsCertificateDialogs;1",
+                           gCertificateDialogs);
+  registerCleanupFunction(() => {
+    MockRegistrar.unregister(certificateDialogsCID);
+  });
+});
+
+// Test that the trust header message corresponds to the provided cert, and that
+// the View Cert button launches the cert viewer for the provided cert.
+add_task(function* testTrustHeaderAndViewCertButton() {
+  for (let testCase of TEST_CASES) {
+    let [win, retVals] = yield openCertDownloadDialog(testCase.cert);
+    let expectedTrustHeaderString =
+      `Do you want to trust \u201C${testCase.expectedDisplayString}\u201D ` +
+      "for the following purposes?";
+    Assert.equal(win.document.getElementById("trustHeader").textContent,
+                 expectedTrustHeaderString,
+                 "Actual and expected trust header text should match for " +
+                 `${testCase.certFilename}`);
+
+    gCertificateDialogs.viewCertCallCount = 0;
+    gCertificateDialogs.expectedCert = testCase.cert;
+    info("Pressing View Cert button");
+    win.document.getElementById("viewC-button").doCommand();
+    Assert.equal(gCertificateDialogs.viewCertCallCount, 1,
+                 "viewCert() should've been called once");
+
+    yield BrowserTestUtils.closeWindow(win);
+  }
+});
+
+// Test that the right values are returned when the dialog is accepted.
+add_task(function* testAcceptDialogReturnValues() {
+  let [win, retVals] = yield openCertDownloadDialog(TEST_CASES[0].cert);
+  win.document.getElementById("trustSSL").checked = true;
+  win.document.getElementById("trustEmail").checked = false;
+  win.document.getElementById("trustObjSign").checked = true;
+  info("Accepting dialog");
+  win.document.getElementById("download_cert").acceptDialog();
+  yield BrowserTestUtils.windowClosed(win);
+
+  Assert.ok(retVals.get("importConfirmed"),
+            "Return value should signal user chose to import the cert");
+  Assert.ok(retVals.get("trustForSSL"),
+            "Return value should signal SSL trust checkbox was checked");
+  Assert.ok(!retVals.get("trustForEmail"),
+            "Return value should signal E-mail trust checkbox was unchecked");
+  Assert.ok(retVals.get("trustForObjSign"),
+            "Return value should signal Obj Sign trust checkbox was checked");
+});
+
+// Test that the right values are returned when the dialog is canceled.
+add_task(function* testCancelDialogReturnValues() {
+  let [win, retVals] = yield openCertDownloadDialog(TEST_CASES[0].cert);
+  info("Canceling dialog");
+  win.document.getElementById("download_cert").cancelDialog();
+  yield BrowserTestUtils.windowClosed(win);
+
+  Assert.ok(!retVals.get("importConfirmed"),
+            "Return value should signal user chose not to import the cert");
+});
--- a/testing/mochitest/browser.eslintrc.js
+++ b/testing/mochitest/browser.eslintrc.js
@@ -1,13 +1,14 @@
 // Parent config file for all browser-chrome files.
 module.exports = {
   "rules": {
-    "mozilla/import-headjs-globals": 2,
-    "mozilla/import-browserjs-globals": 2,
+    "mozilla/import-headjs-globals": 1,
+    "mozilla/import-browserjs-globals": 1,
+    "mozilla/mark-test-function-used": 1,
   },
 
   "env": {
     "browser": true,
     //"node": true
   },
 
   // All globals made available in the test environment.
--- a/testing/mochitest/chrome.eslintrc.js
+++ b/testing/mochitest/chrome.eslintrc.js
@@ -1,12 +1,13 @@
 // Parent config file for all mochitest files.
 module.exports = {
   rules: {
     "mozilla/import-headjs-globals": 1,
+    "mozilla/mark-test-function-used": 1,
   },
 
   "env": {
     "browser": true,
   },
 
   // All globals made available in the test environment.
   "globals": {
--- a/testing/mochitest/mochitest.eslintrc.js
+++ b/testing/mochitest/mochitest.eslintrc.js
@@ -1,12 +1,13 @@
 // Parent config file for all mochitest files.
 module.exports = {
   rules: {
     "mozilla/import-headjs-globals": 1,
+    "mozilla/mark-test-function-used": 1,
   },
 
   "env": {
     "browser": true,
   },
 
   // All globals made available in the test environment.
   "globals": {
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -38610,16 +38610,58 @@
           }
         ],
         "html/semantics/links/links-created-by-a-and-area-elements/htmlanchorelement_noopener.html": [
           {
             "path": "html/semantics/links/links-created-by-a-and-area-elements/htmlanchorelement_noopener.html",
             "url": "/html/semantics/links/links-created-by-a-and-area-elements/htmlanchorelement_noopener.html"
           }
         ],
+        "html/webappapis/idle-callbacks/callback-exception.html": [
+          {
+            "path": "html/webappapis/idle-callbacks/callback-exception.html",
+            "url": "/html/webappapis/idle-callbacks/callback-exception.html"
+          }
+        ],
+        "html/webappapis/idle-callbacks/callback-iframe.html": [
+          {
+            "path": "html/webappapis/idle-callbacks/callback-iframe.html",
+            "url": "/html/webappapis/idle-callbacks/callback-iframe.html"
+          }
+        ],
+        "html/webappapis/idle-callbacks/callback-invoked.html": [
+          {
+            "path": "html/webappapis/idle-callbacks/callback-invoked.html",
+            "url": "/html/webappapis/idle-callbacks/callback-invoked.html"
+          }
+        ],
+        "html/webappapis/idle-callbacks/callback-multiple-calls.html": [
+          {
+            "path": "html/webappapis/idle-callbacks/callback-multiple-calls.html",
+            "url": "/html/webappapis/idle-callbacks/callback-multiple-calls.html"
+          }
+        ],
+        "html/webappapis/idle-callbacks/callback-timeout.html": [
+          {
+            "path": "html/webappapis/idle-callbacks/callback-timeout.html",
+            "url": "/html/webappapis/idle-callbacks/callback-timeout.html"
+          }
+        ],
+        "html/webappapis/idle-callbacks/cancel-invoked.html": [
+          {
+            "path": "html/webappapis/idle-callbacks/cancel-invoked.html",
+            "url": "/html/webappapis/idle-callbacks/cancel-invoked.html"
+          }
+        ],
+        "html/webappapis/idle-callbacks/idlharness.html": [
+          {
+            "path": "html/webappapis/idle-callbacks/idlharness.html",
+            "url": "/html/webappapis/idle-callbacks/idlharness.html"
+          }
+        ],
         "secure-contexts/basic-dedicated-worker.html": [
           {
             "path": "secure-contexts/basic-dedicated-worker.html",
             "url": "/secure-contexts/basic-dedicated-worker.html"
           }
         ],
         "secure-contexts/basic-dedicated-worker.https.html": [
           {
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/webappapis/idle-callbacks/__dir__.ini
@@ -0,0 +1,1 @@
+prefs: [dom.requestIdleCallback.enabled:true]
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/idle-callbacks/callback-exception.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>requestIdleCallback callback exception reported to error handler</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id="log"></div>
+<script>
+  var custom_exception = 'requestIdleCallbackException';
+  setup({allow_uncaught_exception : true});
+  async_test(function (t) {
+    assert_false(document.hidden, "document.hidden must exist and be false to run this test properly");
+    addEventListener("error",function(e) {
+      t.step(function() {
+        assert_equals(e.error.message, custom_exception);
+        t.done();
+      })
+    });
+    window.requestIdleCallback(function () {
+      throw new Error(custom_exception);
+    });
+  }, "requestIdleCallback callback exceptions are reported to error handler");
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/idle-callbacks/callback-iframe.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id="log"></div>
+<iframe style="display:none" id="frame"></iframe>
+<script>
+  async_test(function (t) {
+    let frame = document.getElementById("frame");
+    frame.contentWindow.test = function() {
+      frame.contentWindow.requestIdleCallback(t.step_func_done());
+    }
+
+    frame.contentWindow.test();
+  });
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/idle-callbacks/callback-invoked.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>requestIdleCallback callback must be called eventually</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id="log"></div>
+<script>
+  async_test(function (t) {
+    assert_false(document.hidden, "document.hidden must exist and be false to run this test properly");
+    window.requestIdleCallback(t.step_func_done());
+  }, "requestIdleCallback callback is invoked at least once before the timeout");
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/idle-callbacks/callback-multiple-calls.html
@@ -0,0 +1,41 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>multiple calls to requestIdleCallback</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id="log"></div>
+<script>
+  async_test(function (t) {
+    assert_false(document.hidden, "document.hidden must exist and be false to run this test properly");
+    var counter = 0;
+    function f(c) {
+      assert_equals(counter, c);
+      if (counter === 99) {
+        t.done();
+      }
+
+      ++counter;
+    }
+    for (var i = 0; i < 100; ++i) {
+      let j = i;
+      window.requestIdleCallback(t.step_func(function () { f(j) }));
+    }
+  }, "requestIdleCallback callbacks should be invoked in order (called iteratively)");
+
+  async_test(function (t) {
+    assert_false(document.hidden, "document.hidden must exist and be false to run this test properly");
+    var counter = 0;
+
+    function f(c) {
+      assert_equals(counter, c);
+      if (counter === 99) {
+        t.done();
+      }
+
+      ++counter;
+      window.requestIdleCallback(t.step_func(function () { f(c + 1) }));
+    }
+
+    window.requestIdleCallback(t.step_func(function () { f(0) }));
+  }, "requestIdleCallback callbacks should be invoked in order (called recursively)");
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/idle-callbacks/callback-timeout.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>requestIdleCallback timeout callback must be called with didTimeout equal to true</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id="log"></div>
+<script>
+  async_test(function (t) {
+    assert_false(document.hidden, "document.hidden must exist and be false to run this test properly");
+    var counter = 0;
+
+    function g(deadline) {
+      assert_true(deadline.didTimeout)
+      t.done();
+    }
+
+    function f(deadline) {
+      assert_false(deadline.didTimeout);
+      window.requestIdleCallback(t.step_func(g), {timeout:300});
+
+      var d = Date.now() + 500;
+      while (Date.now() < d) {
+
+      }
+    }
+    window.requestIdleCallback(t.step_func(f));
+  }, "requestIdleCallback callback should time out");
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/idle-callbacks/cancel-invoked.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>cancelling idle requests</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id="log"></div>
+<script>
+  test(function (t) {
+    window.cancelIdleCallback(42);
+    assert_true(true);
+  }, "cancelIdleCallback does nothing if there is no callback with the given handle");
+
+  async_test(function (t) {
+    assert_false(document.hidden, "document.hidden must exist and be false to run this test properly");
+    var neverCalled = true;
+    var handle = window.requestIdleCallback(function () {
+      neverCalled = false;
+    });
+    window.cancelIdleCallback(handle);
+
+    t.step_timeout(function() {
+      assert_true(neverCalled);
+      t.done();
+    }, 2000);
+  }, "A cancelled callback is never invoked");
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/idle-callbacks/idlharness.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>idlharness test</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+
+<pre id='untested_idl' style='display:none'>
+[PrimaryGlobal]
+interface Window {
+};
+</pre>
+
+<pre id='idl'>
+partial interface Window {
+  unsigned long requestIdleCallback(IdleRequestCallback callback,
+                                    optional IdleRequestOptions options);
+  void          cancelIdleCallback(unsigned long handle);
+};
+
+dictionary IdleRequestOptions {
+  unsigned long timeout;
+};
+
+callback IdleRequestCallback = void (IdleDeadline deadline);
+</pre>
+<script>
+  var idl_array = new IdlArray();
+  idl_array.add_untested_idls(document.getElementById("untested_idl").textContent);
+  idl_array.add_idls(document.getElementById("idl").textContent);
+  idl_array.add_objects({Window: ["window"]});
+  idl_array.test();
+</script>
--- a/testing/xpcshell/xpcshell.eslintrc.js
+++ b/testing/xpcshell/xpcshell.eslintrc.js
@@ -1,12 +1,13 @@
 // Parent config file for all xpcshell files.
 module.exports = {
   rules: {
     "mozilla/import-headjs-globals": 1,
+    "mozilla/mark-test-function-used": 1,
   },
 
   // All globals made available in the test environment.
   "globals": {
     "add_task": false,
     "add_test": false,
     "Assert": false,
     "deepEqual": false,
--- a/toolkit/components/extensions/.eslintrc.js
+++ b/toolkit/components/extensions/.eslintrc.js
@@ -27,17 +27,16 @@ module.exports = { // eslint-disable-lin
     "TabManager": true,
     "WindowListManager": true,
     "XPCOMUtils": true,
   },
 
   "rules": {
     // Rules from the mozilla plugin
     "mozilla/balanced-listeners": 2,
-    "mozilla/mark-test-function-used": 1,
     "mozilla/no-aArgs": 1,
     "mozilla/no-cpows-in-tests": 1,
     "mozilla/var-only-at-top-level": 1,
 
     "valid-jsdoc": [2, {
       "prefer": {
         "return": "returns",
       },
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -1210,31 +1210,35 @@ class MockExtension {
   }
 
   cleanupGeneratedFile() {
     flushJarCache(this.file);
     return OS.File.remove(this.file.path);
   }
 }
 
+let _browserUpdated = false;
+
 // We create one instance of this class per extension. |addonData|
 // comes directly from bootstrap.js when initializing.
 this.Extension = class extends ExtensionData {
-  constructor(addonData) {
+  constructor(addonData, startupReason) {
     super(addonData.resourceURI);
 
     this.uuid = UUIDMap.get(addonData.id);
 
     if (addonData.cleanupFile) {
       Services.obs.addObserver(this, "xpcom-shutdown", false);
       this.cleanupFile = addonData.cleanupFile || null;
       delete addonData.cleanupFile;
     }
 
     this.addonData = addonData;
+    this.startupReason = startupReason;
+
     this.id = addonData.id;
     this.baseURI = NetUtil.newURI(this.getURL("")).QueryInterface(Ci.nsIURL);
     this.principal = this.createPrincipal();
 
     this.onStartup = null;
 
     this.hasShutdown = false;
     this.onShutdown = new Set();
@@ -1243,16 +1247,24 @@ this.Extension = class extends Extension
 
     this.apis = [];
     this.whiteListedHosts = null;
     this.webAccessibleResources = null;
 
     this.emitter = new EventEmitter();
   }
 
+  static set browserUpdated(updated) {
+    _browserUpdated = updated;
+  }
+
+  static get browserUpdated() {
+    return _browserUpdated;
+  }
+
   /**
    * This code is designed to make it easy to test a WebExtension
    * without creating a bunch of files. Everything is contained in a
    * single JSON blob.
    *
    * Properties:
    *   "background": "<JS code>"
    *     A script to be loaded as the background script.
--- a/toolkit/components/extensions/ExtensionXPCShellUtils.jsm
+++ b/toolkit/components/extensions/ExtensionXPCShellUtils.jsm
@@ -15,16 +15,21 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/Extension.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
                                   "resource://gre/modules/Schemas.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 
+XPCOMUtils.defineLazyGetter(this, "Management", () => {
+  const {Management} = Cu.import("resource://gre/modules/Extension.jsm", {});
+  return Management;
+});
+
 /* exported ExtensionTestUtils */
 
 let BASE_MANIFEST = Object.freeze({
   "applications": Object.freeze({
     "gecko": Object.freeze({
       "id": "test@web.ext",
     }),
   }),
@@ -45,65 +50,69 @@ class ExtensionWrapper {
     this.testResolve = null;
     this.testDone = new Promise(resolve => { this.testResolve = resolve; });
 
     this.messageHandler = new Map();
     this.messageAwaiter = new Map();
 
     this.messageQueue = new Set();
 
+    this.attachListeners();
+
     this.testScope.do_register_cleanup(() => {
       if (this.messageQueue.size) {
         let names = Array.from(this.messageQueue, ([msg]) => msg);
         this.testScope.equal(JSON.stringify(names), "[]", "message queue is empty");
       }
       if (this.messageAwaiter.size) {
         let names = Array.from(this.messageAwaiter.keys());
         this.testScope.equal(JSON.stringify(names), "[]", "no tasks awaiting on messages");
       }
     });
 
-    /* eslint-disable mozilla/balanced-listeners */
-    extension.on("test-eq", (kind, pass, msg, expected, actual) => {
-      this.testScope.ok(pass, `${msg} - Expected: ${expected}, Actual: ${actual}`);
-    });
-    extension.on("test-log", (kind, pass, msg) => {
-      this.testScope.do_print(msg);
-    });
-    extension.on("test-result", (kind, pass, msg) => {
-      this.testScope.ok(pass, msg);
-    });
-    extension.on("test-done", (kind, pass, msg, expected, actual) => {
-      this.testScope.ok(pass, msg);
-      this.testResolve(msg);
-    });
-
-    extension.on("test-message", (kind, msg, ...args) => {
-      let handler = this.messageHandler.get(msg);
-      if (handler) {
-        handler(...args);
-      } else {
-        this.messageQueue.add([msg, ...args]);
-        this.checkMessages();
-      }
-    });
-    /* eslint-enable mozilla/balanced-listeners */
-
     this.testScope.do_register_cleanup(() => {
       if (this.state == "pending" || this.state == "running") {
         this.testScope.equal(this.state, "unloaded", "Extension left running at test shutdown");
         return this.unload();
       } else if (extension.state == "unloading") {
         this.testScope.equal(this.state, "unloaded", "Extension not fully unloaded at test shutdown");
       }
     });
 
     this.testScope.do_print(`Extension loaded`);
   }
 
+  attachListeners() {
+    /* eslint-disable mozilla/balanced-listeners */
+    this.extension.on("test-eq", (kind, pass, msg, expected, actual) => {
+      this.testScope.ok(pass, `${msg} - Expected: ${expected}, Actual: ${actual}`);
+    });
+    this.extension.on("test-log", (kind, pass, msg) => {
+      this.testScope.do_print(msg);
+    });
+    this.extension.on("test-result", (kind, pass, msg) => {
+      this.testScope.ok(pass, msg);
+    });
+    this.extension.on("test-done", (kind, pass, msg, expected, actual) => {
+      this.testScope.ok(pass, msg);
+      this.testResolve(msg);
+    });
+
+    this.extension.on("test-message", (kind, msg, ...args) => {
+      let handler = this.messageHandler.get(msg);
+      if (handler) {
+        handler(...args);
+      } else {
+        this.messageQueue.add([msg, ...args]);
+        this.checkMessages();
+      }
+    });
+    /* eslint-enable mozilla/balanced-listeners */
+  }
+
   startup() {
     if (this.state != "uninitialized") {
       throw new Error("Extension already started");
     }
     this.state = "pending";
 
     return this.extension.startup().then(
       result => {
@@ -195,18 +204,16 @@ class ExtensionWrapper {
     this.messageHandler.set(msg, callback);
   }
 }
 
 var ExtensionTestUtils = {
   BASE_MANIFEST,
 
   normalizeManifest: Task.async(function* (manifest, baseManifest = BASE_MANIFEST) {
-    const {Management} = Cu.import("resource://gre/modules/Extension.jsm", {});
-
     yield Management.lazyInit();
 
     let errors = [];
     let context = {
       url: null,
 
       logError: error => {
         errors.push(error);
--- a/toolkit/components/extensions/ext-backgroundPage.js
+++ b/toolkit/components/extensions/ext-backgroundPage.js
@@ -96,21 +96,17 @@ BackgroundPage.prototype = {
 
     // Set the add-on's main debugger global, for use in the debugger
     // console.
     if (this.extension.addonData.instanceID) {
       AddonManager.getAddonByInstanceID(this.extension.addonData.instanceID)
                   .then(addon => addon.setDebugGlobal(window));
     }
 
-    // TODO(robwu): This implementation of onStartup is wrong, see
-    // https://bugzil.la/1247435#c1
-    if (this.extension.onStartup) {
-      this.extension.onStartup();
-    }
+    this.extension.emit("startup");
   }),
 
   shutdown() {
     if (this.extension.addonData.instanceID) {
       AddonManager.getAddonByInstanceID(this.extension.addonData.instanceID)
                   .then(addon => addon.setDebugGlobal(null));
     }
 
--- a/toolkit/components/extensions/ext-runtime.js
+++ b/toolkit/components/extensions/ext-runtime.js
@@ -3,38 +3,54 @@
 var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Extension",
+                                  "resource://gre/modules/Extension.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
                                   "resource://gre/modules/ExtensionManagement.jsm");
 
 var {
-  EventManager,
+  ignoreEvent,
   SingletonEventManager,
-  ignoreEvent,
 } = ExtensionUtils;
 
 extensions.registerSchemaAPI("runtime", "addon_parent", context => {
   let {extension} = context;
   return {
     runtime: {
-      onStartup: new EventManager(context, "runtime.onStartup", fire => {
-        extension.onStartup = fire;
+      onStartup: ignoreEvent(context, "runtime.onStartup"),
+
+      onInstalled: new SingletonEventManager(context, "runtime.onInstalled", fire => {
+        let listener = () => {
+          switch (extension.startupReason) {
+            case "APP_STARTUP":
+              if (Extension.browserUpdated) {
+                fire({reason: "browser_update"});
+              }
+              break;
+            case "ADDON_INSTALL":
+              fire({reason: "install"});
+              break;
+            case "ADDON_UPGRADE":
+              fire({reason: "update"});
+              break;
+          }
+        };
+        extension.on("startup", listener);
         return () => {
-          extension.onStartup = null;
+          extension.off("startup", listener);
         };
       }).api(),
 
-      onInstalled: ignoreEvent(context, "runtime.onInstalled"),
-
       onUpdateAvailable: new SingletonEventManager(context, "runtime.onUpdateAvailable", fire => {
         let instanceID = extension.addonData.instanceID;
         AddonManager.addUpgradeListener(instanceID, upgrade => {
           extension.upgrade = upgrade;
           let details = {
             version: upgrade.version,
           };
           context.runSafe(fire, details);
--- a/toolkit/components/extensions/schemas/runtime.json
+++ b/toolkit/components/extensions/schemas/runtime.json
@@ -117,17 +117,17 @@
         "type": "string",
         "enum": ["throttled", "no_update", "update_available"],
         "allowedContexts": ["content"],
         "description": "Result of the update check."
       },
       {
         "id": "OnInstalledReason",
         "type": "string",
-        "enum": ["install", "update", "chrome_update", "shared_module_update"],
+        "enum": ["install", "update", "browser_update"],
         "allowedContexts": ["content"],
         "description": "The reason that this event is being dispatched."
       },
       {
         "id": "OnRestartRequiredReason",
         "type": "string",
         "allowedContexts": ["content"],
         "description": "The reason that the event is being dispatched. 'app_update' is used when the restart is needed because the application is updated to a newer version. 'os_update' is used when the restart is needed because the browser/OS is updated to a newer version. 'periodic' is used when the system runs for more than the permitted uptime set in the enterprise policy.",
@@ -454,36 +454,37 @@
       {
         "name": "onStartup",
         "unsupported": true,
         "type": "function",
         "description": "Fired when a profile that has this extension installed first starts up. This event is not fired when an incognito profile is started, even if this extension is operating in 'split' incognito mode."
       },
       {
         "name": "onInstalled",
-        "unsupported": true,
         "type": "function",
         "description": "Fired when the extension is first installed, when the extension is updated to a new version, and when the browser is updated to a new version.",
         "parameters": [
           {
             "type": "object",
             "name": "details",
             "properties": {
               "reason": {
                 "$ref": "OnInstalledReason",
                 "description": "The reason that this event is being dispatched."
               },
               "previousVersion": {
                 "type": "string",
                 "optional": true,
+                "unsupported": true,
                 "description": "Indicates the previous version of the extension, which has just been updated. This is present only if 'reason' is 'update'."
               },
               "id": {
                 "type": "string",
                 "optional": true,
+                "unsupported": true,
                 "description": "Indicates the ID of the imported shared module extension which updated. This is present only if 'reason' is 'shared_module_update'."
               }
             }
           }
         ]
       },
       {
         "name": "onSuspend",
--- a/toolkit/components/extensions/test/mochitest/test_ext_all_apis.js
+++ b/toolkit/components/extensions/test/mochitest/test_ext_all_apis.js
@@ -68,16 +68,17 @@ let expectedBackgroundApis = [
   "management.ExtensionDisabledReason",
   "management.ExtensionInstallType",
   "management.ExtensionType",
   "management.getSelf",
   "management.uninstallSelf",
   "runtime.getBackgroundPage",
   "runtime.getBrowserInfo",
   "runtime.getPlatformInfo",
+  "runtime.onInstalled",
   "runtime.onUpdateAvailable",
   "runtime.openOptionsPage",
   "runtime.reload",
   "runtime.setUninstallURL",
 ];
 
 function sendAllApis() {
   function isEvent(key, val) {
--- a/toolkit/components/extensions/test/xpcshell/head.js
+++ b/toolkit/components/extensions/test/xpcshell/head.js
@@ -2,16 +2,17 @@
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 /* exported createHttpServer, promiseConsoleOutput, cleanupDir */
 
 Components.utils.import("resource://gre/modules/Task.jsm");
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Timer.jsm");
+Components.utils.import("resource://testing-common/AddonTestUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                   "resource://gre/modules/AppConstants.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Extension",
                                   "resource://gre/modules/Extension.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionData",
                                   "resource://gre/modules/Extension.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_onInstalled.js
@@ -0,0 +1,324 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+XPCOMUtils.defineLazyGetter(this, "Management", () => {
+  const {Management} = Cu.import("resource://gre/modules/Extension.jsm", {});
+  return Management;
+});
+
+const {
+  createAppInfo,
+  createTempWebExtensionFile,
+  promiseAddonByID,
+  promiseAddonEvent,
+  promiseCompleteAllInstalls,
+  promiseFindAddonUpdates,
+  promiseRestartManager,
+  promiseShutdownManager,
+  promiseStartupManager,
+} = AddonTestUtils;
+
+AddonTestUtils.init(this);
+
+// Allow for unsigned addons.
+AddonTestUtils.overrideCertDB();
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
+
+function awaitEvent(eventName) {
+  return new Promise(resolve => {
+    let listener = (_eventName, ...args) => {
+      if (_eventName === eventName) {
+        Management.off(eventName, listener);
+        resolve(...args);
+      }
+    };
+
+    Management.on(eventName, listener);
+  });
+}
+
+add_task(function* test_should_fire_on_addon_update() {
+  const EXTENSION_ID = "test_runtime_on_installed_addon_update@tests.mozilla.org";
+
+  const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity";
+
+  // The test extension uses an insecure update url.
+  Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
+
+  const testServer = createHttpServer();
+  const port = testServer.identity.primaryPort;
+
+  let extension = ExtensionTestUtils.loadExtension({
+    useAddonManager: "permanent",
+    manifest: {
+      "version": "1.0",
+      "applications": {
+        "gecko": {
+          "id": EXTENSION_ID,
+          "update_url": `http://localhost:${port}/test_update.json`,
+        },
+      },
+    },
+    background() {
+      browser.runtime.onUpdateAvailable.addListener(details => {
+        browser.test.sendMessage("reloading");
+        browser.runtime.reload();
+      });
+
+      browser.runtime.onInstalled.addListener(details => {
+        browser.test.sendMessage("installed", details);
+      });
+    },
+  });
+
+  testServer.registerPathHandler("/test_update.json", (request, response) => {
+    response.write(`{
+      "addons": {
+        "${EXTENSION_ID}": {
+          "updates": [
+            {
+              "version": "2.0",
+              "update_link": "http://localhost:${port}/addons/test_runtime_on_installed-2.0.xpi"
+            }
+          ]
+        }
+      }
+    }`);
+  });
+
+  let webExtensionFile = createTempWebExtensionFile({
+    manifest: {
+      version: "2.0",
+      applications: {
+        gecko: {
+          id: EXTENSION_ID,
+        },
+      },
+    },
+    background() {
+      browser.runtime.onInstalled.addListener(details => {
+        browser.test.sendMessage("installed", details);
+      });
+    },
+  });
+
+  testServer.registerFile("/addons/test_runtime_on_installed-2.0.xpi", webExtensionFile);
+
+  yield promiseStartupManager();
+
+  yield extension.startup();
+  let details = yield extension.awaitMessage("installed");
+  equal(details.reason, "install", "runtime.onInstalled fired with the correct reason");
+
+  let addon = yield promiseAddonByID(EXTENSION_ID);
+  equal(addon.version, "1.0", "The installed addon has the correct version");
+
+  let update = yield promiseFindAddonUpdates(addon);
+  let install = update.updateAvailable;
+
+  let promiseInstalled = promiseAddonEvent("onInstalled");
+  yield promiseCompleteAllInstalls([install]);
+
+  yield extension.awaitMessage("reloading");
+
+  let startupPromise = awaitEvent("ready");
+
+  let [updated_addon] = yield promiseInstalled;
+  equal(updated_addon.version, "2.0", "The updated addon has the correct version");
+
+  extension.extension = yield startupPromise;
+  extension.attachListeners();
+
+  details = yield extension.awaitMessage("installed");
+  equal(details.reason, "update", "runtime.onInstalled fired with the correct reason");
+
+  yield extension.unload();
+
+  yield updated_addon.uninstall();
+  yield promiseShutdownManager();
+});
+
+add_task(function* test_should_fire_on_browser_update() {
+  const EXTENSION_ID = "test_runtime_on_installed_browser_update@tests.mozilla.org";
+
+  yield promiseStartupManager();
+
+  let extension = ExtensionTestUtils.loadExtension({
+    useAddonManager: "permanent",
+    manifest: {
+      "version": "1.0",
+      "applications": {
+        "gecko": {
+          "id": EXTENSION_ID,
+        },
+      },
+    },
+    background() {
+      let onInstalledDetails = null;
+
+      browser.runtime.onInstalled.addListener(details => {
+        onInstalledDetails = details;
+      });
+
+      browser.test.onMessage.addListener(message => {
+        if (message == "get-on-installed-details") {
+          browser.test.sendMessage("on-installed-details", onInstalledDetails);
+        }
+      });
+    },
+  });
+
+  yield extension.startup();
+
+  extension.sendMessage("get-on-installed-details");
+  let details = yield extension.awaitMessage("on-installed-details");
+  equal(details.reason, "install", "runtime.onInstalled fired with the correct reason");
+
+  let startupPromise = awaitEvent("ready");
+  yield promiseRestartManager("1");
+  extension.extension = yield startupPromise;
+  extension.attachListeners();
+
+  extension.sendMessage("get-on-installed-details");
+  details = yield extension.awaitMessage("on-installed-details");
+  equal(details, null, "runtime.onInstalled should not have fired");
+
+  // Update the browser.
+  startupPromise = awaitEvent("ready");
+  yield promiseRestartManager("2");
+  extension.extension = yield startupPromise;
+  extension.attachListeners();
+
+  extension.sendMessage("get-on-installed-details");
+  details = yield extension.awaitMessage("on-installed-details");
+  equal(details.reason, "browser_update", "runtime.onInstalled fired with the correct reason");
+
+  // Restart the browser.
+  startupPromise = awaitEvent("ready");
+  yield promiseRestartManager("2");
+  extension.extension = yield startupPromise;
+  extension.attachListeners();
+
+  extension.sendMessage("get-on-installed-details");
+  details = yield extension.awaitMessage("on-installed-details");
+  equal(details, null, "runtime.onInstalled should not have fired");
+
+  // Update the browser again.
+  startupPromise = awaitEvent("ready");
+  yield promiseRestartManager("3");
+  extension.extension = yield startupPromise;
+  extension.attachListeners();
+
+  extension.sendMessage("get-on-installed-details");
+  details = yield extension.awaitMessage("on-installed-details");
+  equal(details.reason, "browser_update", "runtime.onInstalled fired with the correct reason");
+
+  yield extension.unload();
+
+  yield promiseShutdownManager();
+});
+
+add_task(function* test_should_not_fire_on_reload() {
+  const EXTENSION_ID = "test_runtime_on_installed_reload@tests.mozilla.org";
+
+  yield promiseStartupManager();
+
+  let extension = ExtensionTestUtils.loadExtension({
+    useAddonManager: "permanent",
+    manifest: {
+      "version": "1.0",
+      "applications": {
+        "gecko": {
+          "id": EXTENSION_ID,
+        },
+      },
+    },
+    background() {
+      let onInstalledDetails = null;
+
+      browser.runtime.onInstalled.addListener(details => {
+        onInstalledDetails = details;
+      });
+
+      browser.test.onMessage.addListener(message => {
+        if (message == "reload-extension") {
+          browser.runtime.reload();
+        } else if (message == "get-on-installed-details") {
+          browser.test.sendMessage("on-installed-details", onInstalledDetails);
+        }
+      });
+    },
+  });
+
+  yield extension.startup();
+
+  extension.sendMessage("get-on-installed-details");
+  let details = yield extension.awaitMessage("on-installed-details");
+  equal(details.reason, "install", "runtime.onInstalled fired with the correct reason");
+
+  let startupPromise = awaitEvent("ready");
+  extension.sendMessage("reload-extension");
+  extension.extension = yield startupPromise;
+  extension.attachListeners();
+
+  extension.sendMessage("get-on-installed-details");
+  details = yield extension.awaitMessage("on-installed-details");
+  equal(details, null, "runtime.onInstalled should not have fired");
+
+  yield extension.unload();
+  yield promiseShutdownManager();
+});
+
+add_task(function* test_should_not_fire_on_restart() {
+  const EXTENSION_ID = "test_runtime_on_installed_restart@tests.mozilla.org";
+
+  yield promiseStartupManager();
+
+  let extension = ExtensionTestUtils.loadExtension({
+    useAddonManager: "permanent",
+    manifest: {
+      "version": "1.0",
+      "applications": {
+        "gecko": {
+          "id": EXTENSION_ID,
+        },
+      },
+    },
+    background() {
+      let onInstalledDetails = null;
+
+      browser.runtime.onInstalled.addListener(details => {
+        onInstalledDetails = details;
+      });
+
+      browser.test.onMessage.addListener(message => {
+        if (message == "get-on-installed-details") {
+          browser.test.sendMessage("on-installed-details", onInstalledDetails);
+        }
+      });
+    },
+  });
+
+  yield extension.startup();
+
+  extension.sendMessage("get-on-installed-details");
+  let details = yield extension.awaitMessage("on-installed-details");
+  equal(details.reason, "install", "runtime.onInstalled fired with the correct reason");
+
+  let addon = yield promiseAddonByID(EXTENSION_ID);
+  addon.userDisabled = true;
+
+  let startupPromise = awaitEvent("ready");
+  addon.userDisabled = false;
+  extension.extension = yield startupPromise;
+  extension.attachListeners();
+
+  extension.sendMessage("get-on-installed-details");
+  details = yield extension.awaitMessage("on-installed-details");
+  equal(details, null, "runtime.onInstalled should not have fired");
+
+  yield extension.markUnloaded();
+  yield promiseShutdownManager();
+});
--- a/toolkit/components/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell.ini
@@ -41,16 +41,17 @@ skip-if = release_or_beta
 [test_ext_management_uninstall_self.js]
 [test_ext_manifest_content_security_policy.js]
 [test_ext_manifest_incognito.js]
 [test_ext_manifest_minimum_chrome_version.js]
 [test_ext_onmessage_removelistener.js]
 [test_ext_runtime_connect_no_receiver.js]
 [test_ext_runtime_getBrowserInfo.js]
 [test_ext_runtime_getPlatformInfo.js]
+[test_ext_runtime_onInstalled.js]
 [test_ext_runtime_sendMessage.js]
 [test_ext_runtime_sendMessage_errors.js]
 [test_ext_runtime_sendMessage_no_receiver.js]
 [test_ext_runtime_sendMessage_self.js]
 [test_ext_schemas.js]
 [test_ext_schemas_api_injection.js]
 [test_ext_schemas_async.js]
 [test_ext_schemas_allowed_contexts.js]
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -80,16 +80,18 @@ Cu.import("resource://gre/modules/XPCOMU
 Cu.import("resource://gre/modules/AsyncShutdown.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
                                   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
                                   "resource://gre/modules/addons/AddonRepository.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Extension",
+                                  "resource://gre/modules/Extension.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "CertUtils", function() {
   let certUtils = {};
   Components.utils.import("resource://gre/modules/CertUtils.jsm", certUtils);
   return certUtils;
 });
@@ -845,16 +847,18 @@ var AddonManagerInternal = {
 
       let oldAppVersion = null;
       try {
         oldAppVersion = Services.prefs.getCharPref(PREF_EM_LAST_APP_VERSION);
         appChanged = Services.appinfo.version != oldAppVersion;
       }
       catch (e) { }
 
+      Extension.browserUpdated = appChanged;
+
       let oldPlatformVersion = null;
       try {
         oldPlatformVersion = Services.prefs.getCharPref(PREF_EM_LAST_PLATFORM_VERSION);
       }
       catch (e) { }
 
       if (appChanged !== false) {
         logger.debug("Application has been upgraded");
--- a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
@@ -1083,16 +1083,75 @@ var AddonTestUtils = {
    * @return {Promise<Addon>}
    *        Resolves to the add-on with the given ID.
    */
   promiseAddonByID(id) {
     return new Promise(resolve => AddonManager.getAddonByID(id, resolve));
   },
 
   /**
+   * Returns a promise that will be resolved when an add-on update check is
+   * complete. The value resolved will be an AddonInstall if a new version was
+   * found.
+   *
+   * @param {object} addon The add-on to find updates for.
+   * @param {integer} reason The type of update to find.
+   * @return {Promise<object>} an object containing information about the update.
+   */
+  promiseFindAddonUpdates(addon, reason = AddonManager.UPDATE_WHEN_PERIODIC_UPDATE) {
+    let equal = this.testScope.equal;
+    return new Promise((resolve, reject) => {
+      let result = {};
+      addon.findUpdates({
+        onNoCompatibilityUpdateAvailable: function(addon2) {
+          if ("compatibilityUpdate" in result) {
+            throw new Error("Saw multiple compatibility update events");
+          }
+          equal(addon, addon2, "onNoCompatibilityUpdateAvailable");
+          result.compatibilityUpdate = false;
+        },
+
+        onCompatibilityUpdateAvailable: function(addon2) {
+          if ("compatibilityUpdate" in result) {
+            throw new Error("Saw multiple compatibility update events");
+          }
+          equal(addon, addon2, "onCompatibilityUpdateAvailable");
+          result.compatibilityUpdate = true;
+        },
+
+        onNoUpdateAvailable: function(addon2) {
+          if ("updateAvailable" in result) {
+            throw new Error("Saw multiple update available events");
+          }
+          equal(addon, addon2, "onNoUpdateAvailable");
+          result.updateAvailable = false;
+        },
+
+        onUpdateAvailable: function(addon2, install) {
+          if ("updateAvailable" in result) {
+            throw new Error("Saw multiple update available events");
+          }
+          equal(addon, addon2, "onUpdateAvailable");
+          result.updateAvailable = install;
+        },
+
+        onUpdateFinished: function(addon2, error) {
+          equal(addon, addon2, "onUpdateFinished");
+          if (error == AddonManager.UPDATE_STATUS_NO_ERROR) {
+            resolve(result);
+          } else {
+            result.error = error;
+            reject(result);
+          }
+        }
+      }, reason);
+    });
+  },
+
+  /**
    * A promise-based variant of AddonManager.getAddonsWithOperationsByTypes
    *
    * @param {Array<string>} types
    *        The first argument to AddonManager.getAddonsWithOperationsByTypes
    * @return {Promise<Array<Addon>>}
    *        Resolves to an array of add-ons with the given operations
    *        pending.
    */
--- a/toolkit/mozapps/extensions/internal/WebExtensionBootstrap.js
+++ b/toolkit/mozapps/extensions/internal/WebExtensionBootstrap.js
@@ -3,23 +3,34 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 Components.utils.import("resource://gre/modules/Extension.jsm");
 
 var extension;
 
+const BOOTSTRAP_REASON_TO_STRING_MAP = {
+  1: "APP_STARTUP",
+  2: "APP_SHUTDOWN",
+  3: "ADDON_ENABLE",
+  4: "ADDON_DISABLE",
+  5: "ADDON_INSTALL",
+  6: "ADDON_UNINSTALL",
+  7: "ADDON_UPGRADE",
+  8: "ADDON_DOWNGRADE",
+}
+
 function install(data, reason)
 {
 }
 
 function startup(data, reason)
 {
-  extension = new Extension(data);
+  extension = new Extension(data, BOOTSTRAP_REASON_TO_STRING_MAP[reason]);
   extension.startup();
 }
 
 function shutdown(data, reason)
 {
   extension.shutdown();
 }
 
--- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
@@ -51,16 +51,44 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://testing-common/httpd.js");
 XPCOMUtils.defineLazyModuleGetter(this, "MockAsyncShutdown",
                                   "resource://testing-common/AddonTestUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "MockRegistrar",
                                   "resource://testing-common/MockRegistrar.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "MockRegistry",
                                   "resource://testing-common/MockRegistry.jsm");
 
+const {
+  awaitPromise,
+  createAppInfo,
+  createInstallRDF,
+  createTempWebExtensionFile,
+  createUpdateRDF,
+  getFileForAddon,
+  manuallyInstall,
+  manuallyUninstall,
+  promiseAddonByID,
+  promiseAddonEvent,
+  promiseAddonsByIDs,
+  promiseAddonsWithOperationsByTypes,
+  promiseCompleteAllInstalls,
+  promiseConsoleOutput,
+  promiseFindAddonUpdates,
+  promiseInstallAllFiles,
+  promiseInstallFile,
+  promiseRestartManager,
+  promiseSetExtensionModifiedTime,
+  promiseShutdownManager,
+  promiseStartupManager,
+  promiseWriteProxyFileToDir,
+  registerDirectory,
+  setExtensionModifiedTime,
+  writeFilesToZip
+} = AddonTestUtils;
+
 // WebExtension wrapper for ease of testing
 ExtensionTestUtils.init(this);
 
 AddonTestUtils.init(this);
 AddonTestUtils.overrideCertDB();
 
 Object.defineProperty(this, "gAppInfo", {
   get() {
@@ -311,18 +339,16 @@ function isNightlyChannel() {
   try {
     channel = Services.prefs.getCharPref("app.update.channel");
   }
   catch (e) { }
 
   return channel != "aurora" && channel != "beta" && channel != "release" && channel != "esr";
 }
 
-var {createAppInfo} = AddonTestUtils;
-
 /**
  * Tests that an add-on does appear in the crash report annotations, if
  * crash reporting is enabled. The test will fail if the add-on is not in the
  * annotation.
  * @param  aId
  *         The ID of the add-on
  * @param  aVersion
  *         The version of the add-on
@@ -571,21 +597,16 @@ function do_check_compatibilityoverride(
 }
 
 function do_check_icons(aActual, aExpected) {
   for (var size in aExpected) {
     do_check_eq(remove_port(aActual[size]), remove_port(aExpected[size]));
   }
 }
 
-var {promiseStartupManager} = AddonTestUtils;
-var {promiseRestartManager} = AddonTestUtils;
-var {promiseShutdownManager} = AddonTestUtils;
-var {awaitPromise} = AddonTestUtils;
-
 function startupManager(aAppChanged) {
   promiseStartupManager(aAppChanged);
 }
 
 /**
  * Restarts the add-on manager as if the host application was restarted.
  *
  * @param  aNewVersion
@@ -618,19 +639,16 @@ function check_startup_changes(aType, aI
   ids.sort();
   var changes = AddonManager.getStartupChanges(aType);
   changes = changes.filter(aEl => /@tests.mozilla.org$/.test(aEl));
   changes.sort();
 
   do_check_eq(JSON.stringify(ids), JSON.stringify(changes));
 }
 
-var {createUpdateRDF} = AddonTestUtils;
-var {createInstallRDF} = AddonTestUtils;
-
 /**
  * Writes an install.rdf manifest into a directory using the properties passed
  * in a JS object. The objects should contain a property for each property to
  * appear in the RDF. The object may contain an array of objects with id,
  * minVersion and maxVersion in the targetApplications property to give target
  * application compatibility.
  *
  * @param   aData
@@ -730,18 +748,16 @@ function writeInstallRDFForExtension(aDa
  */
 function promiseWriteWebManifestForExtension(aData, aDir, aId = aData.applications.gecko.id) {
   let files = {
     "manifest.json": JSON.stringify(aData),
   }
   return AddonTestUtils.promiseWriteFilesToExtension(aDir.path, aId, files);
 }
 
-var {writeFilesToZip} = AddonTestUtils;
-
 /**
  * Creates an XPI file for some manifest data in the temporary directory and
  * returns the nsIFile for it. The file will be deleted when the test completes.
  *
  * @param   aData
  *          The object holding data about the add-on
  * @return  A file pointing to the created XPI file
  */
@@ -752,28 +768,16 @@ function createTempXPIFile(aData, aExtra
   if (typeof aExtraFile == "object")
     Object.assign(files, aExtraFile);
   else if (aExtraFile)
     files[aExtraFile] = "";
 
   return AddonTestUtils.createTempXPIFile(files);
 }
 
-var {createTempWebExtensionFile} = AddonTestUtils;
-
-var {setExtensionModifiedTime} = AddonTestUtils;
-var {promiseSetExtensionModifiedTime} = AddonTestUtils;
-
-var {manuallyInstall} = AddonTestUtils;
-var {manuallyUninstall} = AddonTestUtils;
-
-var {getFileForAddon} = AddonTestUtils;
-
-var {registerDirectory} = AddonTestUtils;
-
 var gExpectedEvents = {};
 var gExpectedInstalls = [];
 var gNext = null;
 
 function getExpectedEvent(aId) {
   if (!(aId in gExpectedEvents))
     do_throw("Wasn't expecting events for " + aId);
   if (gExpectedEvents[aId].length == 0)
@@ -1014,35 +1018,29 @@ function ensure_test_completed() {
     if (gExpectedEvents[i].length > 0)
       do_throw("Didn't see all the expected events for " + i);
   }
   gExpectedEvents = {};
   if (gExpectedInstalls)
     do_check_eq(gExpectedInstalls.length, 0);
 }
 
-var {promiseAddonEvent} = AddonTestUtils;
-
-var {promiseCompleteAllInstalls} = AddonTestUtils;
-
 /**
  * A helper method to install an array of AddonInstall to completion and then
  * call a provided callback.
  *
  * @param   aInstalls
  *          The array of AddonInstalls to install
  * @param   aCallback
  *          The callback to call when all installs have finished
  */
 function completeAllInstalls(aInstalls, aCallback) {
   promiseCompleteAllInstalls(aInstalls).then(aCallback);
 }
 
-var {promiseInstallFile, promiseInstallAllFiles} = AddonTestUtils;
-
 /**
  * A helper method to install an array of files and call a callback after the
  * installs are completed.
  *
  * @param   aFiles
  *          The array of files to install
  * @param   aCallback
  *          The callback to call when all installs have finished
@@ -1269,79 +1267,15 @@ function saveJSON(aData, aFile) {
 function callback_soon(aFunction) {
   return function(...args) {
     do_execute_soon(function() {
       aFunction.apply(null, args);
     }, aFunction.name ? "delayed callback " + aFunction.name : "delayed callback");
   }
 }
 
-var {promiseAddonsByIDs} = AddonTestUtils;
-
-var {promiseAddonByID} = AddonTestUtils;
-
-var {promiseAddonsWithOperationsByTypes} = AddonTestUtils;
-
-/**
- * Returns a promise that will be resolved when an add-on update check is
- * complete. The value resolved will be an AddonInstall if a new version was
- * found.
- */
-function promiseFindAddonUpdates(addon, reason = AddonManager.UPDATE_WHEN_PERIODIC_UPDATE) {
-  return new Promise((resolve, reject) => {
-    let result = {};
-    addon.findUpdates({
-      onNoCompatibilityUpdateAvailable: function(addon2) {
-        if ("compatibilityUpdate" in result) {
-          do_throw("Saw multiple compatibility update events");
-        }
-        equal(addon, addon2, "onNoCompatibilityUpdateAvailable");
-        result.compatibilityUpdate = false;
-      },
-
-      onCompatibilityUpdateAvailable: function(addon2) {
-        if ("compatibilityUpdate" in result) {
-          do_throw("Saw multiple compatibility update events");
-        }
-        equal(addon, addon2, "onCompatibilityUpdateAvailable");
-        result.compatibilityUpdate = true;
-      },
-
-      onNoUpdateAvailable: function(addon2) {
-        if ("updateAvailable" in result) {
-          do_throw("Saw multiple update available events");
-        }
-        equal(addon, addon2, "onNoUpdateAvailable");
-        result.updateAvailable = false;
-      },
-
-      onUpdateAvailable: function(addon2, install) {
-        if ("updateAvailable" in result) {
-          do_throw("Saw multiple update available events");
-        }
-        equal(addon, addon2, "onUpdateAvailable");
-        result.updateAvailable = install;
-      },
-
-      onUpdateFinished: function(addon2, error) {
-        equal(addon, addon2, "onUpdateFinished");
-        if (error == AddonManager.UPDATE_STATUS_NO_ERROR) {
-          resolve(result);
-        } else {
-          result.error = error;
-          reject(result);
-        }
-      }
-    }, reason);
-  });
-}
-
-var {promiseConsoleOutput} = AddonTestUtils;
-
-var {promiseWriteProxyFileToDir} = AddonTestUtils;
-
 function writeProxyFileToDir(aDir, aAddon, aId) {
   awaitPromise(promiseWriteProxyFileToDir(aDir, aAddon, aId));
 
   let file = aDir.clone();
   file.append(aId);
   return file
 }
--- a/tools/lint/docs/linters/eslint-plugin-mozilla.rst
+++ b/tools/lint/docs/linters/eslint-plugin-mozilla.rst
@@ -67,18 +67,19 @@ The following patterns are supported:
 -  ``XPCOMUtils.defineLazyModuleGetter(this, "setNamedTimeout", ...)``
 -  ``loader.lazyGetter(this, "toolboxStrings"``
 -  ``XPCOMUtils.defineLazyGetter(this, "clipboardHelper"``
 
 
 mark-test-function-used
 -----------------------
 
-Simply marks test (the test method) as used. This avoids ESLint telling
-us that the function is never called.
+Simply marks `test` (the test method) or `run_test` as used when in mochitests
+or xpcshell tests respectively. This avoids ESLint telling us that the function
+is never called.
 
 
 no-aArgs
 --------
 
 Checks that function argument names don't start with lowercase 'a' followed by
 a capital letter. This is to prevent the use of Hungarian notation whereby the
 first letter is a prefix that indicates the type or intended use of a variable.
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/mark-test-function-used.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/mark-test-function-used.js
@@ -1,11 +1,12 @@
 /**
- * @fileoverview Simply marks test (the test method) as used. This avoids ESLint
- * telling us that the function is never called..
+ * @fileoverview Simply marks `test` (the test method) or `run_test` as used when
+ * in mochitests or xpcshell tests respectively. This avoids ESLint telling us
+ * that the function is never called.
  *
  * 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/.
  */
 
 "use strict";
 
@@ -17,16 +18,20 @@ var helpers = require("../helpers");
 
 module.exports = function(context) {
   // ---------------------------------------------------------------------------
   // Public
   // ---------------------------------------------------------------------------
 
   return {
     Program: function() {
-      if (!helpers.getIsBrowserMochitest(this)) {
+      if (helpers.getIsBrowserMochitest(this)) {
+        context.markVariableAsUsed("test");
         return;
       }
 
-      context.markVariableAsUsed("test");
+      if (helpers.getIsXpcshellTest(this)) {
+        context.markVariableAsUsed("run_test");
+        return;
+      }
     }
   };
 };
--- a/widget/nsNativeTheme.cpp
+++ b/widget/nsNativeTheme.cpp
@@ -559,23 +559,23 @@ nsNativeTheme::IsVerticalProgress(nsIFra
   return IsVerticalMeter(aFrame);
 }
 
 bool
 nsNativeTheme::IsVerticalMeter(nsIFrame* aFrame)
 {
   NS_PRECONDITION(aFrame, "You have to pass a non-null aFrame");
   switch (aFrame->StyleDisplay()->mOrient) {
-    case NS_STYLE_ORIENT_HORIZONTAL:
+    case StyleOrient::Horizontal:
       return false;
-    case NS_STYLE_ORIENT_VERTICAL:
+    case StyleOrient::Vertical:
       return true;
-    case NS_STYLE_ORIENT_INLINE:
+    case StyleOrient::Inline:
       return aFrame->GetWritingMode().IsVertical();
-    case NS_STYLE_ORIENT_BLOCK:
+    case StyleOrient::Block:
       return !aFrame->GetWritingMode().IsVertical();
   }
   NS_NOTREACHED("unexpected -moz-orient value");
   return false;
 }
 
 // menupopup:
 bool
--- a/xpcom/glue/nsThreadUtils.cpp
+++ b/xpcom/glue/nsThreadUtils.cpp
@@ -2,16 +2,17 @@
 /* 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 "nsThreadUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Likely.h"
+#include "mozilla/TimeStamp.h"
 #include "LeakRefPtr.h"
 
 #ifdef MOZILLA_INTERNAL_API
 # include "nsThreadManager.h"
 #else
 # include "nsXPCOMCIDInternal.h"
 # include "nsIThreadManager.h"
 # include "nsServiceManagerUtils.h"
@@ -27,16 +28,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 +56,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 +216,68 @@ 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);
 }
 
+nsresult
+NS_DelayedDispatchToCurrentThread(already_AddRefed<nsIRunnable>&& aEvent, uint32_t aDelayMs)
+{
+  nsCOMPtr<nsIRunnable> event(aEvent);
+#ifdef MOZILLA_INTERNAL_API
+  nsIThread* thread = NS_GetCurrentThread();
+  if (!thread) {
+    return NS_ERROR_UNEXPECTED;
+  }
+#else
+  nsresult rv;
+  nsCOMPtr<nsIThread> thread;
+  rv = NS_GetCurrentThread(getter_AddRefs(thread));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+#endif
+
+  return thread->DelayedDispatch(event.forget(), aDelayMs);
+}
+
+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,23 +9,26 @@
 
 #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/TimeStamp.h"
 #include "mozilla/Tuple.h"
 #include "mozilla/TypeTraits.h"
 
 //-----------------------------------------------------------------------------
 // These methods are alternatives to the methods on nsIThreadManager, provided
 // for convenience.
 
 /**
@@ -125,16 +128,23 @@ 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_DelayedDispatchToCurrentThread(
+  already_AddRefed<nsIRunnable>&& aEvent, uint32_t aDelayMs);
+
+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 +228,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 +280,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,73 @@ 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;
+  {
+    MutexAutoUnlock unlock(mLock);
+    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 +1154,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,58 @@ 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.
-  // 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.
+  // This lock protects access to mObserver, mEvents, mIdleEvents,
+  // mIdlePeriod and mEventsAreDoomed.  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;
 }