Merge mozilla-central to autoland
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 01 Jun 2017 13:57:22 +0200
changeset 361777 4db765715213ac3e0bf46fe492acb5b031b128e7
parent 361776 457e6aea4650b4cc8bbc6581342fdc44bc8dbec1 (current diff)
parent 361760 0bcea6bac1797e14b00af45cc7c368d12460ab7f (diff)
child 361778 e62d60b214d1d68a604fb7a65b01a5a6691743b2
push id31942
push userryanvm@gmail.com
push dateThu, 01 Jun 2017 15:54:15 +0000
treeherdermozilla-central@cac2fd43de81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to autoland
browser/themes/shared/compacttheme.inc.css
browser/themes/shared/tabbrowser/tab-audio.svg
--- a/browser/base/content/test/general/browser_bug462289.js
+++ b/browser/base/content/test/general/browser_bug462289.js
@@ -10,39 +10,39 @@ function focus_in_navbar() {
 
 function test() {
   waitForExplicitFinish();
 
   tab1 = BrowserTestUtils.addTab(gBrowser, "about:blank", {skipAnimation: true});
   tab2 = BrowserTestUtils.addTab(gBrowser, "about:blank", {skipAnimation: true});
 
   EventUtils.synthesizeMouseAtCenter(tab1, {});
-  executeSoon(step2);
+  setTimeout(step2, 0);
 }
 
 function step2() {
   is(gBrowser.selectedTab, tab1, "1st click on tab1 selects tab");
   isnot(document.activeElement, tab1, "1st click on tab1 does not activate tab");
 
   EventUtils.synthesizeMouseAtCenter(tab1, {});
-  executeSoon(step3);
+  setTimeout(step3, 0);
 }
 
 function step3() {
   is(gBrowser.selectedTab, tab1, "2nd click on selected tab1 keeps tab selected");
   isnot(document.activeElement, tab1, "2nd click on selected tab1 does not activate tab");
 
   ok(true, "focusing URLBar then sending 1 Shift+Tab.");
   gURLBar.focus();
   EventUtils.synthesizeKey("VK_TAB", {shiftKey: true});
   is(gBrowser.selectedTab, tab1, "tab key to selected tab1 keeps tab selected");
   is(document.activeElement, tab1, "tab key to selected tab1 activates tab");
 
   EventUtils.synthesizeMouseAtCenter(tab1, {});
-  executeSoon(step4);
+  setTimeout(step4, 0);
 }
 
 function step4() {
   is(gBrowser.selectedTab, tab1, "3rd click on activated tab1 keeps tab selected");
   is(document.activeElement, tab1, "3rd click on activated tab1 keeps tab activated");
 
   gBrowser.addEventListener("TabSwitchDone", step5);
   EventUtils.synthesizeMouseAtCenter(tab2, {});
@@ -55,17 +55,17 @@ function step5() {
   // listener, and focuses the current tab if another tab previously had focus.
   is(gBrowser.selectedTab, tab2, "click on tab2 while tab1 is activated selects tab");
   is(document.activeElement, tab2, "click on tab2 while tab1 is activated activates tab");
 
   info("focusing content then sending middle-button mousedown to tab2.");
   gBrowser.selectedBrowser.focus();
 
   EventUtils.synthesizeMouseAtCenter(tab2, {button: 1, type: "mousedown"});
-  executeSoon(step6);
+  setTimeout(step6, 0);
 }
 
 function step6() {
   is(gBrowser.selectedTab, tab2, "middle-button mousedown on selected tab2 keeps tab selected");
   isnot(document.activeElement, tab2, "middle-button mousedown on selected tab2 does not activate tab");
 
   gBrowser.removeTab(tab2);
   gBrowser.removeTab(tab1);
--- a/browser/themes/shared/compacttheme.inc.css
+++ b/browser/themes/shared/compacttheme.inc.css
@@ -351,17 +351,17 @@ window:not([chromehidden~="toolbar"]) #u
 @media (min-resolution: 1.1dppx) {
   .tab-throbber[selected][progress] {
     list-style-image: url("chrome://browser/skin/compacttheme/loading-inverted@2x.png");
   }
 }
 
 .tab-icon-sound[soundplaying],
 .tab-icon-sound[muted] {
-  filter: url(chrome://global/skin/filters.svg#fill) !important; /* removes drop-shadow filter */
+  filter: none !important; /* removes drop-shadow filter */
 }
 
 /* Don't need space for the tab curves (66px - 30px) */
 .tabs-newtab-button {
   width: 36px;
 }
 
 .tabs-newtab-button:hover {
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -159,17 +159,19 @@
   skin/classic/browser/search-indicator-magnifying-glass.svg   (../shared/search/search-indicator-magnifying-glass.svg)
   skin/classic/browser/search-arrow-go.svg                     (../shared/search/search-arrow-go.svg)
   skin/classic/browser/gear.svg                                (../shared/search/gear.svg)
   skin/classic/browser/sidebar/close.svg                       (../shared/sidebar/close.svg)
   skin/classic/browser/tabbrowser/connecting.png               (../shared/tabbrowser/connecting.png)
   skin/classic/browser/tabbrowser/connecting@2x.png            (../shared/tabbrowser/connecting@2x.png)
   skin/classic/browser/tabbrowser/crashed.svg                  (../shared/tabbrowser/crashed.svg)
   skin/classic/browser/tabbrowser/pendingpaint.png             (../shared/tabbrowser/pendingpaint.png)
-  skin/classic/browser/tabbrowser/tab-audio.svg                (../shared/tabbrowser/tab-audio.svg)
+  skin/classic/browser/tabbrowser/tab-audio-playing.svg        (../shared/tabbrowser/tab-audio-playing.svg)
+  skin/classic/browser/tabbrowser/tab-audio-muted.svg          (../shared/tabbrowser/tab-audio-muted.svg)
+  skin/classic/browser/tabbrowser/tab-audio-blocked.svg        (../shared/tabbrowser/tab-audio-blocked.svg)
   skin/classic/browser/tabbrowser/tab-audio-small.svg          (../shared/tabbrowser/tab-audio-small.svg)
   skin/classic/browser/tabbrowser/tab-overflow-indicator.png   (../shared/tabbrowser/tab-overflow-indicator.png)
   skin/classic/browser/toolbarbutton-dropdown-arrow.png        (../shared/toolbarbutton-dropdown-arrow.png)
   skin/classic/browser/toolbarbutton-dropdown-arrow-inverted.png (../shared/toolbarbutton-dropdown-arrow-inverted.png)
   skin/classic/browser/translating-16.png                      (../shared/translation/translating-16.png)
   skin/classic/browser/translating-16@2x.png                   (../shared/translation/translating-16@2x.png)
   skin/classic/browser/translation-16.png                      (../shared/translation/translation-16.png)
   skin/classic/browser/translation-16@2x.png                   (../shared/translation/translation-16@2x.png)
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/tabbrowser/tab-audio-blocked.svg
@@ -0,0 +1,9 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg"
+     width="16" height="16" viewBox="0 0 16 16">
+  <path fill="context-fill" d="M8,0C3.6,0,0,3.6,0,8s3.6,8,8,8s8-3.6,8-8S12.4,0,8,0z M5.6,11.6l6-3.6l-6-3.6V11.6z M8,14.2
+  c-3.4,0-6.2-2.8-6.2-6.2S4.6,1.8,8,1.8s6.2,2.8,6.2,6.2S11.4,14.2,8,14.2z"/>
+</svg>
+
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/tabbrowser/tab-audio-muted.svg
@@ -0,0 +1,9 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg"
+     width="16" height="16" viewBox="0 0 16 16">
+  <path fill="context-fill" d="M12.5,3.4L9,6.3V2L5.2,5H4C2.9,5,2,5.9,2,7v2c0,0.9,0.6,1.6,1.4,1.9l-1.9,1.5l1,1.2l11-9L12.5,3.4z M9,14v-4l-2.5,2L9,14z"/>
+  c-3.4,0-6.2-2.8-6.2-6.2S4.6,1.8,8,1.8s6.2,2.8,6.2,6.2S11.4,14.2,8,14.2z"/>
+</svg>
+
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/tabbrowser/tab-audio-playing.svg
@@ -0,0 +1,9 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg"
+     width="16" height="16" viewBox="0 0 16 16">
+  <path fill="context-fill" d="M4,5C2.9,5,2,5.9,2,7v2c0,1.1,0.9,2,2,2h1.2L9,14V2L5.2,5H4z M11,8c0-0.6-0.4-1-1-1v2C10.6,9,11,8.6,11,8z M13,8 c0-1.4-1-2.6-2.3-2.9L10.4,6C11.3,6.2,12,7,12,8s-0.7,1.8-1.6,2l0.4,0.9C12,10.6,13,9.4,13,8z M11.4,3.2l-0.4,0.9 C12.8,4.6,14,6.2,14,8s-1.2,3.4-2.9,3.8l0.4,0.9C13.5,12.2,15,10.3,15,8S13.5,3.8,11.4,3.2z"/>
+  c-3.4,0-6.2-2.8-6.2-6.2S4.6,1.8,8,1.8s6.2,2.8,6.2,6.2S11.4,14.2,8,14.2z"/>
+</svg>
+
deleted file mode 100644
--- a/browser/themes/shared/tabbrowser/tab-audio.svg
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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/. -->
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
-  <style>
-    path:not(:target) {
-      display: none;
-    }
-  </style>
-
-  <path id="tab-audio" d="M4,5C2.9,5,2,5.9,2,7v2c0,1.1,0.9,2,2,2h1.2L9,14V2L5.2,5H4z M11,8c0-0.6-0.4-1-1-1v2C10.6,9,11,8.6,11,8z M13,8 c0-1.4-1-2.6-2.3-2.9L10.4,6C11.3,6.2,12,7,12,8s-0.7,1.8-1.6,2l0.4,0.9C12,10.6,13,9.4,13,8z M11.4,3.2l-0.4,0.9 C12.8,4.6,14,6.2,14,8s-1.2,3.4-2.9,3.8l0.4,0.9C13.5,12.2,15,10.3,15,8S13.5,3.8,11.4,3.2z"/>
-
-  <path id="tab-audio-muted" d="M12.5,3.4L9,6.3V2L5.2,5H4C2.9,5,2,5.9,2,7v2c0,0.9,0.6,1.6,1.4,1.9l-1.9,1.5l1,1.2l11-9L12.5,3.4z M9,14v-4l-2.5,2L9,14z"/>
-
-  <path id="tab-audio-blocked" d="M8,0C3.6,0,0,3.6,0,8s3.6,8,8,8s8-3.6,8-8S12.4,0,8,0z M5.6,11.6l6-3.6l-6-3.6V11.6z M8,14.2
-  c-3.4,0-6.2-2.8-6.2-6.2S4.6,1.8,8,1.8s6.2,2.8,6.2,6.2S11.4,14.2,8,14.2z"/>
-</svg>
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -202,39 +202,39 @@
   width: 16px;
   height: 16px;
   padding: 0;
 }
 
 .tab-icon-sound[soundplaying],
 .tab-icon-sound[muted],
 .tab-icon-sound[blocked] {
-  list-style-image: url(chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio);
-  filter: url(chrome://global/skin/filters.svg#fill);
+  list-style-image: url(chrome://browser/skin/tabbrowser/tab-audio-playing.svg);
+  -moz-context-properties: fill;
   fill: currentColor;
 }
 
 .tab-icon-sound[muted] {
-  list-style-image: url(chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-muted);
+  list-style-image: url(chrome://browser/skin/tabbrowser/tab-audio-muted.svg);
 }
 
 .tab-icon-sound[blocked] {
-  list-style-image: url(chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-blocked);
+  list-style-image: url(chrome://browser/skin/tabbrowser/tab-audio-blocked.svg);
 }
 
 .tab-icon-sound:-moz-lwtheme-darktext[soundplaying],
 .tab-icon-sound:-moz-lwtheme-darktext[muted],
 .tab-icon-sound:-moz-lwtheme-darktext[blocked] {
-  filter: url(chrome://global/skin/filters.svg#fill) drop-shadow(1px 1px 1px white);
+  filter: drop-shadow(1px 1px 1px white);
 }
 
 .tab-icon-sound:-moz-lwtheme-brighttext[soundplaying],
 .tab-icon-sound:-moz-lwtheme-brighttext[muted],
 .tab-icon-sound:-moz-lwtheme-brighttext[blocked] {
-  filter: url(chrome://global/skin/filters.svg#fill) drop-shadow(1px 1px 1px black);
+  filter: drop-shadow(1px 1px 1px black);
 }
 
 .tab-icon-sound[soundplaying]:not(:hover),
 .tab-icon-sound[muted]:not(:hover),
 .tab-icon-sound[blocked]:not(:hover) {
   opacity: .8;
 }
 
@@ -576,20 +576,20 @@
 .alltabs-item[tabIsVisible] {
   /* box-shadow instead of background-color to work around native styling */
   box-shadow: inset -5px 0 ThreeDShadow;
 }
 
 .alltabs-endimage[soundplaying],
 .alltabs-endimage[muted],
 .alltabs-endimage[blocked] {
-  list-style-image: url(chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio);
-  filter: url(chrome://global/skin/filters.svg#fill);
+  list-style-image: url(chrome://browser/skin/tabbrowser/tab-audio-playing.svg);
+  -moz-context-properties: fill;
   fill: currentColor;
 }
 
 .alltabs-endimage[muted] {
-  list-style-image: url(chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-muted);
+  list-style-image: url(chrome://browser/skin/tabbrowser/tab-audio-muted.svg);
 }
 
 .alltabs-endimage[blocked] {
-  list-style-image: url(chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-blocked);
+  list-style-image: url(chrome://browser/skin/tabbrowser/tab-audio-blocked.svg);
 }
--- a/build/moz.configure/warnings.configure
+++ b/build/moz.configure/warnings.configure
@@ -107,11 +107,16 @@ check_and_add_gcc_warning('-Wformat')
 # We use mix of both POSIX and Win32 printf format across the tree, so format
 # warnings are useless on mingw.
 check_and_add_gcc_warning('-Wno-format',
                           when=depends(target)(lambda t: t.kernel == 'WINNT'))
 
 # We hit this all over the place with the gtest INSTANTIATE_TEST_CASE_P macro
 check_and_add_gcc_warning('-Wno-gnu-zero-variadic-macro-arguments')
 
+# Add compile-time warnings for unprotected functions and format functions
+# that represent possible security problems
+check_and_add_gcc_warning('-Wformat-security')
+check_and_add_gcc_warning('-Wformat-overflow=2')
+
 # Please keep these last in this file
 add_old_configure_assignment('_WARNINGS_CFLAGS', warnings_cflags)
 add_old_configure_assignment('_WARNINGS_CXXFLAGS', warnings_cxxflags)
--- a/devtools/client/debugger/test/mochitest/head.js
+++ b/devtools/client/debugger/test/mochitest/head.js
@@ -47,18 +47,22 @@ registerCleanupFunction(function* () {
     info("Removing tab.");
     gBrowser.removeCurrentTab();
   }
 
   // Properly shut down the server to avoid memory leaks.
   DebuggerServer.destroy();
 
   // Debugger tests use a lot of memory, so force a GC to help fragmentation.
-  info("Forcing GC after debugger test.");
-  Cu.forceGC();
+  info("Forcing GC/CC after debugger test.");
+  yield new Promise(resolve => {
+    Cu.forceGC();
+    Cu.forceCC();
+    Cu.schedulePreciseGC(resolve);
+  });
 });
 
 // Import the GCLI test helper
 var testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
 testDir = testDir.replace(/\/\//g, "/");
 testDir = testDir.replace("chrome:/mochitest", "chrome://mochitest");
 var helpersjs = testDir + "/../../../commandline/test/helpers.js";
 Services.scriptloader.loadSubScript(helpersjs, this);
--- a/devtools/client/netmonitor/test/browser_net_simple-request-data.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request-data.js
@@ -8,46 +8,52 @@
  */
 
 // The following intermittent rejections should not be left uncaught. This test
 // has been whitelisted until the issue is fixed.
 //
 // NOTE: Whitelisting a class of rejections should be limited. Normally you
 //       should use "expectUncaughtRejection" to flag individual failures.
 Cu.import("resource://testing-common/PromiseTestUtils.jsm", this);
-PromiseTestUtils.whitelistRejectionsGlobally(/cookies is undefined/);
 PromiseTestUtils.whitelistRejectionsGlobally(/requestItem is undefined/);
 
 function test() {
   // Disable tcp fast open, because it is setting a response header indicator
   // (bug 1352274). TCP Fast Open is not present on all platforms therefore the
   // number of response headers will vary depending on the platform.
   Services.prefs.setBoolPref("network.tcp.tcp_fastopen_enable", false);
 
   let { L10N } = require("devtools/client/netmonitor/src/utils/l10n");
 
-  initNetMonitor(SIMPLE_SJS).then(({ tab, monitor }) => {
+  initNetMonitor(SIMPLE_SJS).then(async ({ tab, monitor }) => {
     info("Starting test... ");
 
     let { document, store, windowRequire } = monitor.panelWin;
     let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
     let { EVENTS } = windowRequire("devtools/client/netmonitor/src/constants");
     let {
       getDisplayedRequests,
       getSelectedRequest,
       getSortedRequests,
     } = windowRequire("devtools/client/netmonitor/src/selectors/index");
 
     store.dispatch(Actions.batchEnable(false));
 
-    waitForNetworkEvents(monitor, 1)
-      .then(() => teardown(monitor))
-      .then(finish);
+    let promiseList = [];
+    promiseList.push(waitForNetworkEvents(monitor, 1));
 
-    monitor.panelWin.once(EVENTS.NETWORK_EVENT, () => {
+    function expectEvent(evt, cb) {
+      promiseList.push(new Promise((resolve, reject) => {
+        monitor.panelWin.once(evt, _ => {
+          cb().then(resolve, reject);
+        });
+      }));
+    }
+
+    expectEvent(EVENTS.NETWORK_EVENT, async () => {
       is(getSelectedRequest(store.getState()), null,
         "There shouldn't be any selected item in the requests menu.");
       is(store.getState().requests.requests.size, 1,
         "The requests menu should not be empty after the first request.");
       is(!!document.querySelector(".network-details-panel"), false,
         "The network details panel should still be hidden after first request.");
 
       let requestItem = getSortedRequests(store.getState()).get(0);
@@ -100,20 +106,20 @@ function test() {
         document,
         getDisplayedRequests(store.getState()),
         requestItem,
         "GET",
         SIMPLE_SJS
       );
     });
 
-    monitor.panelWin.once(EVENTS.RECEIVED_REQUEST_HEADERS, async () => {
+    expectEvent(EVENTS.RECEIVED_REQUEST_HEADERS, async () => {
       await waitUntil(() => {
         let requestItem = getSortedRequests(store.getState()).get(0);
-        return requestItem.requestHeaders;
+        return requestItem && requestItem.requestHeaders;
       });
 
       let requestItem = getSortedRequests(store.getState()).get(0);
 
       ok(requestItem.requestHeaders,
         "There should be a requestHeaders data available.");
       is(requestItem.requestHeaders.headers.length, 10,
         "The requestHeaders data has an incorrect |headers| property.");
@@ -126,46 +132,46 @@ function test() {
         document,
         getDisplayedRequests(store.getState()),
         requestItem,
         "GET",
         SIMPLE_SJS
       );
     });
 
-    monitor.panelWin.once(EVENTS.RECEIVED_REQUEST_COOKIES, async () => {
+    expectEvent(EVENTS.RECEIVED_REQUEST_COOKIES, async () => {
       await waitUntil(() => {
         let requestItem = getSortedRequests(store.getState()).get(0);
-        return requestItem.requestCookies;
+        return requestItem && requestItem.requestCookies;
       });
 
       let requestItem = getSortedRequests(store.getState()).get(0);
 
       ok(requestItem.requestCookies,
         "There should be a requestCookies data available.");
-      is(requestItem.requestCookies.cookies.length, 2,
+      is(requestItem.requestCookies.length, 2,
         "The requestCookies data has an incorrect |cookies| property.");
 
       verifyRequestItemTarget(
         document,
         getDisplayedRequests(store.getState()),
         requestItem,
         "GET",
         SIMPLE_SJS
       );
     });
 
     monitor.panelWin.once(EVENTS.RECEIVED_REQUEST_POST_DATA, () => {
       ok(false, "Trap listener: this request doesn't have any post data.");
     });
 
-    monitor.panelWin.once(EVENTS.RECEIVED_RESPONSE_HEADERS, async () => {
+    expectEvent(EVENTS.RECEIVED_RESPONSE_HEADERS, async () => {
       await waitUntil(() => {
         let requestItem = getSortedRequests(store.getState()).get(0);
-        return requestItem.responseHeaders;
+        return requestItem && requestItem.responseHeaders;
       });
 
       let requestItem = getSortedRequests(store.getState()).get(0);
 
       ok(requestItem.responseHeaders,
         "There should be a responseHeaders data available.");
       is(requestItem.responseHeaders.headers.length, 10,
         "The responseHeaders data has an incorrect |headers| property.");
@@ -176,42 +182,43 @@ function test() {
         document,
         getDisplayedRequests(store.getState()),
         requestItem,
         "GET",
         SIMPLE_SJS
       );
     });
 
-    monitor.panelWin.once(EVENTS.RECEIVED_RESPONSE_COOKIES, async () => {
+    expectEvent(EVENTS.RECEIVED_RESPONSE_COOKIES, async () => {
       await waitUntil(() => {
         let requestItem = getSortedRequests(store.getState()).get(0);
-        return requestItem.responseCookies;
+        return requestItem && requestItem.responseCookies;
       });
 
       let requestItem = getSortedRequests(store.getState()).get(0);
 
       ok(requestItem.responseCookies,
         "There should be a responseCookies data available.");
-      is(requestItem.responseCookies.cookies.length, 2,
+      is(requestItem.responseCookies.length, 2,
         "The responseCookies data has an incorrect |cookies| property.");
 
       verifyRequestItemTarget(
         document,
         getDisplayedRequests(store.getState()),
         requestItem,
         "GET",
         SIMPLE_SJS
       );
     });
 
-    monitor.panelWin.once(EVENTS.STARTED_RECEIVING_RESPONSE, async () => {
+    expectEvent(EVENTS.STARTED_RECEIVING_RESPONSE, async () => {
       await waitUntil(() => {
         let requestItem = getSortedRequests(store.getState()).get(0);
-        return requestItem.httpVersion &&
+        return requestItem &&
+               requestItem.httpVersion &&
                requestItem.status &&
                requestItem.statusText &&
                requestItem.headersSize;
       });
 
       let requestItem = getSortedRequests(store.getState()).get(0);
 
       is(requestItem.httpVersion, "HTTP/1.1",
@@ -231,20 +238,21 @@ function test() {
         SIMPLE_SJS,
         {
           status: "200",
           statusText: "Och Aye"
         }
       );
     });
 
-    monitor.panelWin.once(EVENTS.RECEIVED_RESPONSE_CONTENT, async () => {
+    expectEvent(EVENTS.RECEIVED_RESPONSE_CONTENT, async () => {
       await waitUntil(() => {
         let requestItem = getSortedRequests(store.getState()).get(0);
-        return requestItem.transferredSize &&
+        return requestItem &&
+               requestItem.transferredSize &&
                requestItem.contentSize &&
                requestItem.mimeType &&
                requestItem.responseContent;
       });
 
       let requestItem = getSortedRequests(store.getState()).get(0);
 
       is(requestItem.transferredSize, "12",
@@ -279,20 +287,20 @@ function test() {
           type: "plain",
           fullMimeType: "text/plain; charset=utf-8",
           transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
           size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
         }
       );
     });
 
-    monitor.panelWin.once(EVENTS.UPDATING_EVENT_TIMINGS, async () => {
+    expectEvent(EVENTS.UPDATING_EVENT_TIMINGS, async () => {
       await waitUntil(() => {
         let requestItem = getSortedRequests(store.getState()).get(0);
-        return requestItem.eventTimings;
+        return requestItem && requestItem.eventTimings;
       });
 
       let requestItem = getSortedRequests(store.getState()).get(0);
 
       is(typeof requestItem.totalTime, "number",
         "The attached totalTime is incorrect.");
       ok(requestItem.totalTime >= 0,
         "The attached totalTime should be positive.");
@@ -304,17 +312,17 @@ function test() {
         "GET",
         SIMPLE_SJS,
         {
           time: true
         }
       );
     });
 
-    monitor.panelWin.once(EVENTS.RECEIVED_EVENT_TIMINGS, () => {
+    expectEvent(EVENTS.RECEIVED_EVENT_TIMINGS, async () => {
       let requestItem = getSortedRequests(store.getState()).get(0);
 
       ok(requestItem.eventTimings,
         "There should be a eventTimings data available.");
       is(typeof requestItem.eventTimings.timings.blocked, "number",
         "The eventTimings data has an incorrect |timings.blocked| property.");
       is(typeof requestItem.eventTimings.timings.dns, "number",
         "The eventTimings data has an incorrect |timings.dns| property.");
@@ -337,10 +345,14 @@ function test() {
         SIMPLE_SJS,
         {
           time: true
         }
       );
     });
 
     tab.linkedBrowser.reload();
+
+    await Promise.all(promiseList);
+    await teardown(monitor);
+    finish();
   });
 }
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -96,16 +96,17 @@ const gDefaultFilters = Services.prefs.g
 Services.prefs.setCharPref("devtools.netmonitor.hiddenColumns", "[]");
 
 registerCleanupFunction(() => {
   info("finish() was called, cleaning up...");
 
   Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
   Services.prefs.setCharPref("devtools.netmonitor.filters", gDefaultFilters);
   Services.prefs.clearUserPref("devtools.cache.disabled");
+  Services.cookies.removeAll();
 });
 
 function waitForNavigation(target) {
   return new Promise((resolve) => {
     target.once("will-navigate", () => {
       target.once("navigate", () => {
         resolve();
       });
--- a/docshell/test/chrome/bug360511_window.xul
+++ b/docshell/test/chrome/bug360511_window.xul
@@ -53,17 +53,17 @@
       
         // Click the on the fragment link in the browser, and use setTimeout 
         // to give the event a chance to be processed.
         var event = TestWindow.getDocument().createEvent('MouseEvent');
         event.initMouseEvent("click", true, true, TestWindow.getWindow(), 0,
           0, 0, 0, 0,
           false, false, false, false, 0, null);
         TestWindow.getDocument().getElementById("link1").dispatchEvent(event);
-        setTimeout(nextTest, 0);
+        waitForNextPaint(nextTest);
         yield undefined;
 
         // Store the fragment url for later comparison.
         var fragmentUrl = TestWindow.getBrowser().currentURI.spec;
         var fragDocLocation = TestWindow.getDocument().location.href;
         
         // Verify we're no longer at the top of the page.
         ok(TestWindow.getWindow().scrollY > 0,
--- a/docshell/test/chrome/docshell_helpers.js
+++ b/docshell/test/chrome/docshell_helpers.js
@@ -278,17 +278,17 @@ function pageEventListener(event) {
        "Should not get unexpected event " + event.type);
   }  
 
   // If no expected events were specified, mark the final event as having been 
   // triggered when a pageshow event is fired; this will allow 
   // doPageNavigation() to return.
   if ((typeof(gExpectedEvents) == "undefined") && event.type == "pageshow")
   {
-    setTimeout(function() { gFinalEvent = true; }, 0);
+    waitForNextPaint(function() { gFinalEvent = true; });
     return;
   }
   
   // If there are explicitly no expected events, but we receive one, it's an 
   // error.
   if (gExpectedEvents.length == 0) {
     ok(false, "Unexpected event (" + event.type + ") occurred");
     return;
@@ -327,17 +327,17 @@ function pageEventListener(event) {
   if ("hidden" in expected) {
     is(event.originalTarget.hidden, expected.hidden,
        "The hidden property of the document on page " +
        event.originalTarget.location + " had an unexpected value");
   }
 
   // If we're out of expected events, let doPageNavigation() return.
   if (gExpectedEvents.length == 0)
-    setTimeout(function() { gFinalEvent = true; }, 0);
+    waitForNextPaint(function() { gFinalEvent = true; });
 }
 
 /**
  * End a test.  
  */
 function finish() {
   // Work around bug 467960.
   var history = TestWindow.getBrowser().webNavigation.sessionHistory;
@@ -412,16 +412,20 @@ function waitForTrue(fn, onWaitComplete,
         if (timeoutHit || fn.call()) {
           // Stop calling the test function and notify the callback.
           clearInterval(intervalid);
           onWaitComplete.call();          
         } 
       }, 20);
 }
 
+function waitForNextPaint(cb) {
+  requestAnimationFrame(_ => requestAnimationFrame(cb));
+}
+
 /**
  * Enable or disable the bfcache.
  *
  * Parameters:
  *
  *   enable: if true, set max_total_viewers to -1 (the default); if false, set 
  *           to 0 (disabled), if a number, set it to that specific number
  */
--- a/dom/base/Timeout.cpp
+++ b/dom/base/Timeout.cpp
@@ -1,19 +1,16 @@
 /* -*- 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 "mozilla/dom/TimeoutManager.h"
 
 namespace mozilla {
 namespace dom {
 
 Timeout::Timeout()
   : mCleared(false),
     mRunning(false),
@@ -21,131 +18,58 @@ Timeout::Timeout()
     mIsTracking(false),
     mReason(Reason::eTimeoutOrInterval),
     mTimeoutId(0),
     mInterval(0),
     mFiringId(TimeoutManager::InvalidFiringId),
     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(mScriptHandler)
+  tmp->remove();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Timeout)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
   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)
 
-namespace {
-
-void
-TimerCallback(nsITimer*, void* aClosure)
-{
-  RefPtr<Timeout> timeout = (Timeout*)aClosure;
-  timeout->mWindow->AsInner()->TimeoutManager().RunTimeout(timeout);
-}
-
-void
-TimerNameCallback(nsITimer* aTimer, bool aAnonymize, void* aClosure,
-                  char* aBuf, size_t aLen)
-{
-  RefPtr<Timeout> timeout = (Timeout*)aClosure;
-
-  // Filename and line-number information is privacy sensitive. If we're
-  // supposed to anonymize the data, don't include it.
-  if (aAnonymize) {
-    if (timeout->mIsInterval) {
-      snprintf(aBuf, aLen, "setInterval");
-    } else {
-      snprintf(aBuf, aLen, "setTimeout");
-    }
-    return;
-  }
-
-  const char* filename;
-  uint32_t lineNum, column;
-  timeout->mScriptHandler->GetLocation(&filename, &lineNum, &column);
-  snprintf(aBuf, aLen, "[content] %s:%u:%u", filename, lineNum, column);
-}
-
-} // anonymous namespace
-
-nsresult
-Timeout::InitTimer(nsIEventTarget* aTarget, uint32_t aDelay)
-{
-  // If the given target does not match the timer's current target
-  // then we need to override it before the Init.  Note that GetTarget()
-  // will return the current thread after setting the target to nullptr.
-  // So we need to special case the nullptr target comparison.
-  nsCOMPtr<nsIEventTarget> currentTarget;
-  MOZ_ALWAYS_SUCCEEDS(mTimer->GetTarget(getter_AddRefs(currentTarget)));
-  if ((aTarget && currentTarget != aTarget) ||
-      (!aTarget && currentTarget != NS_GetCurrentThread())) {
-    // Always call Cancel() in case we are re-using a timer.  Otherwise
-    // the subsequent SetTarget() may fail.
-    MOZ_ALWAYS_SUCCEEDS(mTimer->Cancel());
-    MOZ_ALWAYS_SUCCEEDS(mTimer->SetTarget(aTarget));
-  }
-
-  return mTimer->InitWithNameableFuncCallback(
-    TimerCallback, this, aDelay, nsITimer::TYPE_ONE_SHOT, TimerNameCallback);
-}
-
-// Return true if this timeout has a refcount of aCount. This is used to check
-// that dummy_timeout doesn't leak from nsGlobalWindow::RunTimeout.
-#ifdef DEBUG
-bool
-Timeout::HasRefCnt(uint32_t aCount) const
-{
-  return mRefCnt.get() == aCount;
-}
-#endif // DEBUG
-
 void
 Timeout::SetWhenOrTimeRemaining(const TimeStamp& aBaseTime,
                                 const TimeDuration& aDelay)
 {
   // This must not be called on dummy timeouts.  Instead use SetDummyWhen().
   MOZ_DIAGNOSTIC_ASSERT(mWindow);
 
   // If we are frozen simply set mTimeRemaining to be the "time remaining" in
   // the timeout (i.e., the interval itself).  This will be used to create a
   // new mWhen time when the window is thawed.  The end effect is that time does
   // not appear to pass for frozen windows.
   if (mWindow->IsFrozen()) {
     mWhen = TimeStamp();
     mTimeRemaining = aDelay;
+    mScheduledDelay = TimeDuration(0);
     return;
   }
 
   // Since we are not frozen we must set a precise mWhen target wakeup
   // time.  Even if we are suspended we want to use this target time so
   // that it appears time passes while suspended.
   mWhen = aBaseTime + aDelay;
   mTimeRemaining = TimeDuration(0);
+  mScheduledDelay = aDelay;
 }
 
 void
 Timeout::SetDummyWhen(const TimeStamp& aWhen)
 {
   MOZ_DIAGNOSTIC_ASSERT(!mWindow);
   mWhen = aWhen;
 }
@@ -163,10 +87,17 @@ const TimeDuration&
 Timeout::TimeRemaining() const
 {
   MOZ_DIAGNOSTIC_ASSERT(mWhen.IsNull());
   // Note, mWindow->IsFrozen() can be false here.  The Thaw() method calls
   // TimeRemaining() to calculate the new When() value.
   return mTimeRemaining;
 }
 
+const TimeDuration&
+Timeout::ScheduledDelay() const
+{
+  MOZ_DIAGNOSTIC_ASSERT(!mWhen.IsNull());
+  return mScheduledDelay;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/Timeout.h
+++ b/dom/base/Timeout.h
@@ -6,73 +6,63 @@
 
 #ifndef mozilla_dom_timeout_h
 #define mozilla_dom_timeout_h
 
 #include "mozilla/LinkedList.h"
 #include "mozilla/TimeStamp.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
+#include "nsGlobalWindow.h"
+#include "nsITimeoutHandler.h"
 
-class nsGlobalWindow;
 class nsIEventTarget;
 class nsIPrincipal;
-class nsITimeoutHandler;
-class nsITimer;
 class nsIEventTarget;
 
 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 LinkedListElement<RefPtr<Timeout>>
 {
 public:
   Timeout();
 
   NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(Timeout)
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(Timeout)
 
-  // The target may be specified to use a particular event queue for the
-  // resulting timer runnable.  A nullptr target will result in the
-  // default main thread being used.
-  nsresult InitTimer(nsIEventTarget* aTarget, uint32_t aDelay);
-
   enum class Reason
   {
     eTimeoutOrInterval,
     eIdleCallbackTimeout,
   };
 
-#ifdef DEBUG
-  bool HasRefCnt(uint32_t aCount) const;
-#endif // DEBUG
-
   void SetWhenOrTimeRemaining(const TimeStamp& aBaseTime,
                               const TimeDuration& aDelay);
 
   void SetDummyWhen(const TimeStamp& aWhen);
 
   // Can only be called when not frozen.
   const TimeStamp& When() const;
 
   // Can only be called when frozen.
   const TimeDuration& TimeRemaining() const;
 
+  // Can only be called when not frozen.
+  const TimeDuration& ScheduledDelay() const;
+
   // 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;
@@ -104,18 +94,23 @@ public:
   nsCOMPtr<nsITimeoutHandler> mScriptHandler;
 
 private:
   // 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
   // frozen.
   TimeStamp mWhen;
+
   // Remaining time to wait.  Used only when timeouts are frozen.
   TimeDuration mTimeRemaining;
 
-  ~Timeout();
+  // The actual interval in milliseconds.  This may be throttled to
+  // a longer delay than mInterval for a number of reasons.
+  TimeDuration mScheduledDelay;
+
+  ~Timeout() = default;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_timeout_h
new file mode 100644
--- /dev/null
+++ b/dom/base/TimeoutExecutor.cpp
@@ -0,0 +1,240 @@
+/* -*- 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 "TimeoutExecutor.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ISUPPORTS(TimeoutExecutor, nsIRunnable, nsITimerCallback, nsINamed)
+
+TimeoutExecutor::~TimeoutExecutor()
+{
+  // The TimeoutManager should keep the Executor alive until its destroyed,
+  // and then call Shutdown() explicitly.
+  MOZ_DIAGNOSTIC_ASSERT(mMode == Mode::Shutdown);
+  MOZ_DIAGNOSTIC_ASSERT(!mOwner);
+  MOZ_DIAGNOSTIC_ASSERT(!mTimer);
+}
+
+nsresult
+TimeoutExecutor::ScheduleImmediate(const TimeStamp& aDeadline,
+                                   const TimeStamp& aNow)
+{
+  MOZ_DIAGNOSTIC_ASSERT(mDeadline.IsNull());
+  MOZ_DIAGNOSTIC_ASSERT(mMode == Mode::None);
+  MOZ_DIAGNOSTIC_ASSERT(aDeadline <= (aNow + mAllowedEarlyFiringTime));
+
+  nsresult rv =
+    mOwner->EventTarget()->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mMode = Mode::Immediate;
+  mDeadline = aDeadline;
+
+  return NS_OK;
+}
+
+nsresult
+TimeoutExecutor::ScheduleDelayed(const TimeStamp& aDeadline,
+                                 const TimeStamp& aNow)
+{
+  MOZ_DIAGNOSTIC_ASSERT(mDeadline.IsNull());
+  MOZ_DIAGNOSTIC_ASSERT(mMode == Mode::None);
+  MOZ_DIAGNOSTIC_ASSERT(aDeadline > (aNow + mAllowedEarlyFiringTime));
+
+  nsresult rv = NS_OK;
+
+  if (!mTimer) {
+    mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    uint32_t earlyMicros = 0;
+    MOZ_ALWAYS_SUCCEEDS(mTimer->GetAllowedEarlyFiringMicroseconds(&earlyMicros));
+    mAllowedEarlyFiringTime = TimeDuration::FromMicroseconds(earlyMicros);
+  }
+
+  // Always call Cancel() in case we are re-using a timer.  Otherwise
+  // the subsequent SetTarget() may fail.
+  rv = mTimer->Cancel();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mTimer->SetTarget(mOwner->EventTarget());
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Note, we cannot use the normal nsITimer init methods that take
+  // integer milliseconds.  We need higher precision.  Consider this
+  // situation:
+  //
+  // 1. setTimeout(f, 1);
+  // 2. do some work for 500us
+  // 3. setTimeout(g, 1);
+  //
+  // This should fire f() and g() 500us apart.
+  //
+  // In the past worked because each setTimeout() got its own nsITimer.  The 1ms
+  // was preserved and passed through to nsITimer which converted it to a
+  // TimeStamp, etc.
+  //
+  // Now, however, there is only one nsITimer.  We fire f() and then try to
+  // schedule a new nsITimer for g().  Its only 500us in the future, though.  We
+  // must be able to pass this fractional value to nsITimer in order to get an
+  // accurate wakeup time.
+  rv = mTimer->InitHighResolutionWithCallback(this, aDeadline - aNow,
+                                              nsITimer::TYPE_ONE_SHOT);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mMode = Mode::Delayed;
+  mDeadline = aDeadline;
+
+  return NS_OK;
+}
+
+nsresult
+TimeoutExecutor::Schedule(const TimeStamp& aDeadline)
+{
+  TimeStamp now(TimeStamp::Now());
+
+  // Schedule an immediate runnable if the desired deadline has passed
+  // or is slightly in the future.  This is similar to how nsITimer will
+  // fire timers early based on the interval resolution.
+  if (aDeadline <= (now + mAllowedEarlyFiringTime)) {
+    return ScheduleImmediate(aDeadline, now);
+  }
+
+  return ScheduleDelayed(aDeadline, now);
+}
+
+nsresult
+TimeoutExecutor::MaybeReschedule(const TimeStamp& aDeadline)
+{
+  MOZ_DIAGNOSTIC_ASSERT(!mDeadline.IsNull());
+  MOZ_DIAGNOSTIC_ASSERT(mMode == Mode::Immediate ||
+                        mMode == Mode::Delayed);
+
+  if (aDeadline >= mDeadline) {
+    return NS_OK;
+  }
+
+  if (mMode == Mode::Immediate) {
+    // Don't reduce the deadline here as we want to execute the
+    // timer we originally scheduled even if its a few microseconds
+    // in the future.
+    return NS_OK;
+  }
+
+  Cancel();
+  return Schedule(aDeadline);
+}
+
+void
+TimeoutExecutor::MaybeExecute()
+{
+  MOZ_DIAGNOSTIC_ASSERT(mMode != Mode::Shutdown && mMode != Mode::None);
+  MOZ_DIAGNOSTIC_ASSERT(mOwner);
+  MOZ_DIAGNOSTIC_ASSERT(!mDeadline.IsNull());
+
+  TimeStamp deadline(mDeadline);
+
+  // Sometimes nsITimer or canceled timers will fire too early.  If this
+  // happens then just cap our deadline to our maximum time in the future
+  // and proceed.  If there are no timers ready we will get rescheduled
+  // by TimeoutManager.
+  TimeStamp now(TimeStamp::Now());
+  TimeStamp limit = now + mAllowedEarlyFiringTime;
+  if (deadline > limit) {
+    deadline = limit;
+  }
+
+  Cancel();
+
+  mOwner->RunTimeout(now, deadline);
+}
+
+TimeoutExecutor::TimeoutExecutor(TimeoutManager* aOwner)
+  : mOwner(aOwner)
+  , mMode(Mode::None)
+{
+  MOZ_DIAGNOSTIC_ASSERT(mOwner);
+}
+
+void
+TimeoutExecutor::Shutdown()
+{
+  mOwner = nullptr;
+
+  if (mTimer) {
+    mTimer->Cancel();
+    mTimer = nullptr;
+  }
+
+  mMode = Mode::Shutdown;
+  mDeadline = TimeStamp();
+}
+
+nsresult
+TimeoutExecutor::MaybeSchedule(const TimeStamp& aDeadline)
+{
+  MOZ_DIAGNOSTIC_ASSERT(!aDeadline.IsNull());
+
+  if (mMode == Mode::Shutdown) {
+    return NS_OK;
+  }
+
+  if (mMode == Mode::Immediate || mMode == Mode::Delayed) {
+    return MaybeReschedule(aDeadline);
+  }
+
+  return Schedule(aDeadline);
+}
+
+void
+TimeoutExecutor::Cancel()
+{
+  if (mTimer) {
+    mTimer->Cancel();
+  }
+  mMode = Mode::None;
+  mDeadline = TimeStamp();
+}
+
+NS_IMETHODIMP
+TimeoutExecutor::Run()
+{
+  // If the executor is canceled and then rescheduled its possible to get
+  // spurious executions here.  Ignore these unless our current mode matches.
+  if (mMode == Mode::Immediate) {
+    MaybeExecute();
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TimeoutExecutor::Notify(nsITimer* aTimer)
+{
+  // If the executor is canceled and then rescheduled its possible to get
+  // spurious executions here.  Ignore these unless our current mode matches.
+  if (mMode == Mode::Delayed) {
+    MaybeExecute();
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TimeoutExecutor::GetName(nsACString& aNameOut)
+{
+  aNameOut.AssignLiteral("TimeoutExecutor Runnable");
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TimeoutExecutor::SetName(const char* aName)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/base/TimeoutExecutor.h
@@ -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/. */
+
+#ifndef mozilla_dom_timeoutexecutor_h
+#define mozilla_dom_timeoutexecutor_h
+
+#include "nsIRunnable.h"
+#include "nsITimer.h"
+#include "nsINamed.h"
+
+namespace mozilla {
+namespace dom {
+
+class TimeoutExecutor final : public nsIRunnable
+                            , public nsITimerCallback
+                            , public nsINamed
+{
+  TimeoutManager* mOwner;
+  nsCOMPtr<nsITimer> mTimer;
+  TimeStamp mDeadline;
+
+  // Limits how far we allow timers to fire into the future from their
+  // deadline.  Starts off at zero, but is then adjusted when we start
+  // using nsITimer.  The nsITimer implementation may sometimes fire
+  // early and we should allow that to minimize additional wakeups.
+  TimeDuration mAllowedEarlyFiringTime;
+
+  // The TimeoutExecutor is repeatedly scheduled by the TimeoutManager
+  // to fire for the next soonest Timeout.  Since the executor is re-used
+  // it needs to handle switching between a few states.
+  enum class Mode
+  {
+    // None indicates the executor is idle.  It may be scheduled or shutdown.
+    None,
+    // Immediate means the executor is scheduled to run a Timeout with a
+    // deadline that has already expired.
+    Immediate,
+    // Delayed means the executor is scheduled to run a Timeout with a
+    // deadline in the future.
+    Delayed,
+    // Shutdown means the TimeoutManager has been destroyed.  Once this
+    // state is reached the executor cannot be scheduled again.  If the
+    // executor is already dispatched as a runnable or held by a timer then
+    // we may still get a Run()/Notify() call which will be ignored.
+    Shutdown
+  };
+
+  Mode mMode;
+
+  ~TimeoutExecutor();
+
+  nsresult
+  ScheduleImmediate(const TimeStamp& aDeadline, const TimeStamp& aNow);
+
+  nsresult
+  ScheduleDelayed(const TimeStamp& aDeadline, const TimeStamp& aNow);
+
+  nsresult
+  Schedule(const TimeStamp& aDeadline);
+
+  nsresult
+  MaybeReschedule(const TimeStamp& aDeadline);
+
+  void
+  MaybeExecute();
+
+public:
+  explicit TimeoutExecutor(TimeoutManager* aOwner);
+
+  void
+  Shutdown();
+
+  nsresult
+  MaybeSchedule(const TimeStamp& aDeadline);
+
+  void
+  Cancel();
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIRUNNABLE
+  NS_DECL_NSITIMERCALLBACK
+  NS_DECL_NSINAMED
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_timeoutexecutor_h
--- a/dom/base/TimeoutManager.cpp
+++ b/dom/base/TimeoutManager.cpp
@@ -8,16 +8,17 @@
 #include "nsGlobalWindow.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/ThrottledEventQueue.h"
 #include "mozilla/TimeStamp.h"
 #include "nsITimeoutHandler.h"
 #include "mozilla/dom/TabGroup.h"
 #include "OrderedTimeoutIterator.h"
+#include "TimeoutExecutor.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 static LazyLogModule gLog("Timeout");
 
 // Time between sampling timeout execution time.
 const uint32_t kTelemetryPeriodMS = 1000;
@@ -203,28 +204,26 @@ TimeoutManager::IsInvalidFiringId(uint32
   // should be rare.  It can only happen with deeply nested event
   // loop spinning.  For example, a page that does a lot of timers
   // and a lot of sync XHRs within those timers could be slow here.
   return !mFiringIdStack.Contains(aFiringId);
 }
 
 int32_t
 TimeoutManager::DOMMinTimeoutValue(bool aIsTracking) const {
-  // First apply any back pressure delay that might be in effect.
-  int32_t value = std::max(mBackPressureDelayMS, 0);
   // Don't use the background timeout value when the tab is playing audio.
   // Until bug 1336484 we only used to do this for pages that use Web Audio.
   // The original behavior was implemented in bug 11811073.
   bool isBackground = IsBackground();
   bool throttleTracking = aIsTracking && mThrottleTrackingTimeouts;
   auto minValue = throttleTracking ? (isBackground ? gMinTrackingBackgroundTimeoutValue
                                                    : gMinTrackingTimeoutValue)
                                    : (isBackground ? gMinBackgroundTimeoutValue
                                                    : gMinTimeoutValue);
-  return std::max(minValue, value);
+  return minValue;
 }
 
 #define TRACKING_SEPARATE_TIMEOUT_BUCKETING_STRATEGY 0 // Consider all timeouts coming from tracking scripts as tracking
 // These strategies are useful for testing.
 #define ALL_NORMAL_TIMEOUT_BUCKETING_STRATEGY        1 // Consider all timeouts as normal
 #define ALTERNATE_TIMEOUT_BUCKETING_STRATEGY         2 // Put every other timeout in the list of tracking timeouts
 #define RANDOM_TIMEOUT_BUCKETING_STRATEGY            3 // Put timeouts into either the normal or tracking timeouts list randomly
 static int32_t gTimeoutBucketingStrategy = 0;
@@ -245,86 +244,41 @@ uint32_t TimeoutManager::sNestingLevel =
 
 namespace {
 
 // The maximum number of milliseconds to allow consecutive timer callbacks
 // to run in a single event loop runnable.
 #define DEFAULT_MAX_CONSECUTIVE_CALLBACKS_MILLISECONDS 4
 uint32_t gMaxConsecutiveCallbacksMilliseconds;
 
-// The number of queued runnables within the TabGroup ThrottledEventQueue
-// at which to begin applying back pressure to the window.
-#define DEFAULT_THROTTLED_EVENT_QUEUE_BACK_PRESSURE 5000
-static uint32_t gThrottledEventQueueBackPressure;
-
-// The amount of delay to apply to timers when back pressure is triggered.
-// As the length of the ThrottledEventQueue grows delay is increased.  The
-// delay is scaled such that every kThrottledEventQueueBackPressure runnables
-// in the queue equates to an additional kBackPressureDelayMS.
-#define DEFAULT_BACK_PRESSURE_DELAY_MS 250
-static uint32_t gBackPressureDelayMS;
-
-// This defines a limit for how much the delay must drop before we actually
-// reduce back pressure throttle amount.  This makes the throttle delay
-// a bit "sticky" once we enter back pressure.
-#define DEFAULT_BACK_PRESSURE_DELAY_REDUCTION_THRESHOLD_MS 1000
-static uint32_t gBackPressureDelayReductionThresholdMS;
-
-// The minimum delay we can reduce back pressure to before we just floor
-// the value back to zero.  This allows us to ensure that we can exit
-// back pressure event if there are always a small number of runnables
-// queued up.
-#define DEFAULT_BACK_PRESSURE_DELAY_MINIMUM_MS 100
-static uint32_t gBackPressureDelayMinimumMS;
-
-// Convert a ThrottledEventQueue length to a timer delay in milliseconds.
-// This will return a value between 0 and INT32_MAX.
-int32_t
-CalculateNewBackPressureDelayMS(uint32_t aBacklogDepth)
-{
-  double multiplier = static_cast<double>(aBacklogDepth) /
-                      static_cast<double>(gThrottledEventQueueBackPressure);
-  double value = static_cast<double>(gBackPressureDelayMS) * multiplier;
-  // Avoid overflow
-  if (value > INT32_MAX) {
-    value = INT32_MAX;
-  }
-
-  // Once we get close to an empty queue just floor the delay back to zero.
-  // We want to ensure we don't get stuck in a condition where there is a
-  // small amount of delay remaining due to an active, but reasonable, queue.
-  else if (value < static_cast<double>(gBackPressureDelayMinimumMS)) {
-    value = 0;
-  }
-  return static_cast<int32_t>(value);
-}
-
 } // anonymous namespace
 
 TimeoutManager::TimeoutManager(nsGlobalWindow& aWindow)
   : mWindow(aWindow),
+    mExecutor(new TimeoutExecutor(this)),
     mTimeoutIdCounter(1),
     mNextFiringId(InvalidFiringId + 1),
     mRunningTimeout(nullptr),
     mIdleCallbackTimeoutCounter(1),
-    mBackPressureDelayMS(0),
     mThrottleTrackingTimeouts(false)
 {
   MOZ_DIAGNOSTIC_ASSERT(aWindow.IsInnerWindow());
 
   MOZ_LOG(gLog, LogLevel::Debug,
           ("TimeoutManager %p created, tracking bucketing %s\n",
            this, gAnnotateTrackingChannels ? "enabled" : "disabled"));
 }
 
 TimeoutManager::~TimeoutManager()
 {
   MOZ_DIAGNOSTIC_ASSERT(mWindow.AsInner()->InnerObjectsFreed());
   MOZ_DIAGNOSTIC_ASSERT(!mThrottleTrackingTimeoutsTimer);
 
+  mExecutor->Shutdown();
+
   MOZ_LOG(gLog, LogLevel::Debug,
           ("TimeoutManager %p destroyed\n", this));
 }
 
 /* static */
 void
 TimeoutManager::Initialize()
 {
@@ -345,29 +299,16 @@ TimeoutManager::Initialize()
                               TRACKING_SEPARATE_TIMEOUT_BUCKETING_STRATEGY);
   Preferences::AddIntVarCache(&gTrackingTimeoutThrottlingDelay,
                               "dom.timeout.tracking_throttling_delay",
                               DEFAULT_TRACKING_TIMEOUT_THROTTLING_DELAY);
   Preferences::AddBoolVarCache(&gAnnotateTrackingChannels,
                                "privacy.trackingprotection.annotate_channels",
                                false);
 
-  Preferences::AddUintVarCache(&gThrottledEventQueueBackPressure,
-                               "dom.timeout.throttled_event_queue_back_pressure",
-                               DEFAULT_THROTTLED_EVENT_QUEUE_BACK_PRESSURE);
-  Preferences::AddUintVarCache(&gBackPressureDelayMS,
-                               "dom.timeout.back_pressure_delay_ms",
-                               DEFAULT_BACK_PRESSURE_DELAY_MS);
-  Preferences::AddUintVarCache(&gBackPressureDelayReductionThresholdMS,
-                               "dom.timeout.back_pressure_delay_reduction_threshold_ms",
-                               DEFAULT_BACK_PRESSURE_DELAY_REDUCTION_THRESHOLD_MS);
-  Preferences::AddUintVarCache(&gBackPressureDelayMinimumMS,
-                               "dom.timeout.back_pressure_delay_minimum_ms",
-                               DEFAULT_BACK_PRESSURE_DELAY_MINIMUM_MS);
-
   Preferences::AddUintVarCache(&gMaxConsecutiveCallbacksMilliseconds,
                                "dom.timeout.max_consecutive_callbacks_ms",
                                DEFAULT_MAX_CONSECUTIVE_CALLBACKS_MILLISECONDS);
 }
 
 uint32_t
 TimeoutManager::GetTimeoutId(Timeout::Reason aReason)
 {
@@ -452,49 +393,37 @@ TimeoutManager::SetTimeout(nsITimeoutHan
              timeout.get(), timeout->mIsTracking ? "" : "non-"));
     break;
   }
 
   // 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 ||
-      mBackPressureDelayMS > 0 || mWindow.IsBackgroundInternal() ||
+      mWindow.IsBackgroundInternal() ||
       timeout->mIsTracking) {
     // Don't allow timeouts less than DOMMinTimeoutValue() from
     // now...
     realInterval = std::max(realInterval,
                             uint32_t(DOMMinTimeoutValue(timeout->mIsTracking)));
   }
 
   timeout->mWindow = &mWindow;
 
   TimeDuration delta = TimeDuration::FromMilliseconds(realInterval);
   timeout->SetWhenOrTimeRemaining(TimeStamp::Now(), delta);
 
   // If we're not suspended, then set the timer.
   if (!mWindow.IsSuspended()) {
     MOZ_ASSERT(!timeout->When().IsNull());
 
-    nsresult rv;
-    timeout->mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+    nsresult rv = mExecutor->MaybeSchedule(timeout->When());
     if (NS_FAILED(rv)) {
       return rv;
     }
-
-    RefPtr<Timeout> copy = timeout;
-
-    rv = timeout->InitTimer(mWindow.EventTargetFor(TaskCategory::Timer),
-                            realInterval);
-    if (NS_FAILED(rv)) {
-      return rv;
-    }
-
-    // The timeout is now also held in the timer's closure.
-    Unused << copy.forget();
   }
 
   if (!aIsInterval) {
     timeout->mNestingLevel = nestingLevel;
   }
 
   // No popups from timeouts by default
   timeout->mPopupState = openAbused;
@@ -545,49 +474,67 @@ TimeoutManager::SetTimeout(nsITimeoutHan
   return NS_OK;
 }
 
 void
 TimeoutManager::ClearTimeout(int32_t aTimerId, Timeout::Reason aReason)
 {
   uint32_t timerId = (uint32_t)aTimerId;
 
+  bool firstTimeout = true;
+
   ForEachUnorderedTimeoutAbortable([&](Timeout* aTimeout) {
     MOZ_LOG(gLog, LogLevel::Debug,
             ("Clear%s(TimeoutManager=%p, timeout=%p, aTimerId=%u, ID=%u, tracking=%d)\n", aTimeout->mIsInterval ? "Interval" : "Timeout",
              this, aTimeout, timerId, aTimeout->mTimeoutId,
              int(aTimeout->mIsTracking)));
 
     if (aTimeout->mTimeoutId == timerId && aTimeout->mReason == aReason) {
       if (aTimeout->mRunning) {
         /* We're running from inside the aTimeout. Mark this
            aTimeout for deferred deletion by the code in
            RunTimeout() */
         aTimeout->mIsInterval = false;
       }
       else {
         /* Delete the aTimeout from the pending aTimeout list */
         aTimeout->remove();
-
-        if (aTimeout->mTimer) {
-          aTimeout->mTimer->Cancel();
-          aTimeout->mTimer = nullptr;
-          aTimeout->Release();
-        }
-        aTimeout->Release();
       }
       return true; // abort!
     }
+
+    firstTimeout = false;
+
     return false;
   });
+
+  if (!firstTimeout) {
+    return;
+  }
+
+  // If the first timeout was cancelled we need to stop the executor and
+  // restart at the next soonest deadline.
+  mExecutor->Cancel();
+
+  OrderedTimeoutIterator iter(mNormalTimeouts,
+                              mTrackingTimeouts,
+                              nullptr,
+                              nullptr);
+  Timeout* nextTimeout = iter.Next();
+  if (nextTimeout) {
+    MOZ_ALWAYS_SUCCEEDS(mExecutor->MaybeSchedule(nextTimeout->When()));
+  }
 }
 
 void
-TimeoutManager::RunTimeout(Timeout* aTimeout)
+TimeoutManager::RunTimeout(const TimeStamp& aNow, const TimeStamp& aTargetDeadline)
 {
+  MOZ_DIAGNOSTIC_ASSERT(!aNow.IsNull());
+  MOZ_DIAGNOSTIC_ASSERT(!aTargetDeadline.IsNull());
+
   if (mWindow.IsSuspended()) {
     return;
   }
 
   NS_ASSERTION(!mWindow.IsFrozen(), "Timeout running on a window in the bfcache!");
 
   // Limit the overall time spent in RunTimeout() to reduce jank.
   uint32_t totalTimeLimitMS = std::max(1u, gMaxConsecutiveCallbacksMilliseconds);
@@ -599,17 +546,18 @@ TimeoutManager::RunTimeout(Timeout* aTim
     TimeDuration::FromMilliseconds(totalTimeLimit.ToMilliseconds() / 4);
 
   // Ammortize overhead from from calling TimeStamp::Now() in the initial
   // loop, though, by only checking for an elapsed limit every N timeouts.
   const uint32_t kNumTimersPerInitialElapsedCheck = 100;
 
   // Start measuring elapsed time immediately.  We won't potentially expire
   // the time budget until at least one Timeout has run, though.
-  TimeStamp start = TimeStamp::Now();
+  TimeStamp now(aNow);
+  TimeStamp start = now;
 
   Timeout* last_expired_normal_timeout = nullptr;
   Timeout* last_expired_tracking_timeout = nullptr;
   bool     last_expired_timeout_is_normal = false;
   Timeout* last_normal_insertion_point = nullptr;
   Timeout* last_tracking_insertion_point = nullptr;
 
   uint32_t firingId = CreateFiringId();
@@ -622,91 +570,95 @@ TimeoutManager::RunTimeout(Timeout* aTim
   nsCOMPtr<nsIScriptGlobalObject> windowKungFuDeathGrip(&mWindow);
   // Silence the static analysis error about windowKungFuDeathGrip.  Accessing
   // members of mWindow here is safe, because the lifetime of TimeoutManager is
   // the same as the lifetime of the containing nsGlobalWindow.
   Unused << windowKungFuDeathGrip;
 
   // A native timer has gone off. See which of our timeouts need
   // servicing
-  TimeStamp now = TimeStamp::Now();
   TimeStamp deadline;
 
-  if (aTimeout && aTimeout->When() > now) {
+  if (aTargetDeadline > now) {
     // The OS timer fired early (which can happen due to the timers
     // having lower precision than TimeStamp does).  Set |deadline| to
     // be the time when the OS timer *should* have fired so that any
-    // timers that *should* have fired before aTimeout *will* be fired
-    // now.
+    // timers that *should* have fired *will* be fired now.
 
-    deadline = aTimeout->When();
+    deadline = aTargetDeadline;
   } else {
     deadline = now;
   }
 
+  TimeStamp nextDeadline;
+
   // The timeout list is kept in deadline order. Discover the latest timeout
   // whose deadline has expired. On some platforms, native timeout events fire
-  // "early", but we handled that above by setting deadline to aTimeout->When()
+  // "early", but we handled that above by setting deadline to aTargetDeadline
   // if the timer fired early.  So we can stop walking if we get to timeouts
   // whose When() is greater than deadline, since once that happens we know
   // nothing past that point is expired.
   {
     // Use a nested scope in order to make sure the strong references held by
     // the iterator are freed after the loop.
     OrderedTimeoutIterator expiredIter(mNormalTimeouts,
                                        mTrackingTimeouts,
                                        nullptr,
                                        nullptr);
 
     uint32_t numTimersToRun = 0;
-    bool targetTimerSeen = false;
 
     while (true) {
       Timeout* timeout = expiredIter.Next();
       if (!timeout || timeout->When() > deadline) {
+        if (timeout) {
+          nextDeadline = timeout->When();
+        }
         break;
       }
 
       if (IsInvalidFiringId(timeout->mFiringId)) {
         // Mark any timeouts that are on the list to be fired with the
         // firing depth so that we can reentrantly run timeouts
         timeout->mFiringId = firingId;
         last_expired_timeout_is_normal = expiredIter.PickedNormalIter();
         if (last_expired_timeout_is_normal) {
           last_expired_normal_timeout = timeout;
         } else {
           last_expired_tracking_timeout = timeout;
         }
 
         numTimersToRun += 1;
 
-        // Note that we have seen our target timer.  This means we can now
-        // stop processing timers once we hit our threshold below.
-        if (timeout == aTimeout) {
-          targetTimerSeen = true;
-        }
-
-        // Run only a limited number of timers based on the configured
-        // maximum.  Note, we must always run our target timer however.
-        // Further timers that are ready will get picked up by their own
-        // nsITimer runnables when they execute.
-        if (targetTimerSeen) {
-          if (numTimersToRun % kNumTimersPerInitialElapsedCheck == 0) {
-            TimeDuration elapsed(TimeStamp::Now() - start);
-            if (elapsed >= initalTimeLimit) {
-              break;
-            }
+        // Run only a limited number of timers based on the configured maximum.
+        if (numTimersToRun % kNumTimersPerInitialElapsedCheck == 0) {
+          now = TimeStamp::Now();
+          TimeDuration elapsed(now - start);
+          if (elapsed >= initalTimeLimit) {
+            nextDeadline = timeout->When();
+            break;
           }
         }
       }
 
       expiredIter.UpdateIterator();
     }
   }
 
+  now = TimeStamp::Now();
+
+  // Wherever we stopped in the timer list, schedule the executor to
+  // run for the next unexpired deadline.  Note, this *must* be done
+  // before we start executing any content script handlers.  If one
+  // of them spins the event loop the executor must already be scheduled
+  // in order for timeouts to fire properly.
+  if (!nextDeadline.IsNull()) {
+    MOZ_ALWAYS_SUCCEEDS(mExecutor->MaybeSchedule(nextDeadline));
+  }
+
   // Maybe the timeout that the event was fired for has been deleted
   // and there are no others timeouts with deadlines that make them
   // eligible for execution yet. Go away.
   if (!last_expired_normal_timeout && !last_expired_tracking_timeout) {
     return;
   }
 
   // Insert a dummy timeout into the list of timeouts between the
@@ -723,19 +675,16 @@ TimeoutManager::RunTimeout(Timeout* aTim
 
   RefPtr<Timeout> dummy_tracking_timeout = new Timeout();
   dummy_tracking_timeout->mFiringId = firingId;
   dummy_tracking_timeout->SetDummyWhen(now);
   if (!last_expired_timeout_is_normal) {
     last_expired_tracking_timeout->setNext(dummy_tracking_timeout);
   }
 
-  RefPtr<Timeout> timeoutExtraRef1(dummy_normal_timeout);
-  RefPtr<Timeout> timeoutExtraRef2(dummy_tracking_timeout);
-
   // Now we need to search the normal and tracking timer list at the same
   // time to run the timers in the scheduled order.
 
   last_normal_insertion_point = mNormalTimeouts.InsertionPoint();
   if (last_expired_timeout_is_normal) {
     // If we ever start setting insertion point to a non-dummy timeout, the logic
     // in ResetTimersForThrottleReduction will need to change.
     mNormalTimeouts.SetInsertionPoint(dummy_normal_timeout);
@@ -743,18 +692,16 @@ TimeoutManager::RunTimeout(Timeout* aTim
 
   last_tracking_insertion_point = mTrackingTimeouts.InsertionPoint();
   if (!last_expired_timeout_is_normal) {
     // If we ever start setting mTrackingTimeoutInsertionPoint to a non-dummy timeout,
     // the logic in ResetTimersForThrottleReduction will need to change.
     mTrackingTimeouts.SetInsertionPoint(dummy_tracking_timeout);
   }
 
-  bool targetTimeoutSeen = false;
-
   // We stop iterating each list when we go past the last expired timeout from
   // that list that we have observed above.  That timeout will either be the
   // dummy timeout for the list that the last expired timeout came from, or it
   // will be the next item after the last timeout we looked at (or nullptr if
   // we have exhausted the entire list while looking for the last expired
   // timeout).
   {
     // Use a nested scope in order to make sure the strong references held by
@@ -763,17 +710,17 @@ TimeoutManager::RunTimeout(Timeout* aTim
                                    mTrackingTimeouts,
                                    last_expired_normal_timeout ?
                                      last_expired_normal_timeout->getNext() :
                                      nullptr,
                                    last_expired_tracking_timeout ?
                                      last_expired_tracking_timeout->getNext() :
                                      nullptr);
     while (true) {
-      Timeout* timeout = runIter.Next();
+      RefPtr<Timeout> timeout = runIter.Next();
       MOZ_ASSERT(timeout != dummy_normal_timeout &&
                  timeout != dummy_tracking_timeout,
                  "We should have stopped iterating before getting to the dummy timeout");
       if (!timeout) {
         // We have run out of timeouts!
         break;
       }
       runIter.UpdateIterator();
@@ -796,66 +743,45 @@ TimeoutManager::RunTimeout(Timeout* aTim
       // for this timeout and ensure the script language is enabled.
       nsCOMPtr<nsIScriptContext> scx = mWindow.GetContextInternal();
 
       if (!scx) {
         // No context means this window was closed or never properly
         // initialized for this language.  This timer will never fire
         // so just remove it.
         timeout->remove();
-        timeout->Release();
         continue;
       }
 
-      if (timeout == aTimeout) {
-        targetTimeoutSeen = true;
-      }
-
       // This timeout is good to run
       bool timeout_was_cleared = mWindow.RunTimeoutHandler(timeout, scx);
       MOZ_LOG(gLog, LogLevel::Debug,
-              ("Run%s(TimeoutManager=%p, timeout=%p, aTimeout=%p, tracking=%d) returned %d\n", timeout->mIsInterval ? "Interval" : "Timeout",
-               this, timeout, aTimeout,
-               int(aTimeout->mIsTracking),
+              ("Run%s(TimeoutManager=%p, timeout=%p, tracking=%d) returned %d\n", timeout->mIsInterval ? "Interval" : "Timeout",
+               this, timeout.get(),
+               int(timeout->mIsTracking),
                !!timeout_was_cleared));
 
       if (timeout_was_cleared) {
         // Make sure the iterator isn't holding any Timeout objects alive.
         runIter.Clear();
 
-        // The running timeout's window was cleared, this means that
-        // ClearAllTimeouts() was called from a *nested* call, possibly
-        // through a timeout that fired while a modal (to this window)
-        // dialog was open or through other non-obvious paths.
-        // Note that if the last expired timeout corresponding to each list
-        // is null, then we should expect a refcount of two, since the
-        // dummy timeout for this queue was never injected into it, and the
-        // corresponding timeoutExtraRef variable hasn't been cleared yet.
-        if (last_expired_timeout_is_normal) {
-          MOZ_ASSERT(dummy_normal_timeout->HasRefCnt(1), "dummy_normal_timeout may leak");
-          MOZ_ASSERT(dummy_tracking_timeout->HasRefCnt(2), "dummy_tracking_timeout may leak");
-          Unused << timeoutExtraRef1.forget().take();
-        } else {
-          MOZ_ASSERT(dummy_normal_timeout->HasRefCnt(2), "dummy_normal_timeout may leak");
-          MOZ_ASSERT(dummy_tracking_timeout->HasRefCnt(1), "dummy_tracking_timeout may leak");
-          Unused << timeoutExtraRef2.forget().take();
-        }
-
         mNormalTimeouts.SetInsertionPoint(last_normal_insertion_point);
         mTrackingTimeouts.SetInsertionPoint(last_tracking_insertion_point);
 
         // Since ClearAllTimeouts() was called the lists should be empty.
         MOZ_DIAGNOSTIC_ASSERT(!HasTimeouts());
 
         return;
       }
 
+      now = TimeStamp::Now();
+
       // If we have a regular interval timer, we re-schedule the
       // timeout, accounting for clock drift.
-      bool needsReinsertion = RescheduleTimeout(timeout, now, !aTimeout);
+      bool needsReinsertion = RescheduleTimeout(timeout, now);
 
       // Running a timeout can cause another timeout to be deleted, so
       // we need to reset the pointer to the following timeout.
       runIter.UpdateIterator();
 
       timeout->remove();
 
       if (needsReinsertion) {
@@ -867,235 +793,77 @@ TimeoutManager::RunTimeout(Timeout* aTim
                                                       : Timeouts::SortBy::TimeWhen);
         } else {
           mNormalTimeouts.Insert(timeout,
                                  mWindow.IsFrozen() ? Timeouts::SortBy::TimeRemaining
                                                     : Timeouts::SortBy::TimeWhen);
         }
       }
 
-      // Release the timeout struct since it's possibly out of the list
-      timeout->Release();
-
       // Check to see if we have run out of time to execute timeout handlers.
       // If we've exceeded our time budget then terminate the loop immediately.
-      //
-      // Note, we only do this if we have seen the Timeout object explicitly
-      // passed to RunTimeout().  The target timeout must always be executed.
-      if (targetTimeoutSeen) {
-        TimeDuration elapsed = TimeStamp::Now() - start;
-        if (elapsed >= totalTimeLimit) {
-          break;
+      TimeDuration elapsed = now - start;
+      if (elapsed >= totalTimeLimit) {
+        // We ran out of time.  Make sure to schedule the executor to
+        // run immediately for the next timer, if it exists.
+        RefPtr<Timeout> timeout = runIter.Next();
+        if (timeout) {
+          MOZ_ALWAYS_SUCCEEDS(mExecutor->MaybeSchedule(timeout->When()));
         }
+        break;
       }
     }
   }
 
   // Take the dummy timeout off the head of the list
   if (dummy_normal_timeout->isInList()) {
     dummy_normal_timeout->remove();
   }
-  timeoutExtraRef1 = nullptr;
-  MOZ_ASSERT(dummy_normal_timeout->HasRefCnt(1), "dummy_normal_timeout may leak");
   if (dummy_tracking_timeout->isInList()) {
     dummy_tracking_timeout->remove();
   }
-  timeoutExtraRef2 = nullptr;
-  MOZ_ASSERT(dummy_tracking_timeout->HasRefCnt(1), "dummy_tracking_timeout may leak");
 
   mNormalTimeouts.SetInsertionPoint(last_normal_insertion_point);
   mTrackingTimeouts.SetInsertionPoint(last_tracking_insertion_point);
-
-  MaybeApplyBackPressure();
-}
-
-void
-TimeoutManager::MaybeApplyBackPressure()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  // If we are already in back pressure then we don't need to apply back
-  // pressure again.  We also shouldn't need to apply back pressure while
-  // the window is suspended.
-  if (mBackPressureDelayMS > 0 || mWindow.IsSuspended()) {
-    return;
-  }
-
-  RefPtr<ThrottledEventQueue> queue =
-    do_QueryObject(mWindow.TabGroup()->EventTargetFor(TaskCategory::Timer));
-  if (!queue) {
-    return;
-  }
-
-  // Only begin back pressure if the window has greatly fallen behind the main
-  // thread.  This is a somewhat arbitrary threshold chosen such that it should
-  // rarely fire under normaly circumstances.  Its low enough, though,
-  // that we should have time to slow new runnables from being added before an
-  // OOM occurs.
-  if (queue->Length() < gThrottledEventQueueBackPressure) {
-    return;
-  }
-
-  // First attempt to dispatch a runnable to update our back pressure state.  We
-  // do this first in order to verify we can dispatch successfully before
-  // entering the back pressure state.
-  nsCOMPtr<nsIRunnable> r =
-    NewNonOwningRunnableMethod<StoreRefPtrPassByPtr<nsGlobalWindow>>(this,
-      &TimeoutManager::CancelOrUpdateBackPressure, &mWindow);
-  nsresult rv = queue->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
-  NS_ENSURE_SUCCESS_VOID(rv);
-
-  // Since the callback was scheduled successfully we can now persist the
-  // backpressure value.
-  mBackPressureDelayMS = CalculateNewBackPressureDelayMS(queue->Length());
-
-  MOZ_LOG(gLog, LogLevel::Debug,
-          ("Applying %dms of back pressure to TimeoutManager %p "
-           "because of a queue length of %u\n",
-           mBackPressureDelayMS, this,
-           queue->Length()));
-}
-
-void
-TimeoutManager::CancelOrUpdateBackPressure(nsGlobalWindow* aWindow)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aWindow == &mWindow);
-  MOZ_ASSERT(mBackPressureDelayMS > 0);
-
-  // First, re-calculate the back pressure delay.
-  RefPtr<ThrottledEventQueue> queue =
-    do_QueryObject(mWindow.TabGroup()->EventTargetFor(TaskCategory::Timer));
-  auto queueLength = queue ? queue->Length() : 0;
-  int32_t newBackPressureDelayMS = CalculateNewBackPressureDelayMS(queueLength);
-
-  MOZ_LOG(gLog, LogLevel::Debug,
-          ("Updating back pressure from %d to %dms for TimeoutManager %p "
-           "because of a queue length of %u\n",
-           mBackPressureDelayMS, newBackPressureDelayMS,
-           this, queueLength));
-
-  // If the delay has increased, then simply apply it.  Increasing the delay
-  // does not risk re-ordering timers with similar parameters.  We want to
-  // extra careful not to re-order sequential calls to setTimeout(func, 0),
-  // for example.
-  if (newBackPressureDelayMS > mBackPressureDelayMS) {
-    mBackPressureDelayMS = newBackPressureDelayMS;
-  }
-
-  // If the delay has decreased, though, we only apply the new value if it has
-  // reduced significantly.  This hysteresis avoids thrashing the back pressure
-  // value back and forth rapidly.  This is important because reducing the
-  // backpressure delay requires calling ResetTimerForThrottleReduction() which
-  // can be quite expensive.  We only want to call that method if the back log
-  // is really clearing.
-  else if (newBackPressureDelayMS == 0 ||
-           (static_cast<uint32_t>(mBackPressureDelayMS) >
-           (newBackPressureDelayMS + gBackPressureDelayReductionThresholdMS))) {
-    int32_t oldBackPressureDelayMS = mBackPressureDelayMS;
-    mBackPressureDelayMS = newBackPressureDelayMS;
-
-    // If the back pressure delay has gone down we must reset any existing
-    // timers to use the new value.  Otherwise we run the risk of executing
-    // timer callbacks out-of-order.
-    ResetTimersForThrottleReduction(oldBackPressureDelayMS);
-  }
-
-  // If all of the back pressure delay has been removed then we no longer need
-  // to check back pressure updates.  We can simply return without scheduling
-  // another update runnable.
-  if (!mBackPressureDelayMS) {
-    return;
-  }
-
-  // Otherwise, if there is a back pressure delay still in effect we need
-  // queue a runnable to check if it can be reduced in the future.  Note
-  // that this runnable is dispatched to the ThrottledEventQueue.  This
-  // means we will not check for a new value until the current back log
-  // has been processed.  The next update will only keep back pressure if
-  // more runnables continue to be dispatched to the queue.
-  nsCOMPtr<nsIRunnable> r =
-    NewNonOwningRunnableMethod<StoreRefPtrPassByPtr<nsGlobalWindow>>(this,
-      &TimeoutManager::CancelOrUpdateBackPressure, &mWindow);
-  MOZ_ALWAYS_SUCCEEDS(queue->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
 }
 
 bool
-TimeoutManager::RescheduleTimeout(Timeout* aTimeout, const TimeStamp& now,
-                                  bool aRunningPendingTimeouts)
+TimeoutManager::RescheduleTimeout(Timeout* aTimeout, const TimeStamp& now)
 {
   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();
-      aTimeout->mTimer = nullptr;
-      aTimeout->Release();
-    }
     return false;
   }
 
   // Compute time to next timeout for interval timer.
   // Make sure nextInterval is at least DOMMinTimeoutValue().
   TimeDuration nextInterval =
     TimeDuration::FromMilliseconds(
         std::max(aTimeout->mInterval,
                  uint32_t(DOMMinTimeoutValue(aTimeout->mIsTracking))));
 
-  // If we're running pending timeouts, set the next interval to be
-  // relative to "now", and not to when the timeout that was pending
-  // should have fired.
-  TimeStamp firingTime;
-  if (aRunningPendingTimeouts) {
-    firingTime = now + nextInterval;
-  } else {
-    firingTime = aTimeout->When() + nextInterval;
-  }
+  TimeStamp firingTime = now + nextInterval;
 
   TimeStamp currentNow = TimeStamp::Now();
   TimeDuration delay = firingTime - currentNow;
 
   // And make sure delay is nonnegative; that might happen if the timer
   // thread is firing our timers somewhat early or if they're taking a long
   // time to run the callback.
   if (delay < TimeDuration(0)) {
     delay = TimeDuration(0);
   }
 
   aTimeout->SetWhenOrTimeRemaining(currentNow, delay);
 
-  if (!aTimeout->mTimer) {
-    MOZ_DIAGNOSTIC_ASSERT(mWindow.IsFrozen() || mWindow.IsSuspended());
+  if (mWindow.IsSuspended()) {
     return true;
   }
 
-  // Reschedule the OS timer. Don't bother returning any error codes if
-  // this fails since the callers of this method don't care about them.
-  nsresult rv = aTimeout->InitTimer(mWindow.EventTargetFor(TaskCategory::Timer),
-                                    delay.ToMilliseconds());
-
-  if (NS_FAILED(rv)) {
-    NS_ERROR("Error initializing timer for DOM timeout!");
-
-    // We failed to initialize the new OS timer, this timer does
-    // us no good here so we just cancel it (just in case) and
-    // null out the pointer to the OS timer, this will release the
-    // OS timer. As we continue executing the code below we'll end
-    // up deleting the timeout since it's not an interval timeout
-    // any more (since timeout->mTimer == nullptr).
-    aTimeout->mTimer->Cancel();
-    aTimeout->mTimer = nullptr;
-
-    // Now that the OS timer no longer has a reference to the
-    // timeout we need to drop that reference.
-    aTimeout->Release();
-
-    return false;
-  }
+  nsresult rv = mExecutor->MaybeSchedule(aTimeout->When());
+  NS_ENSURE_SUCCESS(rv, false);
 
   return true;
 }
 
 nsresult
 TimeoutManager::ResetTimersForThrottleReduction()
 {
   return ResetTimersForThrottleReduction(gMinBackgroundTimeoutValue);
@@ -1108,47 +876,53 @@ TimeoutManager::ResetTimersForThrottleRe
 
   if (mWindow.IsFrozen() || mWindow.IsSuspended()) {
     return NS_OK;
   }
 
   Timeouts::SortBy sortBy = mWindow.IsFrozen() ? Timeouts::SortBy::TimeRemaining
                                                : Timeouts::SortBy::TimeWhen;
 
-  nsCOMPtr<nsIEventTarget> queue = mWindow.EventTargetFor(TaskCategory::Timer);
   nsresult rv = mNormalTimeouts.ResetTimersForThrottleReduction(aPreviousThrottleDelayMS,
                                                                 *this,
-                                                                sortBy,
-                                                                queue);
+                                                                sortBy);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = mTrackingTimeouts.ResetTimersForThrottleReduction(aPreviousThrottleDelayMS,
                                                          *this,
-                                                         sortBy,
-                                                         queue);
+                                                         sortBy);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  OrderedTimeoutIterator iter(mNormalTimeouts,
+                              mTrackingTimeouts,
+                              nullptr,
+                              nullptr);
+  Timeout* firstTimeout = iter.Next();
+  if (firstTimeout) {
+    rv = mExecutor->MaybeSchedule(firstTimeout->When());
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
   return NS_OK;
 }
 
 nsresult
 TimeoutManager::Timeouts::ResetTimersForThrottleReduction(int32_t aPreviousThrottleDelayMS,
                                                           const TimeoutManager& aTimeoutManager,
-                                                          SortBy aSortBy,
-                                                          nsIEventTarget* aQueue)
+                                                          SortBy aSortBy)
 {
   TimeStamp now = TimeStamp::Now();
 
   // If insertion point is non-null, we're in the middle of firing timers and
   // the timers we're planning to fire all come before insertion point;
   // insertion point itself is a dummy timeout with an When() that may be
   // semi-bogus.  In that case, we don't need to do anything with insertion
   // point or anything before it, so should start at the timer after insertion
   // point, if there is one.
   // Otherwise, start at the beginning of the list.
-  for (Timeout* timeout = InsertionPoint() ?
+  for (RefPtr<Timeout> timeout = InsertionPoint() ?
          InsertionPoint()->getNext() : GetFirst();
        timeout; ) {
     // It's important that this check be <= so that we guarantee that
     // taking std::max with |now| won't make a quantity equal to
     // timeout->When() below.
     if (timeout->When() <= now) {
       timeout = timeout->getNext();
       continue;
@@ -1165,19 +939,17 @@ TimeoutManager::Timeouts::ResetTimersFor
     // We reduced our throttled delay. Re-init the timer appropriately.
     // Compute the interval the timer should have had if it had not been set in a
     // background window
     TimeDuration interval =
       TimeDuration::FromMilliseconds(
           std::max(timeout->mInterval,
                    uint32_t(aTimeoutManager.
                                 DOMMinTimeoutValue(timeout->mIsTracking))));
-    uint32_t oldIntervalMillisecs = 0;
-    timeout->mTimer->GetDelay(&oldIntervalMillisecs);
-    TimeDuration oldInterval = TimeDuration::FromMilliseconds(oldIntervalMillisecs);
+    const TimeDuration& oldInterval = timeout->ScheduledDelay();
     if (oldInterval > interval) {
       // unclamp
       TimeStamp firingTime =
         std::max(timeout->When() - oldInterval + interval, now);
 
       NS_ASSERTION(firingTime < timeout->When(),
                    "Our firing time should strictly decrease!");
 
@@ -1202,29 +974,20 @@ TimeoutManager::Timeouts::ResetTimersFor
       Timeout* prevTimeout = timeout->getPrevious();
       if (prevTimeout && prevTimeout->When() > timeout->When()) {
         // It is safe to remove and re-insert because When() is now
         // strictly smaller than it used to be, so we know we'll insert
         // |timeout| before nextTimeout.
         NS_ASSERTION(!nextTimeout ||
                      timeout->When() < nextTimeout->When(), "How did that happen?");
         timeout->remove();
-        // Insert() will addref |timeout| and reset mFiringId.  Make sure to
-        // undo that after calling it.
+        // Insert() will reset mFiringId. Make sure to undo that.
         uint32_t firingId = timeout->mFiringId;
         Insert(timeout, aSortBy);
         timeout->mFiringId = firingId;
-        timeout->Release();
-      }
-
-      nsresult rv = timeout->InitTimer(aQueue, delay.ToMilliseconds());
-
-      if (NS_FAILED(rv)) {
-        NS_WARNING("Error resetting non background timer for DOM timeout!");
-        return rv;
       }
 
       timeout = nextTimeout;
     } else {
       timeout = timeout->getNext();
     }
   }
 
@@ -1239,56 +1002,47 @@ TimeoutManager::ClearAllTimeouts()
   MOZ_LOG(gLog, LogLevel::Debug,
           ("ClearAllTimeouts(TimeoutManager=%p)\n", this));
 
   if (mThrottleTrackingTimeoutsTimer) {
     mThrottleTrackingTimeoutsTimer->Cancel();
     mThrottleTrackingTimeoutsTimer = nullptr;
   }
 
+  mExecutor->Cancel();
+
   ForEachUnorderedTimeout([&](Timeout* aTimeout) {
     /* 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 == aTimeout) {
       seenRunningTimeout = true;
     }
 
-    if (aTimeout->mTimer) {
-      aTimeout->mTimer->Cancel();
-      aTimeout->mTimer = nullptr;
-
-      // Drop the count since the timer isn't going to hold on
-      // anymore.
-      aTimeout->Release();
-    }
-
     // Set timeout->mCleared to true to indicate that the timeout was
     // cleared and taken out of the list of timeouts
     aTimeout->mCleared = true;
-
-    // Drop the count since we're removing it from the list.
-    aTimeout->Release();
   });
 
   if (seenRunningTimeout) {
     mNormalTimeouts.SetInsertionPoint(nullptr);
     mTrackingTimeouts.SetInsertionPoint(nullptr);
   }
 
   // Clear out our list
   mNormalTimeouts.Clear();
   mTrackingTimeouts.Clear();
 }
 
 void
 TimeoutManager::Timeouts::Insert(Timeout* aTimeout, SortBy aSortBy)
 {
+
   // Start at mLastTimeout and go backwards.  Don't go further than insertion
   // point, though.  This optimizes for the common case of insertion at the end.
   Timeout* prevSibling;
   for (prevSibling = GetLast();
        prevSibling && prevSibling != InsertionPoint() &&
          // This condition needs to match the one in SetTimeoutOrInterval that
          // determines whether to set When() or TimeRemaining().
          (aSortBy == SortBy::TimeRemaining ?
@@ -1301,20 +1055,16 @@ TimeoutManager::Timeouts::Insert(Timeout
   // Now link in aTimeout after prevSibling.
   if (prevSibling) {
     prevSibling->setNext(aTimeout);
   } else {
     InsertFront(aTimeout);
   }
 
   aTimeout->mFiringId = InvalidFiringId;
-
-  // Increment the timeout's reference count since it's now held on to
-  // by the list
-  aTimeout->AddRef();
 }
 
 Timeout*
 TimeoutManager::BeginRunningTimeout(Timeout* aTimeout)
 {
   Timeout* currentTimeout = mRunningTimeout;
   mRunningTimeout = aTimeout;
 
@@ -1371,31 +1121,17 @@ TimeoutManager::Suspend()
   MOZ_LOG(gLog, LogLevel::Debug,
           ("Suspend(TimeoutManager=%p)\n", this));
 
   if (mThrottleTrackingTimeoutsTimer) {
     mThrottleTrackingTimeoutsTimer->Cancel();
     mThrottleTrackingTimeoutsTimer = nullptr;
   }
 
-  ForEachUnorderedTimeout([](Timeout* aTimeout) {
-    // 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 (aTimeout->mTimer) {
-      aTimeout->mTimer->Cancel();
-      aTimeout->mTimer = nullptr;
-
-      // Drop the reference that the timer's closure had on this timeout, we'll
-      // add it back in Resume().
-      aTimeout->Release();
-    }
-  });
+  mExecutor->Cancel();
 }
 
 void
 TimeoutManager::Resume()
 {
   MOZ_LOG(gLog, LogLevel::Debug,
           ("Resume(TimeoutManager=%p)\n", this));
 
@@ -1404,57 +1140,47 @@ TimeoutManager::Resume()
   // again.
   if (mWindow.AsInner()->IsDocumentLoaded() && !mThrottleTrackingTimeouts) {
     MaybeStartThrottleTrackingTimout();
   }
 
   TimeStamp now = TimeStamp::Now();
   DebugOnly<bool> _seenDummyTimeout = false;
 
+  TimeStamp nextWakeUp;
+
   ForEachUnorderedTimeout([&](Timeout* aTimeout) {
     // 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 (!aTimeout->mWindow) {
       NS_ASSERTION(!_seenDummyTimeout, "More than one dummy timeout?!");
       _seenDummyTimeout = true;
       return;
     }
 
-    MOZ_ASSERT(!aTimeout->mTimer);
-
     // The timeout When() is set to the absolute time when the timer should
     // fire.  Recalculate the delay from now until that deadline.  If the
     // the deadline has already passed or falls within our minimum delay
-    // deadline, then clamp the resulting value to the minimum delay.  The
-    // When() will remain at its absolute time, but we won'aTimeout fire the OS
-    // timer until our calculated delay has passed.
+    // deadline, then clamp the resulting value to the minimum delay.
     int32_t remaining = 0;
     if (aTimeout->When() > now) {
       remaining = static_cast<int32_t>((aTimeout->When() - now).ToMilliseconds());
     }
     uint32_t delay = std::max(remaining, DOMMinTimeoutValue(aTimeout->mIsTracking));
-
-    aTimeout->mTimer = do_CreateInstance("@mozilla.org/timer;1");
-    if (!aTimeout->mTimer) {
-      aTimeout->remove();
-      return;
-    }
+    aTimeout->SetWhenOrTimeRemaining(now, TimeDuration::FromMilliseconds(delay));
 
-    nsresult rv = aTimeout->InitTimer(mWindow.EventTargetFor(TaskCategory::Timer),
-                                      delay);
-    if (NS_FAILED(rv)) {
-      aTimeout->mTimer = nullptr;
-      aTimeout->remove();
-      return;
+    if (nextWakeUp.IsNull() || aTimeout->When() < nextWakeUp) {
+      nextWakeUp = aTimeout->When();
     }
+  });
 
-    // Add a reference for the new timer's closure.
-    aTimeout->AddRef();
-  });
+  if (!nextWakeUp.IsNull()) {
+    MOZ_ALWAYS_SUCCEEDS(mExecutor->MaybeSchedule(nextWakeUp));
+  }
 }
 
 void
 TimeoutManager::Freeze()
 {
   MOZ_LOG(gLog, LogLevel::Debug,
           ("Freeze(TimeoutManager=%p)\n", this));
 
@@ -1473,20 +1199,16 @@ TimeoutManager::Freeze()
     // shifts timers to the right as if time does not pass while
     // the window is frozen.
     TimeDuration delta(0);
     if (aTimeout->When() > now) {
       delta = aTimeout->When() - now;
     }
     aTimeout->SetWhenOrTimeRemaining(now, delta);
     MOZ_DIAGNOSTIC_ASSERT(aTimeout->TimeRemaining() == delta);
-
-    // Since we are suspended there should be no OS timer set for
-    // this timeout entry.
-    MOZ_ASSERT(!aTimeout->mTimer);
   });
 }
 
 void
 TimeoutManager::Thaw()
 {
   MOZ_LOG(gLog, LogLevel::Debug,
           ("Thaw(TimeoutManager=%p)\n", this));
@@ -1502,18 +1224,16 @@ TimeoutManager::Thaw()
       NS_ASSERTION(!_seenDummyTimeout, "More than one dummy timeout?!");
       _seenDummyTimeout = true;
       return;
     }
 
     // Set When() back to the time when the timer is supposed to fire.
     aTimeout->SetWhenOrTimeRemaining(now, aTimeout->TimeRemaining());
     MOZ_DIAGNOSTIC_ASSERT(!aTimeout->When().IsNull());
-
-    MOZ_ASSERT(!aTimeout->mTimer);
   });
 }
 
 bool
 TimeoutManager::IsTimeoutTracking(uint32_t aTimeoutId)
 {
   return mTrackingTimeouts.ForEachAbortable([&](Timeout* aTimeout) {
       return aTimeout->mTimeoutId == aTimeoutId;
@@ -1629,8 +1349,14 @@ TimeoutManager::BeginSyncOperation()
 void
 TimeoutManager::EndSyncOperation()
 {
   // If we're running a timeout, restart the measurement from here.
   if (!mWindow.IsChromeWindow() && mRunningTimeout) {
     TimeoutTelemetry::Get().StartRecording(TimeStamp::Now());
   }
 }
+
+nsIEventTarget*
+TimeoutManager::EventTarget()
+{
+  return mWindow.EventTargetFor(TaskCategory::Timer);
+}
--- a/dom/base/TimeoutManager.h
+++ b/dom/base/TimeoutManager.h
@@ -13,16 +13,17 @@
 class nsIEventTarget;
 class nsITimeoutHandler;
 class nsGlobalWindow;
 
 namespace mozilla {
 namespace dom {
 
 class OrderedTimeoutIterator;
+class TimeoutExecutor;
 
 // This class manages the timeouts in a Window's setTimeout/setInterval pool.
 class TimeoutManager final
 {
 public:
   explicit TimeoutManager(nsGlobalWindow& aWindow);
   ~TimeoutManager();
   TimeoutManager(const TimeoutManager& rhs) = delete;
@@ -42,33 +43,23 @@ public:
   nsresult SetTimeout(nsITimeoutHandler* aHandler,
                       int32_t interval, bool aIsInterval,
                       mozilla::dom::Timeout::Reason aReason,
                       int32_t* aReturn);
   void ClearTimeout(int32_t aTimerId,
                     mozilla::dom::Timeout::Reason aReason);
 
   // The timeout implementation functions.
-  void RunTimeout(mozilla::dom::Timeout* aTimeout);
+  void RunTimeout(const TimeStamp& aNow, const TimeStamp& aTargetDeadline);
   // Return true if |aTimeout| needs to be reinserted into the timeout list.
-  bool RescheduleTimeout(mozilla::dom::Timeout* aTimeout, const TimeStamp& now,
-                         bool aRunningPendingTimeouts);
+  bool RescheduleTimeout(mozilla::dom::Timeout* aTimeout, const TimeStamp& now);
 
   void ClearAllTimeouts();
   uint32_t GetTimeoutId(mozilla::dom::Timeout::Reason aReason);
 
-  // Apply back pressure to the window if the TabGroup ThrottledEventQueue
-  // exists and has too many runnables waiting to run.  For example, increase
-  // the minimum timer delay, etc.
-  void MaybeApplyBackPressure();
-
-  // Check the current ThrottledEventQueue depth and update the back pressure
-  // state.  If the queue has drained back pressure may be canceled.
-  void CancelOrUpdateBackPressure(nsGlobalWindow* aWindow);
-
   // When timers are being throttled and we reduce the thottle delay we must
   // reschedule.  The amount of the old throttle delay must be provided in
   // order to bound how many timers must be examined.
   nsresult ResetTimersForThrottleReduction();
 
   int32_t DOMMinTimeoutValue(bool aIsTracking) const;
 
   // aTimeout is the timeout that we're about to start running.  This function
@@ -114,16 +105,19 @@ public:
     if (!mNormalTimeouts.ForEachAbortable(c)) {
       mTrackingTimeouts.ForEachAbortable(c);
     }
   }
 
   void BeginSyncOperation();
   void EndSyncOperation();
 
+  nsIEventTarget*
+  EventTarget();
+
   static const uint32_t InvalidFiringId;
 
 private:
   nsresult ResetTimersForThrottleReduction(int32_t aPreviousThrottleDelayMS);
   void MaybeStartThrottleTrackingTimout();
 
   bool IsBackground() const;
 
@@ -148,18 +142,17 @@ private:
     enum class SortBy
     {
       TimeRemaining,
       TimeWhen
     };
     void Insert(mozilla::dom::Timeout* aTimeout, SortBy aSortBy);
     nsresult ResetTimersForThrottleReduction(int32_t aPreviousThrottleDelayMS,
                                              const TimeoutManager& aTimeoutManager,
-                                             SortBy aSortBy,
-                                             nsIEventTarget* aQueue);
+                                             SortBy aSortBy);
 
     const Timeout* GetFirst() const { return mTimeoutList.getFirst(); }
     Timeout* GetFirst() { return mTimeoutList.getFirst(); }
     const Timeout* GetLast() const { return mTimeoutList.getLast(); }
     Timeout* GetLast() { return mTimeoutList.getLast(); }
     bool IsEmpty() const { return mTimeoutList.isEmpty(); }
     void InsertFront(Timeout* aTimeout) { mTimeoutList.insertFront(aTimeout); }
     void Clear() { mTimeoutList.clear(); }
@@ -195,17 +188,17 @@ private:
         }
       }
       return false;
     }
 
     friend class OrderedTimeoutIterator;
 
   private:
-    typedef mozilla::LinkedList<mozilla::dom::Timeout> TimeoutList;
+    typedef mozilla::LinkedList<RefPtr<Timeout>> TimeoutList;
 
     // mTimeoutList 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.
     TimeoutList               mTimeoutList;
     // 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
@@ -213,30 +206,32 @@ private:
     mozilla::dom::Timeout*    mTimeoutInsertionPoint;
   };
 
   friend class OrderedTimeoutIterator;
 
   // Each nsGlobalWindow object has a TimeoutManager member.  This reference
   // points to that holder object.
   nsGlobalWindow&             mWindow;
+  // The executor is specific to the nsGlobalWindow/TimeoutManager, but it
+  // can live past the destruction of the window if its scheduled.  Therefore
+  // it must be a separate ref-counted object.
+  RefPtr<TimeoutExecutor>     mExecutor;
   // The list of timeouts coming from non-tracking scripts.
   Timeouts                    mNormalTimeouts;
   // The list of timeouts coming from scripts on the tracking protection list.
   Timeouts                    mTrackingTimeouts;
   uint32_t                    mTimeoutIdCounter;
   uint32_t                    mNextFiringId;
   AutoTArray<uint32_t, 2>     mFiringIdStack;
   mozilla::dom::Timeout*      mRunningTimeout;
 
    // The current idle request callback timeout handle
   uint32_t                    mIdleCallbackTimeoutCounter;
 
-  int32_t                     mBackPressureDelayMS;
-
   nsCOMPtr<nsITimer>          mThrottleTrackingTimeoutsTimer;
   bool                        mThrottleTrackingTimeouts;
 
   static uint32_t             sNestingLevel;
 };
 
 }
 }
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -341,16 +341,17 @@ UNIFIED_SOURCES += [
     'StructuredCloneHolder.cpp',
     'StyleSheetList.cpp',
     'SubtleCrypto.cpp',
     'TabGroup.cpp',
     'Text.cpp',
     'TextInputProcessor.cpp',
     'ThirdPartyUtil.cpp',
     'Timeout.cpp',
+    'TimeoutExecutor.cpp',
     'TimeoutHandler.cpp',
     'TimeoutManager.cpp',
     'TreeWalker.cpp',
     'WebKitCSSMatrix.cpp',
     'WebSocket.cpp',
     'WindowNamedPropertiesHandler.cpp',
     'WindowOrientationObserver.cpp',
 ]
--- a/layout/reftests/bugs/703186-1-ref.html
+++ b/layout/reftests/bugs/703186-1-ref.html
@@ -9,15 +9,15 @@
       }
 
       function finish()
       {
         document.documentElement.removeAttribute("class");
       }
     </script>
   </head>
-  <body onload="setTimeout(init, 0);">
+  <body onload="requestAnimationFrame(_ => setTimeout(init, 0));">
     <img src="100x80-white-rect-top-right.png" usemap="#map">
     <map name="map">
       <area id="link" shape="rect" coords="10,10,30,30" href="about:blank">
     </map>
   </body>
 </html>
--- a/layout/reftests/bugs/703186-1.html
+++ b/layout/reftests/bugs/703186-1.html
@@ -11,15 +11,15 @@
       }
 
       function finish()
       {
         document.documentElement.removeAttribute("class");
       }
     </script>
   </head>
-  <body onload="setTimeout(init, 0);">
+  <body onload="requestAnimationFrame(_ => setTimeout(init, 0));">
     <img src="100x80-white-rect-top-right.png" usemap="#map">
     <map name="map">
       <area id="link" shape="rect" coords="10,10,30,30" href="about:blank">
     </map>
   </body>
 </html>
--- a/layout/reftests/bugs/703186-2-ref.html
+++ b/layout/reftests/bugs/703186-2-ref.html
@@ -15,15 +15,15 @@
       }
 
       function finish()
       {
         document.documentElement.removeAttribute("class");
       }
     </script>
   </head>
-  <body onload="setTimeout(init, 0);">
+  <body onload="requestAnimationFrame(_ => setTimeout(init, 0));">
     <img src="100x80-white-rect-top-right.png" usemap="#map">
     <map name="map">
       <area id="link" shape="rect" coords="10,10,30,30" href="about:blank">
     </map>
   </body>
 </html>
--- a/layout/reftests/bugs/703186-2.html
+++ b/layout/reftests/bugs/703186-2.html
@@ -17,15 +17,15 @@
       }
 
       function finish()
       {
         document.documentElement.removeAttribute("class");
       }
     </script>
   </head>
-  <body onload="setTimeout(init, 0);">
+  <body onload="requestAnimationFrame(_ => setTimeout(init, 0));">
     <img src="100x80-white-rect-top-right.png" usemap="#map">
     <map name="map">
       <area id="link" shape="rect" coords="10,10,30,30" href="about:blank">
     </map>
   </body>
 </html>
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -1368,17 +1368,21 @@ GeckoDriver.prototype.getWindowRect = fu
 GeckoDriver.prototype.setWindowRect = function* (cmd, resp) {
   assert.firefox()
   const win = assert.window(this.getCurrentWindow());
   assert.noUserPrompt(this.dialog);
 
   let {x, y, width, height} = cmd.parameters;
 
   if (win.windowState == win.STATE_FULLSCREEN) {
-    win.document.exitFullscreen();
+    yield new Promise(resolve => {
+      win.addEventListener("sizemodechange", resolve, {once: true});
+
+      win.fullScreen = false;
+    });
   }
 
   if (height != null && width != null) {
     assert.positiveInteger(height);
     assert.positiveInteger(width);
 
     if (win.outerWidth != width && win.outerHeight != height) {
       yield new Promise(resolve => {
@@ -2906,23 +2910,19 @@ GeckoDriver.prototype.maximizeWindow = f
  *     A modal dialog is open, blocking this operation.
  */
 GeckoDriver.prototype.fullscreen = function* (cmd, resp) {
   assert.firefox();
   const win = assert.window(this.getCurrentWindow());
   assert.noUserPrompt(this.dialog);
 
   yield new Promise(resolve => {
-    win.addEventListener("resize", resolve, {once: true});
-
-    if (win.windowState == win.STATE_FULLSCREEN) {
-      win.document.exitFullscreen();
-    } else {
-      win.document.documentElement.requestFullscreen();
-    }
+    win.addEventListener("sizemodechange", resolve, {once: true});
+
+    win.fullScreen = !win.fullScreen;
   });
 
   resp.body = {
     x: win.screenX,
     y: win.screenY,
     width: win.outerWidth,
     height: win.outerHeight,
   };
--- a/testing/marionette/harness/marionette_harness/tests/unit/unit-tests.ini
+++ b/testing/marionette/harness/marionette_harness/tests/unit/unit-tests.ini
@@ -68,19 +68,19 @@ skip-if = appname == 'fennec'
 [test_visibility.py]
 [test_window_handles_chrome.py]
 skip-if = appname == 'fennec'
 [test_window_handles_content.py]
 [test_window_close_chrome.py]
 skip-if = appname == 'fennec'
 [test_window_close_content.py]
 [test_window_rect.py]
-skip-if = appname == 'fennec' || headless # Bug 1367227
+skip-if = appname == 'fennec'
 [test_window_maximize.py]
-skip-if = appname == 'fennec' || headless # Bug 1367227
+skip-if = appname == 'fennec'
 [test_window_status_content.py]
 [test_window_status_chrome.py]
 
 [test_screenshot.py]
 skip-if = headless # Relies on native styling which headless doesn't support.
 [test_cookies.py]
 [test_title.py]
 [test_title_chrome.py]
--- a/tools/profiler/core/ThreadInfo.h
+++ b/tools/profiler/core/ThreadInfo.h
@@ -49,20 +49,20 @@ public:
     // If these measurements are added, the code must be careful to avoid data
     // races. (The current code doesn't have any race issues because the
     // contents of the PseudoStack object aren't accessed; |this| is used only
     // as an address for lookup by aMallocSizeof).
 
     return n;
   }
 
-  void AddPendingMarker(const char* aMarkerStr, ProfilerMarkerPayload* aPayload,
-                        double aTime)
+  void AddPendingMarker(const char* aMarkerName,
+                        ProfilerMarkerPayload* aPayload, double aTime)
   {
-    ProfilerMarker* marker = new ProfilerMarker(aMarkerStr, aPayload, aTime);
+    ProfilerMarker* marker = new ProfilerMarker(aMarkerName, aPayload, aTime);
     mPendingMarkers.insert(marker);
   }
 
   // Called within signal. Function must be reentrant.
   ProfilerMarkerLinkedList* GetPendingMarkers()
   {
     // The profiled thread is interrupted, so we can access the list safely.
     // Unless the profiled thread was in the middle of changing the list when
--- a/tools/profiler/core/platform.cpp
+++ b/tools/profiler/core/platform.cpp
@@ -107,39 +107,45 @@ typedef mozilla::BaseAutoLock<PSMutex> P
 
 // Only functions that take a PSLockRef arg can access CorePS's and ActivePS's
 // fields.
 typedef const PSAutoLock& PSLockRef;
 
 #define PS_GET(type_, name_) \
   static type_ name_(PSLockRef) { return sInstance->m##name_; } \
 
+#define PS_GET_LOCKLESS(type_, name_) \
+  static type_ name_() { return sInstance->m##name_; } \
+
 #define PS_GET_AND_SET(type_, name_) \
   PS_GET(type_, name_) \
   static void Set##name_(PSLockRef, type_ a##name_) \
     { sInstance->m##name_ = a##name_; }
 
 // All functions in this file can run on multiple threads unless they have an
 // NS_IsMainThread() assertion.
 
 // This class contains the profiler's core global state, i.e. that which is
 // valid even when the profiler is not active. Most profile operations can't do
 // anything useful when this class is not instantiated, so we release-assert
 // its non-nullness in all such operations.
 //
-// Accesses to CorePS are guarded by gPSMutex. Every getter and setter takes a
+// Accesses to CorePS are guarded by gPSMutex. Getters and setters take a
 // PSAutoLock reference as an argument as proof that the gPSMutex is currently
 // locked. This makes it clear when gPSMutex is locked and helps avoid
 // accidental unlocked accesses to global state. There are ways to circumvent
 // this mechanism, but please don't do so without *very* good reason and a
 // detailed explanation.
 //
-// The exception to this rule is each thread's RacyThreadInfo object, which is
-// accessible without locking via TLSInfo::RacyThreadInfo().
+// The exceptions to this rule:
 //
+// - mProcessStartTime, because it's immutable;
+//
+// - each thread's RacyThreadInfo object is accessible without locking via
+//   TLSInfo::RacyThreadInfo().
 class CorePS
 {
 private:
   CorePS()
     : mProcessStartTime(mozilla::TimeStamp::ProcessCreation())
 #ifdef USE_LUL_STACKWALK
     , mLul(nullptr)
 #endif
@@ -202,17 +208,18 @@ public:
 
 #if defined(USE_LUL_STACKWALK)
     if (sInstance->mLul) {
       aLulSize += sInstance->mLul->SizeOfIncludingThis(aMallocSizeOf);
     }
 #endif
   }
 
-  PS_GET(TimeStamp, ProcessStartTime)
+  // No PSLockRef is needed for this field because it's immutable.
+  PS_GET_LOCKLESS(TimeStamp, ProcessStartTime)
 
   PS_GET(ThreadVector&, LiveThreads)
   PS_GET(ThreadVector&, DeadThreads)
 
 #ifdef USE_LUL_STACKWALK
   PS_GET_AND_SET(lul::LUL*, Lul)
 #endif
 
@@ -476,16 +483,17 @@ private:
   bool mWasPaused;
 #endif
 };
 
 ActivePS* ActivePS::sInstance = nullptr;
 uint32_t ActivePS::sNextGeneration = 0;
 
 #undef PS_GET
+#undef PS_GET_LOCKLESS
 #undef PS_GET_AND_SET
 
 // The mutex that guards accesses to CorePS and ActivePS.
 static PSMutex gPSMutex;
 
 // Each live thread has a ThreadInfo, and we store a reference to it in TLS.
 // This class encapsulates that TLS.
 class TLSInfo
@@ -1273,17 +1281,17 @@ DoSampleStackTrace(PSLockRef aLock, Prof
 // This function is called for each sampling period with the current program
 // counter. It is called within a signal and so must be re-entrant.
 static void
 Tick(PSLockRef aLock, ProfileBuffer* aBuffer, const TickSample& aSample)
 {
   aBuffer->addTagThreadId(aSample.mThreadId, aSample.mLastSample);
 
   mozilla::TimeDuration delta =
-    aSample.mTimeStamp - CorePS::ProcessStartTime(aLock);
+    aSample.mTimeStamp - CorePS::ProcessStartTime();
   aBuffer->addTag(ProfileBufferEntry::Time(delta.ToMilliseconds()));
 
 #if defined(HAVE_NATIVE_UNWIND)
   if (ActivePS::FeatureStackWalk(aLock)) {
     DoNativeBacktrace(aLock, aBuffer, aSample);
   } else
 #endif
   {
@@ -1382,17 +1390,17 @@ static void
 StreamTaskTracer(PSLockRef aLock, SpliceableJSONWriter& aWriter)
 {
 #ifdef MOZ_TASK_TRACER
   MOZ_RELEASE_ASSERT(CorePS::Exists() && ActivePS::Exists(aLock));
 
   aWriter.StartArrayProperty("data");
   {
     UniquePtr<nsTArray<nsCString>> data =
-      mozilla::tasktracer::GetLoggedData(CorePS::ProcessStartTime(aLock));
+      mozilla::tasktracer::GetLoggedData(CorePS::ProcessStartTime());
     for (uint32_t i = 0; i < data->Length(); ++i) {
       aWriter.StringElement((data->ElementAt(i)).get());
     }
   }
   aWriter.EndArray();
 
   aWriter.StartArrayProperty("threads");
   {
@@ -1421,17 +1429,17 @@ StreamMetaJSCustomObject(PSLockRef aLock
   MOZ_RELEASE_ASSERT(CorePS::Exists() && ActivePS::Exists(aLock));
 
   aWriter.IntProperty("version", 6);
 
   // The "startTime" field holds the number of milliseconds since midnight
   // January 1, 1970 GMT. This grotty code computes (Now - (Now -
   // ProcessStartTime)) to convert CorePS::ProcessStartTime() into that form.
   mozilla::TimeDuration delta =
-    mozilla::TimeStamp::Now() - CorePS::ProcessStartTime(aLock);
+    mozilla::TimeStamp::Now() - CorePS::ProcessStartTime();
   aWriter.DoubleProperty(
     "startTime", static_cast<double>(PR_Now()/1000.0 - delta.ToMilliseconds()));
 
   if (!NS_IsMainThread()) {
     // Leave the rest of the properties out if we're not on the main thread.
     // At the moment, the only case in which this function is called on a
     // background thread is if we're in a content process and are going to
     // send this profile to the parent process. In that case, the parent
@@ -1591,25 +1599,25 @@ locked_profiler_stream_json_for_this_pro
   {
     const CorePS::ThreadVector& liveThreads = CorePS::LiveThreads(aLock);
     for (size_t i = 0; i < liveThreads.size(); i++) {
       ThreadInfo* info = liveThreads.at(i);
       if (!info->IsBeingProfiled()) {
         continue;
       }
       info->StreamJSON(ActivePS::Buffer(aLock), aWriter,
-                       CorePS::ProcessStartTime(aLock), aSinceTime);
+                       CorePS::ProcessStartTime(), aSinceTime);
     }
 
     const CorePS::ThreadVector& deadThreads = CorePS::DeadThreads(aLock);
     for (size_t i = 0; i < deadThreads.size(); i++) {
       ThreadInfo* info = deadThreads.at(i);
       MOZ_ASSERT(info->IsBeingProfiled());
       info->StreamJSON(ActivePS::Buffer(aLock), aWriter,
-                       CorePS::ProcessStartTime(aLock), aSinceTime);
+                       CorePS::ProcessStartTime(), aSinceTime);
     }
 
 #if defined(GP_OS_android)
     if (ActivePS::FeatureJava(aLock)) {
       java::GeckoJavaSampler::Pause();
 
       aWriter.Start();
       {
@@ -1813,17 +1821,17 @@ SamplerThread::Run()
           }
 
           // If the thread is asleep and has been sampled before in the same
           // sleep episode, find and copy the previous sample, as that's
           // cheaper than taking a new sample.
           if (info->RacyInfo()->CanDuplicateLastSampleDueToSleep()) {
             bool dup_ok =
               ActivePS::Buffer(lock)->DuplicateLastSample(
-                info->ThreadId(), CorePS::ProcessStartTime(lock),
+                info->ThreadId(), CorePS::ProcessStartTime(),
                 info->LastSample());
             if (dup_ok) {
               continue;
             }
           }
 
           // We only track responsiveness for the main thread.
           if (info->IsMainThread()) {
@@ -2726,20 +2734,18 @@ profiler_js_interrupt_callback()
   info->PollJSSampling();
 }
 
 double
 profiler_time()
 {
   MOZ_RELEASE_ASSERT(CorePS::Exists());
 
-  PSAutoLock lock(gPSMutex);
-
   mozilla::TimeDuration delta =
-    mozilla::TimeStamp::Now() - CorePS::ProcessStartTime(lock);
+    mozilla::TimeStamp::Now() - CorePS::ProcessStartTime();
   return delta.ToMilliseconds();
 }
 
 UniqueProfilerBacktrace
 profiler_get_backtrace()
 {
   MOZ_RELEASE_ASSERT(CorePS::Exists());
 
@@ -2840,17 +2846,17 @@ profiler_get_backtrace_noalloc(char *out
       output += labelLength;
     }
     *output++ = '\0';
     *output = '\0';
   }
 }
 
 static void
-locked_profiler_add_marker(PSLockRef aLock, const char* aMarker,
+locked_profiler_add_marker(PSLockRef aLock, const char* aMarkerName,
                            ProfilerMarkerPayload* aPayload)
 {
   MOZ_RELEASE_ASSERT(CorePS::Exists());
   MOZ_RELEASE_ASSERT(ActivePS::Exists(aLock) &&
                      !ActivePS::FeaturePrivacy(aLock));
 
   // aPayload must be freed if we return early.
   mozilla::UniquePtr<ProfilerMarkerPayload> payload(aPayload);
@@ -2858,68 +2864,69 @@ locked_profiler_add_marker(PSLockRef aLo
   RacyThreadInfo* racyInfo = TLSInfo::RacyInfo();
   if (!racyInfo) {
     return;
   }
 
   mozilla::TimeStamp origin = (payload && !payload->GetStartTime().IsNull())
                             ? payload->GetStartTime()
                             : mozilla::TimeStamp::Now();
-  mozilla::TimeDuration delta = origin - CorePS::ProcessStartTime(aLock);
-  racyInfo->AddPendingMarker(aMarker, payload.release(),
+  mozilla::TimeDuration delta = origin - CorePS::ProcessStartTime();
+  racyInfo->AddPendingMarker(aMarkerName, payload.release(),
                              delta.ToMilliseconds());
 }
 
 void
-profiler_add_marker(const char* aMarker, ProfilerMarkerPayload* aPayload)
+profiler_add_marker(const char* aMarkerName, ProfilerMarkerPayload* aPayload)
 {
   MOZ_RELEASE_ASSERT(CorePS::Exists());
 
   PSAutoLock lock(gPSMutex);
 
   // aPayload must be freed if we return early.
   mozilla::UniquePtr<ProfilerMarkerPayload> payload(aPayload);
 
   if (!ActivePS::Exists(lock) || ActivePS::FeaturePrivacy(lock)) {
     return;
   }
 
-  locked_profiler_add_marker(lock, aMarker, payload.release());
+  locked_profiler_add_marker(lock, aMarkerName, payload.release());
 }
 
 void
-profiler_tracing(const char* aCategory, const char* aInfo, TracingKind aKind)
+profiler_tracing(const char* aCategory, const char* aMarkerName,
+                 TracingKind aKind)
 {
   MOZ_RELEASE_ASSERT(CorePS::Exists());
 
   PSAutoLock lock(gPSMutex);
 
   if (!ActivePS::Exists(lock) || ActivePS::FeaturePrivacy(lock)) {
     return;
   }
 
-  auto marker = new ProfilerMarkerTracing(aCategory, aKind);
-  locked_profiler_add_marker(lock, aInfo, marker);
+  auto payload = new ProfilerMarkerTracing(aCategory, aKind);
+  locked_profiler_add_marker(lock, aMarkerName, payload);
 }
 
 void
-profiler_tracing(const char* aCategory, const char* aInfo,
+profiler_tracing(const char* aCategory, const char* aMarkerName,
                  UniqueProfilerBacktrace aCause, TracingKind aKind)
 {
   MOZ_RELEASE_ASSERT(CorePS::Exists());
 
   PSAutoLock lock(gPSMutex);
 
   if (!ActivePS::Exists(lock) || ActivePS::FeaturePrivacy(lock)) {
     return;
   }
 
-  auto marker =
+  auto payload =
     new ProfilerMarkerTracing(aCategory, aKind, mozilla::Move(aCause));
-  locked_profiler_add_marker(lock, aInfo, marker);
+  locked_profiler_add_marker(lock, aMarkerName, payload);
 }
 
 void
 profiler_log(const char* aStr)
 {
   profiler_tracing("log", aStr);
 }
 
@@ -2958,17 +2965,17 @@ profiler_clear_js_context()
 
   // On JS shut down, flush the current buffer as stringifying JIT samples
   // requires a live JSContext.
 
   if (ActivePS::Exists(lock)) {
     // Flush this thread's ThreadInfo, if it is being profiled.
     if (info->IsBeingProfiled()) {
       info->FlushSamplesAndMarkers(ActivePS::Buffer(lock),
-                                   CorePS::ProcessStartTime(lock));
+                                   CorePS::ProcessStartTime());
     }
   }
 
   // We don't call info->StopJSSampling() here; there's no point doing that for
   // a JS thread that is in the process of disappearing.
 
   info->mContext = nullptr;
 }
--- a/tools/profiler/public/GeckoProfiler.h
+++ b/tools/profiler/public/GeckoProfiler.h
@@ -99,18 +99,18 @@ using UniqueProfilerBacktrace =
 // PROFILER_LABEL frames take up considerably less space in the profile buffer
 // than PROFILER_LABEL_DYNAMIC frames.
 #define PROFILER_LABEL_DYNAMIC(name_space, info, category, str) do {} while (0)
 
 // Insert a marker in the profile timeline. This is useful to delimit something
 // important happening such as the first paint. Unlike profiler_label that are
 // only recorded if a sample is collected while it is active, marker will always
 // be collected.
-#define PROFILER_MARKER(info) do {} while (0)
-#define PROFILER_MARKER_PAYLOAD(info, payload) \
+#define PROFILER_MARKER(marker_name) do {} while (0)
+#define PROFILER_MARKER_PAYLOAD(marker_name, payload) \
   do { \
     mozilla::UniquePtr<ProfilerMarkerPayload> payloadDeletor(payload); \
   } while (0)
 
 #else   // defined(MOZ_GECKO_PROFILER)
 
 #if defined(__GNUC__) || defined(_MSC_VER)
 # define PROFILER_FUNCTION_NAME __FUNCTION__
@@ -138,19 +138,19 @@ using UniqueProfilerBacktrace =
                                              __LINE__)
 
 #define PROFILER_LABEL_DYNAMIC(name_space, info, category, str) \
   PROFILER_PLATFORM_TRACING(name_space "::" info) \
   mozilla::ProfilerStackFrameDynamicRAII \
   PROFILER_APPEND_LINE_NUMBER(profiler_raii)(name_space "::" info, category, \
                                              __LINE__, str)
 
-#define PROFILER_MARKER(info) profiler_add_marker(info)
-#define PROFILER_MARKER_PAYLOAD(info, payload) \
-  profiler_add_marker(info, payload)
+#define PROFILER_MARKER(marker_name) profiler_add_marker(marker_name)
+#define PROFILER_MARKER_PAYLOAD(marker_name, payload) \
+  profiler_add_marker(marker_name, payload)
 
 #endif  // defined(MOZ_GECKO_PROFILER)
 
 // Higher-order macro containing all the feature info in one place. Define
 // |macro| appropriately to extract the relevant parts. Note that the number
 // values are used internally only and so can be changed without consequence.
 #define PROFILER_FOR_EACH_FEATURE(macro) \
   /* Dump the display list with the textures. */ \
@@ -206,19 +206,21 @@ struct ProfilerFeature
 
   #undef DECLARE
 };
 
 // These functions are defined whether the profiler is enabled or not.
 
 // Adds a tracing marker to the PseudoStack. A no-op if the profiler is
 // inactive or in privacy mode.
-PROFILER_FUNC_VOID(profiler_tracing(const char* aCategory, const char* aInfo,
+PROFILER_FUNC_VOID(profiler_tracing(const char* aCategory,
+                                    const char* aMarkerName,
                                     TracingKind aKind = TRACING_EVENT))
-PROFILER_FUNC_VOID(profiler_tracing(const char* aCategory, const char* aInfo,
+PROFILER_FUNC_VOID(profiler_tracing(const char* aCategory,
+                                    const char* aMarkerName,
                                     UniqueProfilerBacktrace aCause,
                                     TracingKind aKind = TRACING_EVENT))
 
 // Initialize the profiler. If MOZ_PROFILER_STARTUP is set the profiler will be
 // started. This call must happen before any other profiler calls (except
 // profiler_start(), which will call profiler_init() if it hasn't already run).
 PROFILER_FUNC_VOID(profiler_init(void* stackTop))
 
@@ -254,18 +256,18 @@ PROFILER_FUNC_VOID(profiler_resume())
 
 // Is the profiler active and paused? Returns false if the profiler is inactive.
 PROFILER_FUNC(bool profiler_is_paused(), false)
 
 // Immediately capture the current thread's call stack and return it. A no-op
 // if the profiler is inactive or in privacy mode.
 PROFILER_FUNC(UniqueProfilerBacktrace profiler_get_backtrace(), nullptr)
 
-PROFILER_FUNC_VOID(profiler_get_backtrace_noalloc(char *output,
-                                                  size_t outputSize))
+PROFILER_FUNC_VOID(profiler_get_backtrace_noalloc(char* aOutput,
+                                                  size_t aOutputSize))
 
 // Free a ProfilerBacktrace returned by profiler_get_backtrace().
 #if !defined(MOZ_GECKO_PROFILER)
 inline void
 ProfilerBacktraceDestructor::operator()(ProfilerBacktrace* aBacktrace) {}
 #endif
 
 // Is the profiler active? Note: the return value of this function can become
@@ -369,17 +371,17 @@ PROFILER_FUNC(bool profiler_thread_is_sl
 // on auxiliary threads. Operates the same whether the profiler is active or
 // not.
 PROFILER_FUNC_VOID(profiler_js_interrupt_callback())
 
 // The number of milliseconds since the process started. Operates the same
 // whether the profiler is active or inactive.
 PROFILER_FUNC(double profiler_time(), 0)
 
-PROFILER_FUNC_VOID(profiler_log(const char *str))
+PROFILER_FUNC_VOID(profiler_log(const char* aStr))
 
 // Gets the stack top of the current thread.
 //
 // The thread must have been previously registered with the profiler, otherwise
 // this method will return nullptr.
 PROFILER_FUNC(void* profiler_get_stack_top(), nullptr)
 
 // End of the functions defined whether the profiler is enabled or not.
@@ -442,18 +444,18 @@ profiler_call_exit(void* aHandle)
   }
 
   PseudoStack* pseudoStack = static_cast<PseudoStack*>(aHandle);
   pseudoStack->pop();
 }
 
 // Adds a marker to the PseudoStack. A no-op if the profiler is inactive or in
 // privacy mode.
-void profiler_add_marker(const char *aMarker,
-                         ProfilerMarkerPayload *aPayload = nullptr);
+void profiler_add_marker(const char* aMarkerName,
+                         ProfilerMarkerPayload* aPayload = nullptr);
 
 #define PROFILER_APPEND_LINE_NUMBER_PASTE(id, line) id ## line
 #define PROFILER_APPEND_LINE_NUMBER_EXPAND(id, line) \
   PROFILER_APPEND_LINE_NUMBER_PASTE(id, line)
 #define PROFILER_APPEND_LINE_NUMBER(id) \
   PROFILER_APPEND_LINE_NUMBER_EXPAND(id, __LINE__)
 
 // Uncomment this to turn on systrace or build with
@@ -501,38 +503,38 @@ void profiler_add_marker(const char *aMa
 #define PROFILER_DEFAULT_INTERVAL 1
 
 namespace mozilla {
 
 class MOZ_RAII ProfilerStackFrameRAII {
 public:
   // We only copy the strings at save time, so to take multiple parameters we'd
   // need to copy them then.
-  ProfilerStackFrameRAII(const char *aInfo,
-    js::ProfileEntry::Category aCategory, uint32_t line
+  ProfilerStackFrameRAII(const char* aLabel,
+    js::ProfileEntry::Category aCategory, uint32_t aLine
     MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
   {
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-    mHandle = profiler_call_enter(aInfo, aCategory, this, line);
+    mHandle = profiler_call_enter(aLabel, aCategory, this, aLine);
   }
   ~ProfilerStackFrameRAII() {
     profiler_call_exit(mHandle);
   }
 private:
   MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
   void* mHandle;
 };
 
 class MOZ_RAII ProfilerStackFrameDynamicRAII {
 public:
-  ProfilerStackFrameDynamicRAII(const char* aInfo,
+  ProfilerStackFrameDynamicRAII(const char* aLabel,
     js::ProfileEntry::Category aCategory, uint32_t aLine,
     const char* aDynamicString)
   {
-    mHandle = profiler_call_enter(aInfo, aCategory, this, aLine,
+    mHandle = profiler_call_enter(aLabel, aCategory, this, aLine,
                                   aDynamicString);
   }
 
   ~ProfilerStackFrameDynamicRAII() {
     profiler_call_exit(mHandle);
   }
 
 private:
@@ -588,43 +590,44 @@ public:
     }
   }
 private:
   bool mIssuedWake;
 };
 
 class MOZ_RAII GeckoProfilerTracingRAII {
 public:
-  GeckoProfilerTracingRAII(const char* aCategory, const char* aInfo,
+  GeckoProfilerTracingRAII(const char* aCategory, const char* aMarkerName,
                            UniqueProfilerBacktrace aBacktrace
                            MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
     : mCategory(aCategory)
-    , mInfo(aInfo)
+    , mMarkerName(aMarkerName)
   {
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-    profiler_tracing(mCategory, mInfo, Move(aBacktrace), TRACING_INTERVAL_START);
+    profiler_tracing(mCategory, mMarkerName, Move(aBacktrace),
+                     TRACING_INTERVAL_START);
   }
 
-  GeckoProfilerTracingRAII(const char* aCategory, const char* aInfo
+  GeckoProfilerTracingRAII(const char* aCategory, const char* aMarkerName
                            MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
     : mCategory(aCategory)
-    , mInfo(aInfo)
+    , mMarkerName(aMarkerName)
   {
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-    profiler_tracing(mCategory, mInfo, TRACING_INTERVAL_START);
+    profiler_tracing(mCategory, mMarkerName, TRACING_INTERVAL_START);
   }
 
   ~GeckoProfilerTracingRAII() {
-    profiler_tracing(mCategory, mInfo, TRACING_INTERVAL_END);
+    profiler_tracing(mCategory, mMarkerName, TRACING_INTERVAL_END);
   }
 
 protected:
   MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
   const char* mCategory;
-  const char* mInfo;
+  const char* mMarkerName;
 };
 
 // Convenience class to register and unregister a thread with the profiler.
 // Needs to be the first object on the stack of the thread.
 class MOZ_STACK_CLASS AutoProfilerRegister final
 {
 public:
   explicit AutoProfilerRegister(const char* aName)
--- a/widget/headless/HeadlessWidget.cpp
+++ b/widget/headless/HeadlessWidget.cpp
@@ -145,16 +145,19 @@ HeadlessWidget::Resize(double aX,
     NotifyWindowMoved(aX, aY);
   }
   return Resize(aWidth, aHeight, aRepaint);
 }
 
 void
 HeadlessWidget::SetSizeMode(nsSizeMode aMode)
 {
+  if (aMode == mSizeMode) {
+    return;
+  }
   if (mSizeMode == nsSizeMode_Normal) {
     // Store the last normal size bounds so it can be restored when entering
     // normal mode again.
     mRestoreBounds = mBounds;
   }
 
   nsBaseWidget::SetSizeMode(aMode);
 
@@ -163,33 +166,60 @@ HeadlessWidget::SetSizeMode(nsSizeMode a
   // the window must manually be resized.
   switch(aMode) {
   case nsSizeMode_Normal: {
     Resize(mRestoreBounds.x, mRestoreBounds.y, mRestoreBounds.width, mRestoreBounds.height, false);
     break;
   }
   case nsSizeMode_Minimized:
     break;
-  case nsSizeMode_Maximized:
-  case nsSizeMode_Fullscreen: {
+  case nsSizeMode_Maximized: {
     nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
     if (screen) {
       int32_t left, top, width, height;
       if (NS_SUCCEEDED(screen->GetRectDisplayPix(&left, &top, &width, &height))) {
         Resize(0, 0, width, height, true);
       }
     }
     break;
   }
+  case nsSizeMode_Fullscreen:
+    // This will take care of resizing the window.
+    nsBaseWidget::InfallibleMakeFullScreen(true);
+    break;
   default:
     break;
   }
 }
 
 nsresult
+HeadlessWidget::MakeFullScreen(bool aFullScreen, nsIScreen* aTargetScreen)
+{
+  // Directly update the size mode here so a later call SetSizeMode does
+  // nothing.
+  if (aFullScreen) {
+    if (mSizeMode != nsSizeMode_Fullscreen) {
+      mLastSizeMode = mSizeMode;
+    }
+    mSizeMode = nsSizeMode_Fullscreen;
+  } else {
+    mSizeMode = mLastSizeMode;
+  }
+
+  nsBaseWidget::InfallibleMakeFullScreen(aFullScreen, aTargetScreen);
+
+  if (mWidgetListener) {
+    mWidgetListener->SizeModeChanged(mSizeMode);
+    mWidgetListener->FullscreenChanged(aFullScreen);
+  }
+
+  return NS_OK;
+}
+
+nsresult
 HeadlessWidget::DispatchEvent(WidgetGUIEvent* aEvent, nsEventStatus& aStatus)
 {
 #ifdef DEBUG
   debug_DumpEvent(stdout, aEvent->mWidget, aEvent, "HeadlessWidget", 0);
 #endif
 
   aStatus = nsEventStatus_eIgnore;
 
--- a/widget/headless/HeadlessWidget.h
+++ b/widget/headless/HeadlessWidget.h
@@ -42,16 +42,18 @@ public:
                       double aHeight,
                       bool   aRepaint) override;
   virtual void Resize(double aX,
                       double aY,
                       double aWidth,
                       double aHeight,
                       bool   aRepaint) override;
   virtual void SetSizeMode(nsSizeMode aMode) override;
+  virtual nsresult MakeFullScreen(bool aFullScreen,
+                                  nsIScreen* aTargetScreen = nullptr) override;
   virtual void Enable(bool aState) override;
   virtual bool IsEnabled() const override;
   virtual nsresult SetFocus(bool aRaise) override { return NS_OK; }
   virtual nsresult ConfigureChildren(const nsTArray<Configuration>& aConfigurations) override
   {
     MOZ_ASSERT_UNREACHABLE("Headless widgets do not support configuring children.");
     return NS_ERROR_FAILURE;
   }
@@ -81,16 +83,18 @@ public:
 
   virtual nsresult DispatchEvent(WidgetGUIEvent* aEvent,
                                  nsEventStatus& aStatus) override;
 
 private:
   ~HeadlessWidget() {}
   bool mEnabled;
   bool mVisible;
+  // The size mode before entering fullscreen mode.
+  nsSizeMode mLastSizeMode;
   InputContext mInputContext;
   // In headless there is no window manager to track window bounds
   // across size mode changes, so we must track it to emulate.
   LayoutDeviceIntRect mRestoreBounds;
 };
 
 } // namespace widget
 } // namespace mozilla
--- a/xpcom/threads/TimerThread.cpp
+++ b/xpcom/threads/TimerThread.cpp
@@ -29,17 +29,18 @@ using namespace mozilla::tasktracer;
 NS_IMPL_ISUPPORTS(TimerThread, nsIRunnable, nsIObserver)
 
 TimerThread::TimerThread() :
   mInitialized(false),
   mMonitor("TimerThread.mMonitor"),
   mShutdown(false),
   mWaiting(false),
   mNotified(false),
-  mSleeping(false)
+  mSleeping(false),
+  mAllowedEarlyFiringMicroseconds(0)
 {
 }
 
 TimerThread::~TimerThread()
 {
   mThread = nullptr;
 
   NS_ASSERTION(mTimers.IsEmpty(), "Timers remain in TimerThread::~TimerThread");
@@ -402,17 +403,17 @@ TimerThread::Run()
 
   size_t usIntervalResolution;
   BinarySearchIf(MicrosecondsToInterval(), 0, usForPosInterval, IntervalComparator(), &usIntervalResolution);
   MOZ_ASSERT(PR_MicrosecondsToInterval(usIntervalResolution - 1) == 0);
   MOZ_ASSERT(PR_MicrosecondsToInterval(usIntervalResolution) == 1);
 
   // Half of the amount of microseconds needed to get positive PRIntervalTime.
   // We use this to decide how to round our wait times later
-  int32_t halfMicrosecondsIntervalResolution = usIntervalResolution / 2;
+  mAllowedEarlyFiringMicroseconds = usIntervalResolution / 2;
   bool forceRunNextTimer = false;
 
   while (!mShutdown) {
     // Have to use PRIntervalTime here, since PR_WaitCondVar takes it
     PRIntervalTime waitFor;
     bool forceRunThisTimer = forceRunNextTimer;
     forceRunNextTimer = false;
 
@@ -485,34 +486,34 @@ TimerThread::Run()
 
       if (!mTimers.IsEmpty()) {
         TimeStamp timeout = mTimers[0]->Value()->mTimeout;
 
         // Don't wait at all (even for PR_INTERVAL_NO_WAIT) if the next timer
         // is due now or overdue.
         //
         // Note that we can only sleep for integer values of a certain
-        // resolution. We use halfMicrosecondsIntervalResolution, calculated
+        // resolution. We use mAllowedEarlyFiringMicroseconds, calculated
         // before, to do the optimal rounding (i.e., of how to decide what
         // interval is so small we should not wait at all).
         double microseconds = (timeout - now).ToMilliseconds() * 1000;
 
         if (ChaosMode::isActive(ChaosFeature::TimerScheduling)) {
           // The mean value of sFractions must be 1 to ensure that
           // the average of a long sequence of timeouts converges to the
           // actual sum of their times.
           static const float sFractions[] = {
             0.0f, 0.25f, 0.5f, 0.75f, 1.0f, 1.75f, 2.75f
           };
           microseconds *=
             sFractions[ChaosMode::randomUint32LessThan(ArrayLength(sFractions))];
           forceRunNextTimer = true;
         }
 
-        if (microseconds < halfMicrosecondsIntervalResolution) {
+        if (microseconds < mAllowedEarlyFiringMicroseconds) {
           forceRunNextTimer = false;
           goto next; // round down; execute event now
         }
         waitFor = PR_MicrosecondsToInterval(
           static_cast<uint32_t>(microseconds)); // Floor is accurate enough.
         if (waitFor == 0) {
           waitFor = 1;  // round up, wait the minimum time we can wait
         }
@@ -800,8 +801,14 @@ TimerThread::Observe(nsISupports* /* aSu
     DoBeforeSleep();
   } else if (strcmp(aTopic, "wake_notification") == 0 ||
              strcmp(aTopic, "resume_process_notification") == 0) {
     DoAfterSleep();
   }
 
   return NS_OK;
 }
+
+uint32_t
+TimerThread::AllowedEarlyFiringMicroseconds() const
+{
+  return mAllowedEarlyFiringMicroseconds;
+}
--- a/xpcom/threads/TimerThread.h
+++ b/xpcom/threads/TimerThread.h
@@ -51,16 +51,19 @@ public:
   void DoBeforeSleep();
   void DoAfterSleep();
 
   bool IsOnTimerThread() const
   {
     return mThread == NS_GetCurrentThread();
   }
 
+  uint32_t
+  AllowedEarlyFiringMicroseconds() const;
+
 private:
   ~TimerThread();
 
   bool    mInitialized;
 
   // These internal helper methods must be called while mMonitor is held.
   // AddTimerInternal returns false if the insertion failed.
   bool    AddTimerInternal(nsTimerImpl* aTimer);
@@ -111,16 +114,17 @@ private:
     {
       // This is reversed because std::push_heap() sorts the "largest" to
       // the front of the heap.  We want that to be the earliest timer.
       return aRight->mTimeout < aLeft->mTimeout;
     }
   };
 
   nsTArray<UniquePtr<Entry>> mTimers;
+  uint32_t mAllowedEarlyFiringMicroseconds;
 };
 
 struct TimerAdditionComparator
 {
   TimerAdditionComparator(const mozilla::TimeStamp& aNow,
                           nsTimerImpl* aTimerToInsert) :
     now(aNow)
 #ifdef DEBUG
--- a/xpcom/threads/nsITimer.idl
+++ b/xpcom/threads/nsITimer.idl
@@ -5,16 +5,17 @@
 
 #include "nsISupports.idl"
 
 interface nsIObserver;
 interface nsIEventTarget;
 
 %{C++
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/TimeStamp.h"
 
 /**
  * The signature of the timer callback function passed to initWithFuncCallback.
  * This is the function that will get called when the timer expires if the
  * timer is initialized via initWithFuncCallback.
  *
  * @param aTimer the timer which has expired
  * @param aClosure opaque parameter passed to initWithFuncCallback
@@ -37,16 +38,17 @@ typedef void (*nsTimerCallbackFunc) (nsI
 typedef void (*nsTimerNameCallbackFunc) (nsITimer *aTimer,
                                          bool aAnonymize,
                                          void *aClosure,
                                          char *aBuf, size_t aLen);
 %}
 
 native nsTimerCallbackFunc(nsTimerCallbackFunc);
 native nsTimerNameCallbackFunc(nsTimerNameCallbackFunc);
+[ref] native TimeDuration(mozilla::TimeDuration);
 
 /**
  * The callback interface for timers.
  */
 interface nsITimer;
 
 [function, scriptable, uuid(a796816d-7d47-4348-9ab8-c7aeb3216a7d)]
 interface nsITimerCallback : nsISupports
@@ -162,27 +164,39 @@ interface nsITimer : nsISupports
    */
   [noscript] void initWithFuncCallback(in nsTimerCallbackFunc aCallback,
                                        in voidPtr aClosure,
                                        in unsigned long aDelay,
                                        in unsigned long aType);
 
   /**
    * Initialize a timer to fire after the given millisecond interval.
-   * This version takes a function to call.
+   * This version takes a callback object.
    *
    * @param aFunc      nsITimerCallback interface to call when timer expires
    * @param aDelay     The millisecond interval
    * @param aType      Timer type per TYPE* consts defined above
    */
   void initWithCallback(in nsITimerCallback aCallback,
                         in unsigned long aDelay,
                         in unsigned long aType);
 
   /**
+   * Initialize a timer to fire after the high resolution TimeDuration.
+   * This version takes a callback object.
+   *
+   * @param aFunc      nsITimerCallback interface to call when timer expires
+   * @param aDelay     The high resolution interval
+   * @param aType      Timer type per TYPE* consts defined above
+   */
+  [noscript] void InitHighResolutionWithCallback(in nsITimerCallback aCallback,
+                                                 [const] in TimeDuration aDelay,
+                                                 in unsigned long aType);
+
+  /**
    * Cancel the timer.  This method works on all types, not just on repeating
    * timers -- you might want to cancel a TYPE_ONE_SHOT timer, and even reuse
    * it by re-initializing it (to avoid object destruction and creation costs
    * by conserving one timer instance).
    */
   void cancel();
 
   /**
@@ -246,16 +260,22 @@ interface nsITimer : nsISupports
   /**
    * The nsIEventTarget where the callback will be dispatched. Note that this
    * target may only be set before the call to one of the init methods above.
    *
    * By default the target is the thread that created the timer.
    */
   attribute nsIEventTarget target;
 
+  /**
+   * The number of microseconds this nsITimer implementation can possibly
+   * fire early.
+   */
+  [noscript] readonly attribute unsigned long allowedEarlyFiringMicroseconds;
+
 %{C++
   virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const = 0;
 %}
 };
 
 %{C++
 #define NS_TIMER_CONTRACTID "@mozilla.org/timer;1"
 #define NS_TIMER_CALLBACK_TOPIC "timer-callback"
--- a/xpcom/threads/nsTimerImpl.cpp
+++ b/xpcom/threads/nsTimerImpl.cpp
@@ -144,17 +144,16 @@ nsTimer::Release(void)
   }
 
   return count;
 }
 
 nsTimerImpl::nsTimerImpl(nsITimer* aTimer) :
   mHolder(nullptr),
   mGeneration(0),
-  mDelay(0),
   mITimer(aTimer),
   mMutex("nsTimerImpl::mMutex")
 {
   // XXXbsmedberg: shouldn't this be in Init()?
   mEventTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
 }
 
 //static
@@ -192,19 +191,28 @@ nsTimerImpl::Shutdown()
   if (!gThread) {
     return;
   }
 
   gThread->Shutdown();
   NS_RELEASE(gThread);
 }
 
+nsresult
+nsTimerImpl::InitCommon(uint32_t aDelayMS, uint32_t aType,
+                        Callback&& aNewCallback)
+{
+  return InitCommon(TimeDuration::FromMilliseconds(aDelayMS),
+                    aType, Move(aNewCallback));
+}
+
 
 nsresult
-nsTimerImpl::InitCommon(uint32_t aDelay, uint32_t aType, Callback&& newCallback)
+nsTimerImpl::InitCommon(const TimeDuration& aDelay, uint32_t aType,
+                        Callback&& newCallback)
 {
   mMutex.AssertCurrentThreadOwns();
 
   if (NS_WARN_IF(!gThread)) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   if (!mEventTarget) {
@@ -213,17 +221,17 @@ nsTimerImpl::InitCommon(uint32_t aDelay,
   }
 
   gThread->RemoveTimer(this);
   mCallback.swap(newCallback);
   ++mGeneration;
 
   mType = (uint8_t)aType;
   mDelay = aDelay;
-  mTimeout = TimeStamp::Now() + TimeDuration::FromMilliseconds(mDelay);
+  mTimeout = TimeStamp::Now() + mDelay;
 
   return gThread->AddTimer(this);
 }
 
 nsresult
 nsTimerImpl::InitWithFuncCallbackCommon(nsTimerCallbackFunc aFunc,
                                         void* aClosure,
                                         uint32_t aDelay,
@@ -276,16 +284,26 @@ nsTimerImpl::InitWithNameableFuncCallbac
   return InitWithFuncCallbackCommon(aFunc, aClosure, aDelay, aType, name);
 }
 
 nsresult
 nsTimerImpl::InitWithCallback(nsITimerCallback* aCallback,
                               uint32_t aDelay,
                               uint32_t aType)
 {
+  return InitHighResolutionWithCallback(aCallback,
+                                        TimeDuration::FromMilliseconds(aDelay),
+                                        aType);
+}
+
+nsresult
+nsTimerImpl::InitHighResolutionWithCallback(nsITimerCallback* aCallback,
+                                            const TimeDuration& aDelay,
+                                            uint32_t aType)
+{
   if (NS_WARN_IF(!aCallback)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   Callback cb; // Goes out of scope after the unlock, prevents deadlock
   cb.mType = Callback::Type::Interface;
   cb.mCallback.i = aCallback;
   NS_ADDREF(cb.mCallback.i);
@@ -349,31 +367,31 @@ nsTimerImpl::SetDelay(uint32_t aDelay)
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   bool reAdd = false;
   if (gThread) {
     reAdd = NS_SUCCEEDED(gThread->RemoveTimer(this));
   }
 
-  mDelay = aDelay;
-  mTimeout = TimeStamp::Now() + TimeDuration::FromMilliseconds(mDelay);
+  mDelay = TimeDuration::FromMilliseconds(aDelay);
+  mTimeout = TimeStamp::Now() + mDelay;
 
   if (reAdd) {
     gThread->AddTimer(this);
   }
 
   return NS_OK;
 }
 
 nsresult
 nsTimerImpl::GetDelay(uint32_t* aDelay)
 {
   MutexAutoLock lock(mMutex);
-  *aDelay = mDelay;
+  *aDelay = mDelay.ToMilliseconds();
   return NS_OK;
 }
 
 nsresult
 nsTimerImpl::SetType(uint32_t aType)
 {
   MutexAutoLock lock(mMutex);
   mType = (uint8_t)aType;
@@ -435,16 +453,22 @@ nsTimerImpl::SetTarget(nsIEventTarget* a
   if (aTarget) {
     mEventTarget = aTarget;
   } else {
     mEventTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
   }
   return NS_OK;
 }
 
+nsresult
+nsTimerImpl::GetAllowedEarlyFiringMicroseconds(uint32_t* aValueOut)
+{
+  *aValueOut = gThread ? gThread->AllowedEarlyFiringMicroseconds() : 0;
+  return NS_OK;
+}
 
 void
 nsTimerImpl::Fire(int32_t aGeneration)
 {
   uint8_t oldType;
   uint32_t oldDelay;
   TimeStamp oldTimeout;
   nsCOMPtr<nsITimer> kungFuDeathGrip;
@@ -454,17 +478,17 @@ nsTimerImpl::Fire(int32_t aGeneration)
     // If some other thread Cancels/Inits after this, they're just too late.
     MutexAutoLock lock(mMutex);
     if (aGeneration != mGeneration) {
       return;
     }
 
     mCallbackDuringFire.swap(mCallback);
     oldType = mType;
-    oldDelay = mDelay;
+    oldDelay = mDelay.ToMilliseconds();
     oldTimeout = mTimeout;
     // Ensure that the nsITimer does not unhook from the nsTimerImpl during
     // Fire; this will cause null pointer crashes if the user of the timer drops
     // its reference, and then uses the nsITimer* passed in the callback.
     kungFuDeathGrip = mITimer;
   }
 
   PROFILER_LABEL("Timer", "Fire",
@@ -507,21 +531,20 @@ nsTimerImpl::Fire(int32_t aGeneration)
       ;
   }
 
   Callback trash; // Swap into here to dispose of callback after the unlock
   MutexAutoLock lock(mMutex);
   if (aGeneration == mGeneration && IsRepeating()) {
     // Repeating timer has not been re-init or canceled; reschedule
     mCallbackDuringFire.swap(mCallback);
-    TimeDuration delay = TimeDuration::FromMilliseconds(mDelay);
     if (IsSlack()) {
-      mTimeout = TimeStamp::Now() + delay;
+      mTimeout = TimeStamp::Now() + mDelay;
     } else {
-      mTimeout = mTimeout + delay;
+      mTimeout = mTimeout + mDelay;
     }
     if (gThread) {
       gThread->AddTimer(this);
     }
   }
 
   mCallbackDuringFire.swap(trash);
 
--- a/xpcom/threads/nsTimerImpl.h
+++ b/xpcom/threads/nsTimerImpl.h
@@ -124,17 +124,21 @@ public:
     typedef nsTimerNameCallbackFunc NameFunc;
     typedef mozilla::Variant<NameNothing, NameString, NameFunc> Name;
     static const NameNothing Nothing;
     Name mName;
 
     void*                 mClosure;
   };
 
-  nsresult InitCommon(uint32_t aDelay, uint32_t aType, Callback&& newCallback);
+  nsresult InitCommon(uint32_t aDelayMS, uint32_t aType,
+                      Callback&& newCallback);
+
+  nsresult InitCommon(const TimeDuration& aDelay, uint32_t aType,
+                      Callback&& newCallback);
 
   Callback& GetCallback()
   {
     mMutex.AssertCurrentThreadOwns();
     if (mCallback.mType == Callback::Type::Unknown) {
       return mCallbackDuringFire;
     }
 
@@ -191,17 +195,17 @@ public:
   uint8_t               mType;
 
   // The generation number of this timer, re-generated each time the timer is
   // initialized so one-shot timers can be canceled and re-initialized by the
   // arming thread without any bad race conditions.
   // Updated only after this timer has been removed from the timer thread.
   int32_t               mGeneration;
 
-  uint32_t              mDelay;
+  TimeDuration          mDelay;
   // Updated only after this timer has been removed from the timer thread.
   TimeStamp             mTimeout;
 
 #ifdef MOZ_TASK_TRACER
   mozilla::tasktracer::TracedTaskCommon mTracedTask;
 #endif
 
   static double         sDeltaSum;