Merge inbound to mozilla-central. a=merge PRE_TREEWIDE_CLANG_FORMAT
authorNoemi Erli <nerli@mozilla.com>
Fri, 30 Nov 2018 11:35:05 +0200
changeset 505381 58a0412e15574f063cd380517a0369bfb48b22e0
parent 505372 c5b713000513a2cdc1fdbc70aeb8f7d78bd687b2 (diff)
parent 505380 8e424cc8655849d3629bf1730b1f6f2a342eb1d1 (current diff)
child 505382 2ac59ec6f8de97fa704341cec1bf57c489b19810
child 505391 9ae8864b61d0490fa367ad3cdab41faff6d807db
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone65.0a1
first release with
nightly linux32
58a0412e1557 / 65.0a1 / 20181130093534 / files
nightly linux64
58a0412e1557 / 65.0a1 / 20181130093534 / files
nightly mac
58a0412e1557 / 65.0a1 / 20181130093534 / files
nightly win32
58a0412e1557 / 65.0a1 / 20181130093534 / files
nightly win64
58a0412e1557 / 65.0a1 / 20181130093534 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
.clang-format-ignore
tools/rewriting/ThirdPartyPaths.txt
--- a/.clang-format-ignore
+++ b/.clang-format-ignore
@@ -53,16 +53,20 @@ xpcom/tests/.*
 gfx/gl/GLConsts.h
 gfx/webrender_bindings/webrender_ffi_generated.h
 intl/unicharutil/util/nsUnicodePropertyData.cpp
 intl/unicharutil/util/nsUnicodeScriptCodes.h
 media/mp4parse-rust/mp4parse.h
 widget/gtk/wayland/gtk-primary-selection-client-protocol.h
 widget/gtk/wayland/gtk-primary-selection-protocol.c
 
+# Ignored because these files are used to generate a windows.h STL wrapper,
+# and reformatting them can break generating that wrapper.
+config/windows-h-.*.h
+
 # The XPTCall stubs files have some inline assembly macros
 # that get reformatted badly. See bug 1510781.
 xpcom/reflect/xptcall/md/win32/.*
 xpcom/reflect/xptcall/md/unix/.*
 
 # Generated from ./tools/rewriting/ThirdPartyPaths.txt
 # awk '{print ""$1".*"}' ./tools/rewriting/ThirdPartyPaths.txt
 browser/components/translation/cld2/.*
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -207,19 +207,16 @@ pref("browser.fixup.domainwhitelist.loca
 pref("general.smoothScroll", true);
 #ifdef UNIX_BUT_NOT_MAC
 pref("general.autoScroll", false);
 #else
 pref("general.autoScroll", true);
 #endif
 
 pref("browser.stopReloadAnimation.enabled", true);
-pref("browser.schedulePressure.enabled", true);
-pref("browser.schedulePressure.defaultCount", 3);
-pref("browser.schedulePressure.timeoutMs", 300);
 
 // UI density of the browser chrome. This mostly affects toolbarbutton
 // and urlbar spacing. The possible values are 0=normal, 1=compact, 2=touch.
 pref("browser.uidensity", 0);
 // Whether Firefox will automatically override the uidensity to "touch"
 // while the user is in a touch environment (such as Windows tablet mode).
 pref("browser.touchmode.auto", true);
 
@@ -429,18 +426,16 @@ pref("browser.link.open_newwindow.restri
 #ifdef XP_MACOSX
 pref("browser.link.open_newwindow.disabled_in_fullscreen", true);
 #else
 pref("browser.link.open_newwindow.disabled_in_fullscreen", false);
 #endif
 
 // Tabbed browser
 pref("browser.tabs.multiselect", true);
-pref("browser.tabs.20FpsThrobber", false);
-pref("browser.tabs.30FpsThrobber", false);
 pref("browser.tabs.closeTabByDblclick", false);
 pref("browser.tabs.closeWindowWithLastTab", true);
 // Open related links to a tab, e.g., link in current tab, at next to the
 // current tab if |insertRelatedAfterCurrent| is true.  Otherwise, always
 // append new tab to the end.
 pref("browser.tabs.insertRelatedAfterCurrent", true);
 // Open all links, e.g., bookmarks, history items at next to current tab
 // if |insertAfterCurrent| is true.  Otherwise, append new tab to the end
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -194,25 +194,23 @@ panelview[mainview] > .panel-header {
 .tab-icon-image:not([fadein]),
 .tab-close-button:not([fadein]),
 .tabbrowser-tab:not([fadein])::after,
 .tab-background:not([fadein]) {
   visibility: hidden;
 }
 
 .tab-label:not([fadein]),
-.tab-throbber:not([fadein]),
-.tab-throbber-fallback:not([fadein]) {
+.tab-throbber:not([fadein]) {
   display: none;
 }
 
 %ifdef NIGHTLY_BUILD
 @supports -moz-bool-pref("browser.tabs.hideThrobber") {
-  .tab-throbber,
-  .tab-throbber-fallback {
+  .tab-throbber {
     display: none !important;
   }
 }
 %endif
 
 #tabbrowser-tabs[positionpinnedtabs] > .tabbrowser-tab[pinned] {
   position: fixed !important;
   display: block; /* position:fixed already does this (bug 579776), but let's be explicit */
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -47,17 +47,16 @@ XPCOMUtils.defineLazyModuleGetters(this,
   ProcessHangMonitor: "resource:///modules/ProcessHangMonitor.jsm",
   PromiseUtils: "resource://gre/modules/PromiseUtils.jsm",
   ReaderMode: "resource://gre/modules/ReaderMode.jsm",
   ReaderParent: "resource:///modules/ReaderParent.jsm",
   SafeBrowsing: "resource://gre/modules/SafeBrowsing.jsm",
   Sanitizer: "resource:///modules/Sanitizer.jsm",
   SessionStartup: "resource:///modules/sessionstore/SessionStartup.jsm",
   SessionStore: "resource:///modules/sessionstore/SessionStore.jsm",
-  SchedulePressure: "resource:///modules/SchedulePressure.jsm",
   ShortcutUtils: "resource://gre/modules/ShortcutUtils.jsm",
   SimpleServiceDiscovery: "resource://gre/modules/SimpleServiceDiscovery.jsm",
   SiteDataManager: "resource:///modules/SiteDataManager.jsm",
   SitePermissions: "resource:///modules/SitePermissions.jsm",
   TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm",
   TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.jsm",
   Translation: "resource:///modules/translation/Translation.jsm",
   UITour: "resource:///modules/UITour.jsm",
--- a/browser/base/content/tabbrowser.css
+++ b/browser/base/content/tabbrowser.css
@@ -10,17 +10,16 @@
 #tabbrowser-tabs[closebuttons="activetab"] > .tabbrowser-tab > .tab-stack > .tab-content > .tab-close-button:not([selected="true"]),
 .tab-icon-pending:not([pendingicon]),
 .tab-icon-pending[busy],
 .tab-icon-pending[pinned],
 .tab-icon-image:not([src]):not([pinned]):not([crashed])[selected],
 .tab-icon-image:not([src]):not([pinned]):not([crashed]):not([sharing]),
 .tab-icon-image[busy],
 .tab-throbber:not([busy]),
-.tab-throbber-fallback:not([busy]),
 .tab-icon-sound:not([soundplaying]):not([muted]):not([activemedia-blocked]),
 .tab-icon-sound[pinned],
 .tab-sharing-icon-overlay,
 .tab-icon-overlay {
   display: none;
 }
 
 .tab-sharing-icon-overlay[sharing]:not([selected]),
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -57,18 +57,16 @@ window._gBrowser = {
 
     // To correctly handle keypresses for potential FindAsYouType, while
     // the tab's find bar is not yet initialized.
     messageManager.addMessageListener("Findbar:Keypress", this);
     this._setFindbarData();
 
     XPCOMUtils.defineLazyPreferenceGetter(this, "animationsEnabled",
       "toolkit.cosmeticAnimations.enabled");
-    XPCOMUtils.defineLazyPreferenceGetter(this, "schedulePressureDefaultCount",
-      "browser.schedulePressure.defaultCount");
 
     this._setupEventListeners();
   },
 
   ownerGlobal: window,
 
   ownerDocument: document,
 
@@ -4986,60 +4984,30 @@ class TabProgressListener {
       }
 
       if (this._shouldShowProgress(aRequest)) {
         if (!(aStateFlags & Ci.nsIWebProgressListener.STATE_RESTORING) &&
             aWebProgress && aWebProgress.isTopLevel) {
           this.mTab.setAttribute("busy", "true");
           gBrowser._tabAttrModified(this.mTab, ["busy"]);
           this.mTab._notselectedsinceload = !this.mTab.selected;
-          SchedulePressure.startMonitoring(window, {
-            highPressureFn() {
-              // Only switch back to the SVG loading indicator after getting
-              // three consecutive low pressure callbacks. Used to prevent
-              // switching quickly between the SVG and APNG loading indicators.
-              gBrowser.tabContainer._schedulePressureCount = gBrowser.schedulePressureDefaultCount;
-              gBrowser.tabContainer.setAttribute("schedulepressure", "true");
-            },
-            lowPressureFn() {
-              if (!gBrowser.tabContainer._schedulePressureCount ||
-                --gBrowser.tabContainer._schedulePressureCount <= 0) {
-                gBrowser.tabContainer.removeAttribute("schedulepressure");
-              }
-
-              // If tabs are closed while they are loading we need to
-              // stop monitoring schedule pressure. We don't stop monitoring
-              // during high pressure times because we want to eventually
-              // return to the SVG tab loading animations.
-              let continueMonitoring = true;
-              if (!document.querySelector(".tabbrowser-tab[busy]")) {
-                SchedulePressure.stopMonitoring(window);
-                continueMonitoring = false;
-              }
-              return { continueMonitoring };
-            },
-          });
           gBrowser.syncThrobberAnimations(this.mTab);
         }
 
         if (this.mTab.selected) {
           gBrowser._isBusy = true;
         }
       }
     } else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
                aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
 
       let modifiedAttrs = [];
       if (this.mTab.hasAttribute("busy")) {
         this.mTab.removeAttribute("busy");
         modifiedAttrs.push("busy");
-        if (!document.querySelector(".tabbrowser-tab[busy]")) {
-          SchedulePressure.stopMonitoring(window);
-          gBrowser.tabContainer.removeAttribute("schedulepressure");
-        }
 
         // Only animate the "burst" indicating the page has loaded if
         // the top-level page is the one that finished loading.
         if (aWebProgress.isTopLevel && !aWebProgress.isLoadingDocument &&
             Components.isSuccessCode(aStatus) &&
             !gBrowser.tabAnimationsInProgress &&
             Services.prefs.getBoolPref("toolkit.cosmeticAnimations.enabled")) {
           if (this.mTab._notselectedsinceload) {
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1904,20 +1904,16 @@
                   anonid="tab-loading-burst"
                   class="tab-loading-burst"/>
         <xul:hbox xbl:inherits="pinned,selected=visuallyselected,titlechanged,attention"
                   class="tab-content" align="center">
           <xul:hbox xbl:inherits="fadein,pinned,busy,progress,selected=visuallyselected"
                     anonid="tab-throbber"
                     class="tab-throbber"
                     layer="true"/>
-          <xul:image xbl:inherits="fadein,pinned,busy,progress,selected=visuallyselected"
-                     class="tab-throbber-fallback"
-                     role="presentation"
-                     layer="true"/>
           <xul:hbox xbl:inherits="fadein,pinned,busy,progress,selected=visuallyselected,pendingicon"
                     anonid="tab-icon-pending"
                     class="tab-icon-pending"/>
           <xul:image xbl:inherits="src=image,triggeringprincipal=iconloadingprincipal,requestcontextid,fadein,pinned,selected=visuallyselected,busy,crashed,sharing"
                      anonid="tab-icon-image"
                      class="tab-icon-image"
                      validate="never"
                      role="presentation"/>
--- a/browser/components/preferences/in-content/tests/browser.ini
+++ b/browser/components/preferences/in-content/tests/browser.ini
@@ -34,16 +34,17 @@ skip-if = (os == 'win') # Bug 1480314
 [browser_bug1020245_openPreferences_to_paneContent.js]
 [browser_bug1184989_prevent_scrolling_when_preferences_flipped.js]
 [browser_engines.js]
 support-files =
   browser_bug1184989_prevent_scrolling_when_preferences_flipped.xul
 [browser_change_app_handler.js]
 skip-if = os != "win" # Windows-specific handler application selection dialog
 [browser_checkspelling.js]
+[browser_cloud_storage.js]
 [browser_connection.js]
 [browser_connection_bug388287.js]
 [browser_connection_bug1445991.js]
 skip-if = (verify && debug && (os == 'linux' || os == 'mac'))
 [browser_connection_dnsoverhttps.js]
 [browser_contentblocking.js]
 [browser_cookies_exceptions.js]
 [browser_defaultbrowser_alwayscheck.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_cloud_storage.js
@@ -0,0 +1,158 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+ChromeUtils.defineModuleGetter(this, "FileUtils",
+                               "resource://gre/modules/FileUtils.jsm");
+ChromeUtils.defineModuleGetter(this, "NetUtil",
+                               "resource://gre/modules/NetUtil.jsm");
+
+const DROPBOX_DOWNLOAD_FOLDER = "Dropbox";
+const DROPBOX_CONFIG_FOLDER = (AppConstants.platform === "win") ? "Dropbox" :
+                                                                  ".dropbox";
+const DROPBOX_KEY = "Dropbox";
+const CLOUD_SERVICES_PREF = "cloud.services.";
+
+function create_subdir(dir, subdirname) {
+  let subdir = dir.clone();
+  subdir.append(subdirname);
+  if (subdir.exists()) {
+    subdir.remove(true);
+  }
+  subdir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+  return subdir;
+}
+
+/**
+ * Replaces a directory service entry with a given nsIFile.
+ */
+function registerFakePath(key, folderName) {
+  let dirsvc = Services.dirsvc.QueryInterface(Ci.nsIProperties);
+
+  // Create a directory inside the profile and register it as key
+  let profD = dirsvc.get("ProfD", Ci.nsIFile);
+  // create a subdir just to keep our files out of the way
+  let file = create_subdir(profD, folderName);
+
+  let originalFile;
+  try {
+    // If a file is already provided save it and undefine, otherwise set will
+    // throw for persistent entries (ones that are cached).
+    originalFile = dirsvc.get(key, Ci.nsIFile);
+    dirsvc.undefine(key);
+  } catch (e) {
+    // dirsvc.get will throw if nothing provides for the key and dirsvc.undefine
+    // will throw if it's not a persistent entry, in either case we don't want
+    // to set the original file in cleanup.
+    originalFile = undefined;
+  }
+
+  dirsvc.set(key, file);
+  registerCleanupFunction(() => {
+    dirsvc.undefine(key);
+    if (originalFile) {
+      dirsvc.set(key, originalFile);
+    }
+  });
+}
+
+async function mock_dropbox() {
+  let discoveryFile = null;
+  if (AppConstants.platform === "win") {
+    discoveryFile = FileUtils.getFile("LocalAppData", [DROPBOX_CONFIG_FOLDER]);
+  } else {
+    discoveryFile = FileUtils.getFile("Home", [DROPBOX_CONFIG_FOLDER]);
+  }
+  discoveryFile.append("info.json");
+  if (!discoveryFile.exists()) {
+    discoveryFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+  }
+  console.log(discoveryFile.path);
+  // Mock Dropbox Download folder in Home directory
+  let downloadFolder = FileUtils.getFile("Home", [DROPBOX_DOWNLOAD_FOLDER, "Downloads"]);
+  if (!downloadFolder.exists()) {
+    downloadFolder.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+  }
+  console.log(downloadFolder.path);
+
+  registerCleanupFunction(() => {
+    if (discoveryFile.exists()) {
+      discoveryFile.remove(true);
+    }
+    if (downloadFolder.exists()) {
+      downloadFolder.remove(true);
+    }
+  });
+
+  let data = '{"personal": {"path": "Test"}}';
+  let ostream = FileUtils.openSafeFileOutputStream(discoveryFile);
+  let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
+                  createInstance(Ci.nsIScriptableUnicodeConverter);
+  converter.charset = "UTF-8";
+  let istream = converter.convertToInputStream(data);
+  return new Promise(resolve => {
+    NetUtil.asyncCopy(istream, ostream, resolve);
+  });
+}
+
+add_task(async function setup() {
+  // Create mock Dropbox discovery and download folder for cloudstorage API
+  // to detect dropbox provider app
+  // Set prefs required to display second radio option
+  // 'Save to Dropbox' under Downloads
+  await SpecialPowers.pushPrefEnv({set: [
+    [CLOUD_SERVICES_PREF + "api.enabled", true],
+    [CLOUD_SERVICES_PREF + "storage.key", "Dropbox"],
+  ]});
+  let folderName = "CloudStorage";
+  registerFakePath("Home", folderName);
+  registerFakePath("LocalAppData", folderName);
+  let status = await mock_dropbox();
+  ok(Components.isSuccessCode(status),
+    "Cloud Storage: dropbox mockup should be created successfully. status: " + status);
+});
+
+add_task(async function() {
+  await openPreferencesViaOpenPreferencesAPI("general", { leaveOpen: true });
+  let doc = gBrowser.selectedBrowser.contentDocument;
+  let saveWhereOptions = doc.getElementById("saveWhere");
+  let saveToCloud = doc.getElementById("saveToCloud");
+  is(saveWhereOptions.itemCount, 3, "Radio options count");
+  is_element_visible(saveToCloud, "Save to Dropbox option is visible");
+
+  let saveTo = doc.getElementById("saveTo");
+  ok(saveTo.selected, "Ensure first option is selected by default");
+  is(Services.prefs.getIntPref("browser.download.folderList"), 1,
+                               "Set to system downloadsfolder as the default download location");
+
+  let downloadFolder = doc.getElementById("downloadFolder");
+  let chooseFolder = doc.getElementById("chooseFolder");
+  is(downloadFolder.disabled, false, "downloadFolder filefield is enabled");
+  is(chooseFolder.disabled, false, "chooseFolder button is enabled");
+
+  // Test click of second radio option sets browser.download.folderList as 3
+  // which means the default download location is elsewhere as specified by
+  // cloud storage API getDownloadFolder and pref cloud.services.storage.key
+  saveToCloud.click();
+  is(Services.prefs.getIntPref("browser.download.folderList"), 3,
+                               "Default download location is elsewhere as specified by cloud storage API");
+  is(downloadFolder.disabled, true, "downloadFolder filefield is disabled");
+  is(chooseFolder.disabled, true, "chooseFolder button is disabled");
+
+  // Test selecting first radio option enables downloadFolder filefield and chooseFolder button
+  saveTo.click();
+  is(downloadFolder.disabled, false, "downloadFolder filefield is enabled");
+  is(chooseFolder.disabled, false, "chooseFolder button is enabled");
+
+  // Test selecting third radio option keeps downloadFolder and chooseFolder elements disabled
+  let alwaysAsk = doc.getElementById("alwaysAsk");
+  saveToCloud.click();
+  alwaysAsk.click();
+  is(downloadFolder.disabled, true, "downloadFolder filefield is disabled");
+  is(chooseFolder.disabled, true, "chooseFolder button is disabled");
+
+  gBrowser.removeCurrentTab();
+});
--- a/browser/modules/PermissionUI.jsm
+++ b/browser/modules/PermissionUI.jsm
@@ -66,16 +66,19 @@ ChromeUtils.defineModuleGetter(this, "Se
   "resource://gre/modules/Services.jsm");
 ChromeUtils.defineModuleGetter(this, "SitePermissions",
   "resource:///modules/SitePermissions.jsm");
 ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "URICountListener",
   "resource:///modules/BrowserUsageTelemetry.jsm");
 
+XPCOMUtils.defineLazyServiceGetter(this, "IDNService",
+  "@mozilla.org/network/idn-service;1", "nsIIDNService");
+
 XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
   return Services.strings
                  .createBundle("chrome://browser/locale/browser.properties");
 });
 
 var PermissionUI = {};
 
 /**
@@ -938,29 +941,42 @@ StorageAccessPermissionPrompt.prototype 
     return false;
   },
 
   get permissionKey() {
     // Make sure this name is unique per each third-party tracker
     return "storage-access-" + this.principal.origin;
   },
 
+  prettifyHostPort(uri) {
+    try {
+      uri = Services.uriFixup.createExposableURI(uri);
+    } catch (e) {
+      // ignore, since we can't do anything better
+    }
+    let host = IDNService.convertToDisplayIDN(uri.host, {});
+    if (uri.port != -1) {
+      host += `:${uri.port}`;
+    }
+    return host;
+  },
+
   get popupOptions() {
     return {
       displayURI: false,
-      name: this.principal.URI.hostPort,
-      secondName: this.topLevelPrincipal.URI.hostPort,
+      name: this.prettifyHostPort(this.principal.URI),
+      secondName: this.prettifyHostPort(this.topLevelPrincipal.URI),
     };
   },
 
   onShown() {
     let document = this.browser.ownerDocument;
     let label =
       gBrowserBundle.formatStringFromName("storageAccess.description.label",
-                                          [this.request.principal.URI.hostPort, "<>"], 2);
+                                          [this.prettifyHostPort(this.request.principal.URI), "<>"], 2);
     let parts = label.split("<>");
     if (parts.length == 1) {
       parts.push("");
     }
     let map = {
       "storage-access-perm-label": parts[0],
       "storage-access-perm-learnmore":
         gBrowserBundle.GetStringFromName("storageAccess.description.learnmore"),
deleted file mode 100644
--- a/browser/modules/SchedulePressure.jsm
+++ /dev/null
@@ -1,124 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-var EXPORTED_SYMBOLS = ["SchedulePressure"];
-
-ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-XPCOMUtils.defineLazyPreferenceGetter(this, "SCHEDULE_PRESSURE_ENABLED",
-  "browser.schedulePressure.enabled", true);
-XPCOMUtils.defineLazyPreferenceGetter(this, "TIMEOUT_AMOUNT",
-  "browser.schedulePressure.timeoutMs", 300);
-
-/**
- * The SchedulePressure object provides the ability to alter
- * the behavior of a program based on the idle activity of the
- * host machine.
- */
-var SchedulePressure = {
-  _idleCallbackWeakMap: new WeakMap(),
-  _setTimeoutWeakMap: new WeakMap(),
-  _telemetryCallbackWeakMap: new WeakMap(),
-
-  _createTimeoutFn(window, callbackFn) {
-    return () => {
-      if (window.closed) {
-        TelemetryStopwatch.cancel("FX_SCHEDULE_PRESSURE_IDLE_SAMPLE_MS", window);
-        this._telemetryCallbackWeakMap.delete(window);
-        return;
-      }
-      let nextCallbackId = window.requestIdleCallback(callbackFn, {timeout: TIMEOUT_AMOUNT});
-      this._idleCallbackWeakMap.set(window, nextCallbackId);
-
-      // Don't create another timeout-less idle callback if the first
-      // one hasn't completed yet.
-      if (!this._telemetryCallbackWeakMap.has(window) &&
-          TelemetryStopwatch.start("FX_SCHEDULE_PRESSURE_IDLE_SAMPLE_MS", window)) {
-        let telemetryCallbackId = window.requestIdleCallback(() => {
-          TelemetryStopwatch.finish("FX_SCHEDULE_PRESSURE_IDLE_SAMPLE_MS", window);
-          this._telemetryCallbackWeakMap.delete(window);
-        });
-        this._telemetryCallbackWeakMap.set(window, telemetryCallbackId);
-      }
-    };
-  },
-
-  /**
-   * Starts an interval timeout that periodically waits for
-   * an idle callback. If the idle callback fails to get called
-   * within the timeout specified by TIMEOUT_AMOUNT, the
-   * highPressureFn callback will get called. Otherwise the
-   * lowPressureFn callback will get called.
-   *
-   * @param  window
-   *         The DOM window of the requestee.
-   * @param  options
-   *           highPressureFn
-   *           A function that will be called when the idle callback
-   *           fails to be called within the time specified by TIMEOUT_AMOUNT.
-   *           Returning an object with property of `continueMonitoring` set
-   *           to `false` will prevent further monitoring.
-   *           lowPressureFn
-   *           A function that will be called when the idle callback
-   *           gets called within the time specified by TIMEOUT_AMOUNT.
-   *           Returning an object with property of `continueMonitoring` set
-   *           to `false` will prevent further monitoring.
-   */
-  startMonitoring(window, {highPressureFn, lowPressureFn}) {
-    if (!SCHEDULE_PRESSURE_ENABLED ||
-        this._setTimeoutWeakMap.has(window) ||
-        this._idleCallbackWeakMap.has(window)) {
-      return;
-    }
-
-    let callbackFn = idleDeadline => {
-      if (window.closed) {
-        return;
-      }
-
-      let result;
-      if (idleDeadline.didTimeout) {
-        try {
-          result = highPressureFn();
-        } catch (ex) {}
-      } else {
-        try {
-          result = lowPressureFn();
-        } catch (ex) {}
-      }
-
-      if (result && !result.continueMonitoring) {
-        return;
-      }
-
-      this._setTimeoutWeakMap.set(window,
-        window.setTimeout(this._createTimeoutFn(window, callbackFn), TIMEOUT_AMOUNT));
-    };
-
-    this._setTimeoutWeakMap.set(window,
-      window.setTimeout(this._createTimeoutFn(window, callbackFn), TIMEOUT_AMOUNT));
-  },
-
-  /**
-   * Stops the interval timeout that periodically waits for
-   * an idle callback.
-   *
-   * @param  window
-   *         The DOM window of the requestee.
-   */
-  stopMonitoring(window) {
-    function removeFromMapAndCancelTimeout(map, cancelFn) {
-      if (map.has(window)) {
-        cancelFn(map.get(window));
-        map.delete(window);
-      }
-    }
-
-    TelemetryStopwatch.cancel("FX_SCHEDULE_PRESSURE_IDLE_SAMPLE_MS", window);
-    removeFromMapAndCancelTimeout(this._setTimeoutWeakMap, window.clearTimeout);
-    removeFromMapAndCancelTimeout(this._idleCallbackWeakMap, window.cancelIdleCallback);
-    removeFromMapAndCancelTimeout(this._telemetryCallbackWeakMap, window.cancelIdleCallback);
-  },
-};
--- a/browser/modules/TabsList.jsm
+++ b/browser/modules/TabsList.jsm
@@ -267,22 +267,22 @@ class TabsPanel extends TabsListBase {
     });
   }
 
   _setImageAttributes(row, tab) {
     let button = row.firstElementChild;
     let image = this.doc.getAnonymousElementByAttribute(
       button, "class", "toolbarbutton-icon") ||
       this.doc.getAnonymousElementByAttribute(
-        button, "class", "toolbarbutton-icon tab-throbber-fallback");
+        button, "class", "toolbarbutton-icon tab-throbber-tabslist");
 
     if (image) {
       let busy = tab.getAttribute("busy");
       let progress = tab.getAttribute("progress");
       setAttributes(image, {busy, progress});
       if (busy) {
-        image.classList.add("tab-throbber-fallback");
+        image.classList.add("tab-throbber-tabslist");
       } else {
-        image.classList.remove("tab-throbber-fallback");
+        image.classList.remove("tab-throbber-tabslist");
       }
     }
   }
 }
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -144,17 +144,16 @@ EXTRA_JS_MODULES += [
     'OpenInTabsUtils.jsm',
     'PageActions.jsm',
     'PermissionUI.jsm',
     'PingCentre.jsm',
     'ProcessHangMonitor.jsm',
     'ReaderParent.jsm',
     'RemotePrompt.jsm',
     'Sanitizer.jsm',
-    'SchedulePressure.jsm',
     'SelectionChangedMenulist.jsm',
     'SiteDataManager.jsm',
     'SitePermissions.jsm',
     'TabsList.jsm',
     'ThemeVariableMap.jsm',
     'TransientPrefs.jsm',
     'webrtcUI.jsm',
     'ZoomUI.jsm',
--- a/browser/themes/shared/ctrlTab.inc.css
+++ b/browser/themes/shared/ctrlTab.inc.css
@@ -17,17 +17,18 @@
   padding: 20px 10px 10px;
 %ifndef XP_MACOSX
   font-weight: bold;
 %endif
 }
 
 .ctrlTab-preview {
   -moz-appearance: none;
-  color: inherit;
+  /* !important overrides the :hover color from button.css on Linux */
+  color: inherit !important;
   margin: 0;
   text-shadow: 0 0 1px hsl(0,0%,12%), 0 0 2px hsl(0,0%,12%);
 }
 
 .ctrlTab-canvas > html|img,
 .ctrlTab-canvas > html|canvas {
   min-width: inherit;
   max-width: inherit;
@@ -67,17 +68,16 @@
   margin: -10px -10px 0;
 }
 
 #ctrlTab-showAll:not(:focus) > .ctrlTab-preview-inner {
   background-color: rgba(255,255,255,.2);
 }
 
 .ctrlTab-preview:focus > .ctrlTab-preview-inner {
-  color: white;
   background-color: rgba(0,0,0,.75);
   text-shadow: none;
   border-color: #45a1ff;
 }
 
 .ctrlTab-label {
   text-align: center;
 }
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -195,18 +195,16 @@
 
   skin/classic/browser/tabbrowser/tab-connecting.png           (../shared/tabbrowser/tab-connecting.png)
   skin/classic/browser/tabbrowser/tab-connecting@2x.png        (../shared/tabbrowser/tab-connecting@2x.png)
   skin/classic/browser/tabbrowser/tab-loading.png              (../shared/tabbrowser/tab-loading.png)
   skin/classic/browser/tabbrowser/tab-loading@2x.png           (../shared/tabbrowser/tab-loading@2x.png)
   skin/classic/browser/tabbrowser/tab-loading-inverted.png     (../shared/tabbrowser/tab-loading-inverted.png)
   skin/classic/browser/tabbrowser/tab-loading-inverted@2x.png  (../shared/tabbrowser/tab-loading-inverted@2x.png)
   skin/classic/browser/tabbrowser/loading.svg                  (../shared/tabbrowser/loading.svg)
-  skin/classic/browser/tabbrowser/loading-20fps.svg            (../shared/tabbrowser/loading-20fps.svg)
-  skin/classic/browser/tabbrowser/loading-30fps.svg            (../shared/tabbrowser/loading-30fps.svg)
   skin/classic/browser/tabbrowser/loading-burst.svg            (../shared/tabbrowser/loading-burst.svg)
   skin/classic/browser/tabbrowser/crashed.svg                  (../shared/tabbrowser/crashed.svg)
   skin/classic/browser/tabbrowser/indicator-tab-attention.svg  (../shared/tabbrowser/indicator-tab-attention.svg)
   skin/classic/browser/tabbrowser/pendingpaint.png             (../shared/tabbrowser/pendingpaint.png)
   skin/classic/browser/tabbrowser/tab-audio-playing.svg        (../shared/tabbrowser/tab-audio-playing.svg)
   skin/classic/browser/tabbrowser/tab-audio-playing-small.svg  (../shared/tabbrowser/tab-audio-playing-small.svg)
   skin/classic/browser/tabbrowser/tab-audio-muted.svg          (../shared/tabbrowser/tab-audio-muted.svg)
   skin/classic/browser/tabbrowser/tab-audio-muted-small.svg    (../shared/tabbrowser/tab-audio-muted-small.svg)
deleted file mode 100644
--- a/browser/themes/shared/tabbrowser/loading-20fps.svg
+++ /dev/null
@@ -1,68 +0,0 @@
-<!-- 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="320" height="16" fill="context-fill">
-  <svg>
-    <path d="M2.062 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
-  </svg>
-  <svg x="16">
-    <path d="M5.058 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
-  </svg>
-  <svg x="32">
-    <path d="M8.338 6.171c1.435 0 2.6 0.82 2.6 1.829 0 1.01 -1.165 1.829 -2.6 1.829s-2.6 -0.82 -2.6 -1.829c0 -1.01 1.165 -1.829 2.6 -1.829z"/>
-  </svg>
-  <svg x="48">
-    <path d="M10.33 6.343c1.767 0 3.201 0.742 3.201 1.657s-1.434 1.657 -3.2 1.657c-1.766 0 -3.2 -0.742 -3.2 -1.657s1.434 -1.657 3.2 -1.657z"/>
-  </svg>
-  <svg x="64">
-    <path d="M11.648 6.3c1.683 0 3.05 0.762 3.05 1.7s-1.367 1.7 -3.05 1.7c-1.683 0 -3.05 -0.762 -3.05 -1.7s1.367 -1.7 3.05 -1.7z"/>
-  </svg>
-  <svg x="80">
-    <path d="M12.551 6.15c1.394 0 2.525 0.829 2.525 1.85 0 1.021 -1.131 1.85 -2.525 1.85 -1.393 0 -2.525 -0.829 -2.525 -1.85 0 -1.021 1.132 -1.85 2.525 -1.85z"/>
-  </svg>
-  <svg x="96">
-    <path d="M13.17 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
-  </svg>
-  <svg x="112">
-    <path d="M13.578 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2c0 -1.104 0.897 -2 2 -2z"/>
-  </svg>
-  <svg x="128">
-    <path d="M13.823 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
-  </svg>
-  <svg x="144">
-    <path d="M13.935 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
-  </svg>
-  <svg x="160">
-    <path d="M13.937 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
-  </svg>
-  <svg x="176">
-    <path d="M12.219 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
-  </svg>
-  <svg x="192">
-    <path d="M7.913 6.171c1.34 0 2.429 0.82 2.429 1.829 0 1.01 -1.088 1.829 -2.429 1.829 -1.34 0 -2.428 -0.82 -2.428 -1.829 0 -1.01 1.088 -1.829 2.428 -1.829z"/>
-  </svg>
-  <svg x="208">
-    <path d="M5.545 6.343c1.577 0 2.857 0.742 2.857 1.657s-1.28 1.657 -2.857 1.657c-1.577 0 -2.858 -0.742 -2.858 -1.657s1.28 -1.657 2.858 -1.657z"/>
-  </svg>
-  <svg x="224">
-    <path d="M4.168 6.3c1.517 0 2.75 0.762 2.75 1.7s-1.233 1.7 -2.75 1.7c-1.518 0 -2.75 -0.762 -2.75 -1.7s1.232 -1.7 2.75 -1.7z"/>
-  </svg>
-  <svg x="240">
-    <path d="M3.288 6.15c1.311 0 2.375 0.829 2.375 1.85 0 1.021 -1.064 1.85 -2.375 1.85 -1.31 0 -2.375 -0.829 -2.375 -1.85 0 -1.021 1.065 -1.85 2.375 -1.85z"/>
-  </svg>
-  <svg x="256">
-    <path d="M2.714 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2c0 -1.104 0.897 -2 2 -2z"/>
-  </svg>
-  <svg x="272">
-    <path d="M2.35 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2c0 -1.104 0.897 -2 2 -2z"/>
-  </svg>
-  <svg x="288">
-    <path d="M2.142 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
-  </svg>
-  <svg x="304">
-    <path d="M2.053 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2c0 -1.104 0.897 -2 2 -2z"/>
-  </svg>
-  <svg x="320">
-    <path d="M2.062 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
-  </svg>
-</svg>
deleted file mode 100644
--- a/browser/themes/shared/tabbrowser/loading-30fps.svg
+++ /dev/null
@@ -1,98 +0,0 @@
-<!-- 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="480" height="16" fill="context-fill">
-  <svg>
-    <path d="M2.062 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
-  </svg>
-  <svg x="16">
-    <path d="M3.613 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
-  </svg>
-  <svg x="32">
-    <path d="M6.352 6.057c1.214 0 2.2 0.87 2.2 1.943 0 1.072 -0.986 1.943 -2.2 1.943 -1.214 0 -2.2 -0.87 -2.2 -1.943 0 -1.072 0.986 -1.943 2.2 -1.943z"/>
-  </svg>
-  <svg x="48">
-    <path d="M8.338 6.171c1.435 0 2.6 0.82 2.6 1.829 0 1.01 -1.165 1.829 -2.6 1.829s-2.6 -0.82 -2.6 -1.829c0 -1.01 1.165 -1.829 2.6 -1.829z"/>
-  </svg>
-  <svg x="64">
-    <path d="M9.762 6.286c1.655 0 3 0.768 3 1.714s-1.345 1.714 -3 1.714c-1.656 0 -3 -0.768 -3 -1.714s1.344 -1.714 3 -1.714z"/>
-  </svg>
-  <svg x="80">
-    <path d="M10.828 6.4c1.877 0 3.4 0.717 3.4 1.6 0 0.883 -1.523 1.6 -3.4 1.6 -1.876 0 -3.4 -0.717 -3.4 -1.6 0 -0.883 1.524 -1.6 3.4 -1.6z"/>
-  </svg>
-  <svg x="96">
-    <path d="M11.648 6.3c1.683 0 3.05 0.762 3.05 1.7s-1.367 1.7 -3.05 1.7c-1.683 0 -3.05 -0.762 -3.05 -1.7s1.367 -1.7 3.05 -1.7z"/>
-  </svg>
-  <svg x="112">
-    <path d="M12.287 6.2c1.49 0 2.7 0.807 2.7 1.8s-1.21 1.8 -2.7 1.8c-1.49 0 -2.7 -0.807 -2.7 -1.8s1.21 -1.8 2.7 -1.8z"/>
-  </svg>
-  <svg x="128">
-    <path d="M12.785 6.1c1.297 0 2.35 0.851 2.35 1.9s-1.053 1.9 -2.35 1.9c-1.297 0 -2.35 -0.851 -2.35 -1.9s1.053 -1.9 2.35 -1.9z"/>
-  </svg>
-  <svg x="144">
-    <path d="M13.17 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2c0 -1.104 0.897 -2 2 -2z"/>
-  </svg>
-  <svg x="160">
-    <path d="M13.463 6c1.103 0 2 0.896 2 2s-0.897 2 -2 2a2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
-  </svg>
-  <svg x="176">
-    <path d="M13.677 6c1.103 0 2 0.896 2 2s-0.897 2 -2 2a2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
-  </svg>
-  <svg x="192">
-    <path d="M13.823 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
-  </svg>
-  <svg x="208">
-    <path d="M13.911 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
-  </svg>
-  <svg x="224">
-    <path d="M13.947 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
-  </svg>
-  <svg x="240">
-    <path d="M13.937 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
-  </svg>
-  <svg x="256">
-    <path d="M13.27 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2c0 -1.104 0.897 -2 2 -2z"/>
-  </svg>
-  <svg x="272">
-    <path d="M10.65 6.057c1.182 0 2.142 0.87 2.142 1.943 0 1.072 -0.96 1.943 -2.143 1.943 -1.182 0 -2.142 -0.87 -2.142 -1.943 0 -1.072 0.96 -1.943 2.142 -1.943z"/>
-  </svg>
-  <svg x="288">
-    <path d="M7.911 6.171c1.34 0 2.429 0.82 2.429 1.829 0 1.01 -1.088 1.829 -2.429 1.829 -1.34 0 -2.428 -0.82 -2.428 -1.829 0 -1.01 1.088 -1.829 2.428 -1.829z"/>
-  </svg>
-  <svg x="304">
-    <path d="M6.18 6.286c1.498 0 2.715 0.768 2.715 1.714s-1.217 1.714 -2.715 1.714c-1.498 0 -2.714 -0.768 -2.714 -1.714s1.216 -1.714 2.714 -1.714z"/>
-  </svg>
-  <svg x="320">
-    <path d="M5.01 6.4c1.655 0 3 0.717 3 1.6 0 0.883 -1.345 1.6 -3 1.6 -1.656 0 -3 -0.717 -3 -1.6 0 -0.883 1.344 -1.6 3 -1.6z"/>
-  </svg>
-  <svg x="336">
-    <path d="M4.167 6.3c1.518 0 2.75 0.762 2.75 1.7s-1.232 1.7 -2.75 1.7 -2.75 -0.762 -2.75 -1.7 1.232 -1.7 2.75 -1.7z"/>
-  </svg>
-  <svg x="352">
-    <path d="M3.54 6.2c1.38 0 2.5 0.807 2.5 1.8s-1.12 1.8 -2.5 1.8 -2.5 -0.807 -2.5 -1.8 1.12 -1.8 2.5 -1.8z"/>
-  </svg>
-  <svg x="368">
-    <path d="M3.069 6.1c1.241 0 2.25 0.851 2.25 1.9s-1.009 1.9 -2.25 1.9c-1.242 0 -2.25 -0.851 -2.25 -1.9s1.008 -1.9 2.25 -1.9z"/>
-  </svg>
-  <svg x="384">
-    <path d="M2.714 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
-  </svg>
-  <svg x="400">
-    <path d="M2.452 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
-  </svg>
-  <svg x="416">
-    <path d="M2.266 6c1.103 0 2 0.896 2 2s-0.897 2 -2 2a2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
-  </svg>
-  <svg x="432">
-    <path d="M2.142 6c1.103 0 2 0.896 2 2s-0.897 2 -2 2a2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
-  </svg>
-  <svg x="448">
-    <path d="M2.071 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
-  </svg>
-  <svg x="464">
-    <path d="M2.046 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2c0 -1.104 0.897 -2 2 -2z"/>
-  </svg>
-  <svg x="480">
-    <path d="M2.062 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
-  </svg>
-</svg>
--- a/browser/themes/shared/tabbrowser/loading.svg
+++ b/browser/themes/shared/tabbrowser/loading.svg
@@ -1,6 +1,98 @@
 <!-- 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="960" height="16" fill="context-fill">
-  <path d="M2 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM18.392 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM35.565 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM53.024 6.049c1.198 0 2.17.874 2.17 1.951s-.972 1.951-2.17 1.951-2.17-.874-2.17-1.951.972-1.951 2.17-1.951zM70.33 6.099c1.295 0 2.346.852 2.346 1.901 0 1.05-1.05 1.901-2.345 1.901-1.295 0-2.346-.852-2.346-1.901 0-1.05 1.051-1.901 2.346-1.901zM87.423 6.149c1.391 0 2.52.83 2.52 1.851 0 1.022-1.129 1.851-2.52 1.851s-2.52-.83-2.52-1.851c0-1.022 1.129-1.851 2.52-1.851zM104.337 6.199c1.488 0 2.696.807 2.696 1.801 0 .994-1.208 1.801-2.696 1.801-1.488 0-2.696-.807-2.696-1.801 0-.994 1.208-1.801 2.696-1.801zM121.112 6.249c1.584 0 2.87.784 2.87 1.751 0 .967-1.286 1.751-2.87 1.751-1.585 0-2.872-.784-2.872-1.751 0-.967 1.286-1.751 2.87-1.751zM137.776 6.299c1.68 0 3.045.762 3.045 1.701 0 .939-1.364 1.701-3.045 1.701-1.681 0-3.046-.761-3.046-1.701 0-.939 1.365-1.701 3.046-1.701zM154.351 6.349c1.778 0 3.221.74 3.221 1.651s-1.443 1.651-3.22 1.651c-1.779 0-3.222-.74-3.222-1.651s1.443-1.651 3.22-1.651zM170.854 6.399c1.874 0 3.395.717 3.395 1.601s-1.521 1.601-3.395 1.601-3.396-.717-3.396-1.601 1.522-1.601 3.396-1.601zM187.294 6.344c1.769 0 3.205.742 3.205 1.656 0 .914-1.436 1.656-3.205 1.656-1.768 0-3.204-.742-3.204-1.656 0-.914 1.436-1.656 3.204-1.656zM203.683 6.287c1.657 0 3.003.767 3.003 1.713 0 .946-1.346 1.713-3.003 1.713-1.658 0-3.004-.767-3.004-1.713 0-.946 1.346-1.713 3.004-1.713zM220.025 6.23c1.547 0 2.803.793 2.803 1.77s-1.256 1.77-2.803 1.77-2.803-.792-2.803-1.77 1.256-1.77 2.803-1.77zM236.328 6.172c1.436 0 2.602.82 2.602 1.828 0 1.009-1.166 1.828-2.602 1.828s-2.602-.82-2.602-1.828c0-1.009 1.166-1.828 2.602-1.828zM252.596 6.115c1.325 0 2.401.845 2.401 1.885s-1.076 1.885-2.401 1.885c-1.326 0-2.402-.845-2.402-1.885s1.076-1.885 2.402-1.885zM268.832 6.057c1.214 0 2.2.87 2.2 1.943 0 1.072-.986 1.943-2.2 1.943-1.215 0-2.2-.87-2.2-1.943 0-1.072.985-1.943 2.2-1.943zM285.04 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM301.222 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM317.38 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM333.517 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM349.635 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM365.734 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM381.816 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM397.883 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM413.934 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM429.972 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM445.997 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM462.01 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM478.01 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM494 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM509.84 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM525.344 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM540.294 6.049c1.171 0 2.122.874 2.122 1.951s-.95 1.951-2.122 1.951c-1.171 0-2.122-.874-2.122-1.951s.95-1.951 2.122-1.951zM554.717 6.1c1.24 0 2.248.85 2.248 1.9 0 1.05-1.008 1.9-2.248 1.9-1.24 0-2.248-.85-2.248-1.9 0-1.05 1.008-1.9 2.248-1.9zM569.174 6.15c1.31 0 2.373.829 2.373 1.85 0 1.021-1.063 1.85-2.373 1.85-1.31 0-2.374-.828-2.374-1.85 0-1.021 1.063-1.85 2.373-1.85zM583.938 6.2c1.378 0 2.498.806 2.498 1.8s-1.12 1.8-2.498 1.8c-1.38 0-2.499-.806-2.499-1.8s1.12-1.8 2.499-1.8zM598.964 6.25c1.448 0 2.624.784 2.624 1.75s-1.176 1.75-2.624 1.75c-1.448 0-2.624-.784-2.624-1.75s1.176-1.75 2.624-1.75zM614.18 6.3c1.517 0 2.75.762 2.75 1.7s-1.234 1.7-2.75 1.7-2.75-.762-2.75-1.7 1.233-1.7 2.75-1.7zM629.534 6.35c1.587 0 2.875.74 2.875 1.65 0 .91-1.288 1.65-2.875 1.65-1.586 0-2.874-.74-2.874-1.65 0-.91 1.289-1.65 2.875-1.65zM644.992 6.4c1.656 0 3 .717 3 1.6 0 .883-1.344 1.6-3 1.6-1.655 0-3-.717-3-1.6 0-.883 1.345-1.6 3-1.6zM660.532 6.343c1.578 0 2.858.742 2.858 1.657s-1.28 1.657-2.858 1.657c-1.577 0-2.857-.742-2.857-1.657s1.28-1.657 2.857-1.657zM676.138 6.286c1.498 0 2.714.768 2.714 1.714s-1.216 1.714-2.714 1.714-2.714-.768-2.714-1.714 1.216-1.714 2.714-1.714zM691.798 6.229c1.419 0 2.571.793 2.571 1.771 0 .978-1.152 1.771-2.571 1.771-1.42 0-2.572-.793-2.572-1.771 0-.978 1.152-1.771 2.572-1.771zM707.503 6.171c1.34 0 2.428.82 2.428 1.829 0 1.01-1.088 1.829-2.428 1.829-1.34 0-2.429-.82-2.429-1.829 0-1.01 1.088-1.829 2.429-1.829zM723.247 6.114c1.261 0 2.285.845 2.285 1.886 0 1.04-1.024 1.886-2.285 1.886-1.262 0-2.287-.846-2.287-1.886s1.024-1.886 2.286-1.886zM739.024 6.057c1.183 0 2.143.87 2.143 1.943 0 1.072-.96 1.943-2.143 1.943-1.182 0-2.143-.87-2.143-1.943 0-1.072.96-1.943 2.143-1.943zM754.831 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM770.665 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM786.521 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM802.399 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM818.295 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM834.21 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM850.139 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM866.083 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM882.04 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM898.01 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM913.993 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM929.985 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM945.988 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4zM962 6a2.001 2.001 0 0 1 0 4 2.001 2.001 0 0 1 0-4z"/>
+<svg xmlns="http://www.w3.org/2000/svg" width="480" height="16" fill="context-fill">
+  <svg>
+    <path d="M2.062 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
+  </svg>
+  <svg x="16">
+    <path d="M3.613 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
+  </svg>
+  <svg x="32">
+    <path d="M6.352 6.057c1.214 0 2.2 0.87 2.2 1.943 0 1.072 -0.986 1.943 -2.2 1.943 -1.214 0 -2.2 -0.87 -2.2 -1.943 0 -1.072 0.986 -1.943 2.2 -1.943z"/>
+  </svg>
+  <svg x="48">
+    <path d="M8.338 6.171c1.435 0 2.6 0.82 2.6 1.829 0 1.01 -1.165 1.829 -2.6 1.829s-2.6 -0.82 -2.6 -1.829c0 -1.01 1.165 -1.829 2.6 -1.829z"/>
+  </svg>
+  <svg x="64">
+    <path d="M9.762 6.286c1.655 0 3 0.768 3 1.714s-1.345 1.714 -3 1.714c-1.656 0 -3 -0.768 -3 -1.714s1.344 -1.714 3 -1.714z"/>
+  </svg>
+  <svg x="80">
+    <path d="M10.828 6.4c1.877 0 3.4 0.717 3.4 1.6 0 0.883 -1.523 1.6 -3.4 1.6 -1.876 0 -3.4 -0.717 -3.4 -1.6 0 -0.883 1.524 -1.6 3.4 -1.6z"/>
+  </svg>
+  <svg x="96">
+    <path d="M11.648 6.3c1.683 0 3.05 0.762 3.05 1.7s-1.367 1.7 -3.05 1.7c-1.683 0 -3.05 -0.762 -3.05 -1.7s1.367 -1.7 3.05 -1.7z"/>
+  </svg>
+  <svg x="112">
+    <path d="M12.287 6.2c1.49 0 2.7 0.807 2.7 1.8s-1.21 1.8 -2.7 1.8c-1.49 0 -2.7 -0.807 -2.7 -1.8s1.21 -1.8 2.7 -1.8z"/>
+  </svg>
+  <svg x="128">
+    <path d="M12.785 6.1c1.297 0 2.35 0.851 2.35 1.9s-1.053 1.9 -2.35 1.9c-1.297 0 -2.35 -0.851 -2.35 -1.9s1.053 -1.9 2.35 -1.9z"/>
+  </svg>
+  <svg x="144">
+    <path d="M13.17 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2c0 -1.104 0.897 -2 2 -2z"/>
+  </svg>
+  <svg x="160">
+    <path d="M13.463 6c1.103 0 2 0.896 2 2s-0.897 2 -2 2a2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
+  </svg>
+  <svg x="176">
+    <path d="M13.677 6c1.103 0 2 0.896 2 2s-0.897 2 -2 2a2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
+  </svg>
+  <svg x="192">
+    <path d="M13.823 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
+  </svg>
+  <svg x="208">
+    <path d="M13.911 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
+  </svg>
+  <svg x="224">
+    <path d="M13.947 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
+  </svg>
+  <svg x="240">
+    <path d="M13.937 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
+  </svg>
+  <svg x="256">
+    <path d="M13.27 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2c0 -1.104 0.897 -2 2 -2z"/>
+  </svg>
+  <svg x="272">
+    <path d="M10.65 6.057c1.182 0 2.142 0.87 2.142 1.943 0 1.072 -0.96 1.943 -2.143 1.943 -1.182 0 -2.142 -0.87 -2.142 -1.943 0 -1.072 0.96 -1.943 2.142 -1.943z"/>
+  </svg>
+  <svg x="288">
+    <path d="M7.911 6.171c1.34 0 2.429 0.82 2.429 1.829 0 1.01 -1.088 1.829 -2.429 1.829 -1.34 0 -2.428 -0.82 -2.428 -1.829 0 -1.01 1.088 -1.829 2.428 -1.829z"/>
+  </svg>
+  <svg x="304">
+    <path d="M6.18 6.286c1.498 0 2.715 0.768 2.715 1.714s-1.217 1.714 -2.715 1.714c-1.498 0 -2.714 -0.768 -2.714 -1.714s1.216 -1.714 2.714 -1.714z"/>
+  </svg>
+  <svg x="320">
+    <path d="M5.01 6.4c1.655 0 3 0.717 3 1.6 0 0.883 -1.345 1.6 -3 1.6 -1.656 0 -3 -0.717 -3 -1.6 0 -0.883 1.344 -1.6 3 -1.6z"/>
+  </svg>
+  <svg x="336">
+    <path d="M4.167 6.3c1.518 0 2.75 0.762 2.75 1.7s-1.232 1.7 -2.75 1.7 -2.75 -0.762 -2.75 -1.7 1.232 -1.7 2.75 -1.7z"/>
+  </svg>
+  <svg x="352">
+    <path d="M3.54 6.2c1.38 0 2.5 0.807 2.5 1.8s-1.12 1.8 -2.5 1.8 -2.5 -0.807 -2.5 -1.8 1.12 -1.8 2.5 -1.8z"/>
+  </svg>
+  <svg x="368">
+    <path d="M3.069 6.1c1.241 0 2.25 0.851 2.25 1.9s-1.009 1.9 -2.25 1.9c-1.242 0 -2.25 -0.851 -2.25 -1.9s1.008 -1.9 2.25 -1.9z"/>
+  </svg>
+  <svg x="384">
+    <path d="M2.714 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
+  </svg>
+  <svg x="400">
+    <path d="M2.452 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
+  </svg>
+  <svg x="416">
+    <path d="M2.266 6c1.103 0 2 0.896 2 2s-0.897 2 -2 2a2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
+  </svg>
+  <svg x="432">
+    <path d="M2.142 6c1.103 0 2 0.896 2 2s-0.897 2 -2 2a2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
+  </svg>
+  <svg x="448">
+    <path d="M2.071 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
+  </svg>
+  <svg x="464">
+    <path d="M2.046 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2c0 -1.104 0.897 -2 2 -2z"/>
+  </svg>
+  <svg x="480">
+    <path d="M2.062 6a2 2 0 0 1 0 4 2.001 2.001 0 0 1 -2 -2 2 2 0 0 1 2 -2z"/>
+  </svg>
 </svg>
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -144,36 +144,36 @@
   }
   100% {
     opacity: 0;
     transform: scale(40);
   }
 }
 
 .tab-throbber,
-.tab-throbber-fallback,
+.tab-throbber-tabslist,
 .tab-icon-pending,
 .tab-icon-image,
 .tab-sharing-icon-overlay,
 .tab-icon-sound,
 .tab-close-button {
   margin-top: 1px;
 }
 
 .tab-throbber,
-.tab-throbber-fallback,
+.tab-throbber-tabslist,
 .tab-icon-pending,
 .tab-icon-image,
 .tab-sharing-icon-overlay {
   height: 16px;
   width: 16px;
 }
 
 .tab-throbber:not([pinned]),
-.tab-throbber-fallback:not([pinned]),
+.tab-throbber-tabslist:not([pinned]),
 .tab-sharing-icon-overlay:not([pinned]),
 .tab-icon-pending:not([pinned]),
 .tab-icon-image:not([pinned]) {
   margin-inline-end: 6px;
 }
 
 :root[sessionrestored] .tab-throbber[busy] {
   position: relative;
@@ -181,47 +181,31 @@
 }
 
 :root[sessionrestored] .tab-throbber[busy]::before {
   content: "";
   position: absolute;
   background-image: url("chrome://browser/skin/tabbrowser/loading.svg");
   background-position: left center;
   background-repeat: no-repeat;
-  width: 960px;
+  width: 480px;
   height: 100%;
-  animation: tab-throbber-animation 1.05s steps(60) infinite;
+  animation: tab-throbber-animation 1.05s steps(30) infinite;
   -moz-context-properties: fill;
 
   /* XXX: It would be nice to transition between the "connecting" color and the
      "loading" color (see the `.tab-throbber[progress]::before` rule below);
      however, that currently forces main thread painting. When this is fixed
      (after WebRender lands), we can do something like
      `transition: fill 0.333s, opacity 0.333s;` */
 
   fill: currentColor;
   opacity: 0.7;
 }
 
-@supports -moz-bool-pref("browser.tabs.30FpsThrobber") {
-  :root[sessionrestored] .tab-throbber[busy]::before {
-    background-image: url("chrome://browser/skin/tabbrowser/loading-30fps.svg");
-    animation: tab-throbber-animation 1.05s steps(30) infinite;
-    width: 480px;
-  }
-}
-
-@supports -moz-bool-pref("browser.tabs.20FpsThrobber") {
-  :root[sessionrestored] .tab-throbber[busy]::before {
-    background-image: url("chrome://browser/skin/tabbrowser/loading-20fps.svg");
-    animation: tab-throbber-animation 1.05s steps(20) infinite;
-    width: 320px;
-  }
-}
-
 :root[sessionrestored] .tab-throbber[busy]:-moz-locale-dir(rtl)::before {
   animation-name: tab-throbber-animation-rtl;
 }
 
 @keyframes tab-throbber-animation {
   0% { transform: translateX(0); }
   100% { transform: translateX(-100%); }
 }
@@ -244,21 +228,16 @@
 }
 
 #TabsToolbar[brighttext] .tab-throbber[progress]:not([selected=true])::before {
   /* Don't change the --tab-loading-fill variable because this should only affect
      tabs that are not visually selected. */
   fill: #84c1ff;
 }
 
-#tabbrowser-tabs[schedulepressure] .tab-throbber,
-#tabbrowser-tabs:not([schedulepressure]) .tab-throbber-fallback {
-  display: none;
-}
-
 .tab-icon-image {
   list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.svg");
   -moz-context-properties: fill;
   fill: currentColor;
 }
 
 .tab-icon-image[sharing]:not([selected]),
 .tab-sharing-icon-overlay {
@@ -342,38 +321,38 @@
 .tab-icon-overlay[muted]:not([crashed]) {
   list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-muted-small.svg");
 }
 
 .tab-icon-overlay[activemedia-blocked]:not([crashed]) {
   list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-blocked-small.svg");
 }
 
-.tab-throbber-fallback[busy] {
+.tab-throbber-tabslist[busy] {
   list-style-image: url("chrome://browser/skin/tabbrowser/tab-connecting.png");
 }
 
-.tab-throbber-fallback[progress] {
+.tab-throbber-tabslist[progress] {
   list-style-image: url("chrome://browser/skin/tabbrowser/tab-loading.png");
 }
 
-#TabsToolbar[brighttext] .tab-throbber-fallback[progress]:not([selected=true]) {
+#TabsToolbar[brighttext] .tab-throbber-tabslist[progress]:not([selected=true]) {
   list-style-image: url("chrome://browser/skin/tabbrowser/tab-loading-inverted.png");
 }
 
 @media (min-resolution: 1.1dppx) {
-  .tab-throbber-fallback[busy] {
+  .tab-throbber-tabslist[busy] {
     list-style-image: url("chrome://browser/skin/tabbrowser/tab-connecting@2x.png");
   }
 
-  .tab-throbber-fallback[progress] {
+  .tab-throbber-tabslist[progress] {
     list-style-image: url("chrome://browser/skin/tabbrowser/tab-loading@2x.png");
   }
 
-  #TabsToolbar[brighttext] .tab-throbber-fallback[progress]:not([selected=true]) {
+  #TabsToolbar[brighttext] .tab-throbber-tabslist[progress]:not([selected=true]) {
     list-style-image: url("chrome://browser/skin/tabbrowser/tab-loading-inverted@2x.png");
   }
 }
 
 .tab-label {
   margin-inline-end: 0;
   margin-inline-start: 0;
   /* Maintain consistent alignment in case of font fallback for non-Latin characters. */
@@ -773,17 +752,17 @@
 }
 
 .all-tabs-item:hover > .all-tabs-button,
 .all-tabs-item:hover > .all-tabs-secondary-button {
   /* Since the background is set on the item, don't set it on the children. */
   background-color: transparent !important;
 }
 
-.all-tabs-item > .all-tabs-button > .tab-throbber-fallback {
+.tab-throbber-tabslist {
   display: block;
   margin-inline-end: 0;
 }
 
 .all-tabs-item[selected] {
   font-weight: bold;
   box-shadow: inset 4px 0 var(--blue-40);
 }
--- a/build/sparse-profiles/taskgraph
+++ b/build/sparse-profiles/taskgraph
@@ -43,8 +43,9 @@ glob:**/*.configure
 glob:**/tooltool-manifests/**
 
 # For scheduling android-gradle-dependencies.
 path:mobile/android/config/
 glob:**/*.gradle
 
 # for action-task building
 path:.taskcluster.yml
+path:.cron.yml
--- a/devtools/client/inspector/flexbox/components/FlexItemList.js
+++ b/devtools/client/inspector/flexbox/components/FlexItemList.js
@@ -31,17 +31,20 @@ class FlexItemList extends PureComponent
       onShowBoxModelHighlighterForNode,
       scrollToTop,
       setSelectedNode,
     } = this.props;
 
     return (
       dom.div(
         { className: "flex-item-list" },
-        dom.div({ className: "flex-item-list-header" }, getStr("flexbox.flexItems")),
+        dom.div({ className: "flex-item-list-header" },
+          !flexItems.length ?
+            getStr("flexbox.noFlexItems") :
+            getStr("flexbox.flexItems")),
         flexItems.map((flexItem, index) => FlexItem({
           key: flexItem.actorID,
           flexItem,
           index: index + 1,
           onHideBoxModelHighlighter,
           onShowBoxModelHighlighterForNode,
           scrollToTop,
           setSelectedNode,
--- a/devtools/client/inspector/flexbox/components/FlexItemSizingOutline.js
+++ b/devtools/client/inspector/flexbox/components/FlexItemSizingOutline.js
@@ -2,60 +2,50 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { PureComponent } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
-const { colorUtils } = require("devtools/shared/css/color");
 
 const Types = require("../types");
 
 class FlexItemSizingOutline extends PureComponent {
   static get propTypes() {
     return {
-      color: PropTypes.string.isRequired,
       flexDirection: PropTypes.string.isRequired,
       flexItem: PropTypes.shape(Types.flexItem).isRequired,
     };
   }
 
   renderBasisOutline(mainBaseSize) {
     return (
       dom.div({
         className: "flex-outline-basis" + (!mainBaseSize ? " zero-basis" : ""),
-        style: {
-          color: colorUtils.setAlpha(this.props.color, 0.4),
-        },
       })
     );
   }
 
   renderDeltaOutline(mainDeltaSize) {
     if (!mainDeltaSize) {
       return null;
     }
 
     return (
       dom.div({
         className: "flex-outline-delta",
-        style: {
-          backgroundColor: colorUtils.setAlpha(this.props.color, 0.1),
-        },
       })
     );
   }
 
   renderFinalOutline(mainFinalSize, mainMaxSize, mainMinSize, isClamped) {
     return (
-      dom.div({
-        className: "flex-outline-final" + (isClamped ? " clamped" : ""),
-      })
+      dom.div({ className: "flex-outline-final" + (isClamped ? " clamped" : "") })
     );
   }
 
   renderPoint(className, label = className) {
     return dom.div({ className: `flex-outline-point ${className}`, "data-label": label });
   }
 
   render() {
@@ -151,17 +141,16 @@ class FlexItemSizingOutline extends Pure
     return (
       dom.div({ className: "flex-outline-container" },
         dom.div(
           {
             className: "flex-outline" +
                        (isRow ? " row" : " column") +
                        (mainDeltaSize > 0 ? " growing" : " shrinking"),
             style: {
-              color: this.props.color,
               gridTemplateColumns,
             },
           },
           renderedBaseAndFinalPoints,
           showMin ? this.renderPoint("min") : null,
           showMax ? this.renderPoint("max") : null,
           this.renderBasisOutline(mainBaseSize),
           this.renderDeltaOutline(mainDeltaSize),
--- a/devtools/client/inspector/flexbox/components/Flexbox.js
+++ b/devtools/client/inspector/flexbox/components/Flexbox.js
@@ -59,32 +59,28 @@ class Flexbox extends PureComponent {
       onShowBoxModelHighlighterForNode,
       scrollToTop,
       setSelectedNode,
     });
   }
 
   renderFlexItemSizing() {
     const {
-      color,
-    } = this.props.flexbox;
-    const {
       flexItems,
       flexItemShown,
       properties,
     } = this.props.flexContainer;
 
     const flexItem = flexItems.find(item => item.nodeFront.actorID === flexItemShown);
     if (!flexItem) {
       return null;
     }
 
     return createElement(Fragment, null,
       FlexItemSizingOutline({
-        color,
         flexDirection: properties["flex-direction"],
         flexItem,
       }),
       FlexItemSizingProperties({
         flexDirection: properties["flex-direction"],
         flexItem,
       })
     );
@@ -105,31 +101,30 @@ class Flexbox extends PureComponent {
       return (
         dom.div({ className: "devtools-sidepanel-no-result" },
           getStr("flexbox.noFlexboxeOnThisPage")
         )
       );
     }
 
     const {
-      flexItems,
       flexItemShown,
     } = flexContainer;
 
     return (
       dom.div({ id: "layout-flexbox-container" },
         Header({
           flexContainer,
           getSwatchColorPickerTooltip,
           onHideBoxModelHighlighter,
           onSetFlexboxOverlayColor,
           onShowBoxModelHighlighterForNode,
           onToggleFlexboxHighlighter,
           setSelectedNode,
         }),
-        !flexItemShown && flexItems.length > 0 ? this.renderFlexItemList() : null,
+        !flexItemShown ? this.renderFlexItemList() : null,
         flexItemShown ? this.renderFlexItemSizing() : null,
       )
     );
   }
 }
 
 module.exports = Flexbox;
--- a/devtools/client/inspector/flexbox/test/browser.ini
+++ b/devtools/client/inspector/flexbox/test/browser.ini
@@ -30,16 +30,17 @@ support-files =
 [browser_flexbox_item_list_01.js]
 [browser_flexbox_item_list_02.js]
 [browser_flexbox_item_list_updates_on_change.js]
 [browser_flexbox_item_outline_exists.js]
 [browser_flexbox_item_outline_has_correct_layout.js]
 [browser_flexbox_item_outline_hidden_when_useless.js]
 [browser_flexbox_item_outline_renders_basisfinal_points_correctly.js]
 [browser_flexbox_item_outline_rotates_for_column.js]
+[browser_flexbox_non_flex_item_is_not_shown.js]
 [browser_flexbox_pseudo_elements_are_listed.js]
 [browser_flexbox_sizing_flexibility_not_displayed_when_useless.js]
 [browser_flexbox_sizing_info_do_not_show_unspecified_min_dimension.js]
 [browser_flexbox_sizing_info_exists.js]
 [browser_flexbox_sizing_info_for_different_writing_modes.js]
 [browser_flexbox_sizing_info_for_pseudos.js]
 [browser_flexbox_sizing_info_for_text_nodes.js]
 [browser_flexbox_sizing_info_has_correct_sections.js]
--- a/devtools/client/inspector/flexbox/test/browser_flexbox_item_list_01.js
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_item_list_01.js
@@ -1,13 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
+const { getStr } = require("devtools/client/inspector/layout/utils/l10n");
+
 // Test the flex item list is empty when there are no flex items for the selected flex
 // container.
 
 const TEST_URI = `
   <div id="container" style="display:flex">
   </div>
 `;
 
@@ -16,17 +18,18 @@ add_task(async function() {
   const { inspector, flexboxInspector } = await openLayoutView();
   const { document: doc } = flexboxInspector;
   const { highlighters } = inspector;
 
   const onFlexHeaderRendered = waitForDOM(doc, ".flex-header");
   await selectNode("#container", inspector);
   const [flexHeader] = await onFlexHeaderRendered;
   const flexHighlighterToggle = flexHeader.querySelector("#flexbox-checkbox-toggle");
-  const flexItemList = doc.querySelector(".flex-item-list");
+  const flexItemListHeader = doc.querySelector(".flex-item-list-header");
 
   info("Checking the state of the Flexbox Inspector.");
   ok(flexHeader, "The flex container header is rendered.");
   ok(flexHighlighterToggle, "The flexbox highlighter toggle is rendered.");
-  ok(!flexItemList, "The flex item list is not rendered.");
+  is(flexItemListHeader.textContent, getStr("flexbox.noFlexItems"),
+    "The flex item list header shows 'No flex items' when there are no items.");
   ok(!flexHighlighterToggle.checked, "The flexbox highlighter toggle is unchecked.");
   ok(!highlighters.flexboxHighlighterShown, "No flexbox highlighter is shown.");
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_non_flex_item_is_not_shown.js
@@ -0,0 +1,28 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that an element that is the child of a flex container but is not actually a flex
+// item is not shown in the sidebar when selected.
+
+const TEST_URI = `
+<div style="display:flex">
+  <div id="item" style="position:absolute;">test</div>
+</div>
+`;
+
+add_task(async function() {
+  await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  const { inspector, flexboxInspector } = await openLayoutView();
+  const { document: doc } = flexboxInspector;
+
+  info("Select the container's supposed flex item.");
+  await selectNode("#item", inspector);
+  const noFlexContainerOrItemSelected =
+    doc.querySelector(".flexbox-pane .devtools-sidepanel-no-result");
+
+  ok(noFlexContainerOrItemSelected,
+    "The flexbox pane shows a message to select a flex container or item to continue.");
+});
--- a/devtools/client/locales/en-US/layout.properties
+++ b/devtools/client/locales/en-US/layout.properties
@@ -19,16 +19,20 @@ flexbox.flexItemOf=Flex Item of %S
 
 # LOCALIZATION NOTE (flexbox.noFlexboxeOnThisPage): In the case where there are no CSS
 # flex containers to display.
 flexbox.noFlexboxeOnThisPage=Select a Flex container or item to continue.
 
 # LOCALIZATION NOTE (flexbox.flexItems): Header label displayed for the flex item list.
 flexbox.flexItems=Flex Items
 
+# LOCALIZATION NOTE (flexbox.noFlexItems): Label shown in the flex items list section if
+# there are no flex items for the flex container to display.
+flexbox.noFlexItems=No flex items
+
 # LOCALIZATION NOTE (flexbox.itemSizing.baseSizeSectionHeader): Header label displayed
 # at the start of the flex item sizing Base Size section.
 flexbox.itemSizing.baseSizeSectionHeader=Base Size
 
 # LOCALIZATION NOTE (flexbox.itemSizing.flexibilitySectionHeader): Header label displayed
 # at the start of the flex item sizing Flexibility section.
 flexbox.itemSizing.flexibilitySectionHeader=Flexibility
 
--- a/devtools/client/themes/layout.css
+++ b/devtools/client/themes/layout.css
@@ -1,12 +1,26 @@
 /* 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/. */
 
+ :root {
+  --flex-basis-outline-border-color: var(--blue-40);
+  --flex-basis-outline-background-color: rgba(69, 161, 255, 0.25);
+  --flex-growing-delta-outline-background-color: rgba(221, 0, 169, 0.13);
+  --flex-shrinking-delta-outline-background-color: #E9E3FF;
+}
+
+:root.theme-dark {
+  --flex-basis-outline-border-color: rgba(10, 132, 255, 0.85);
+  --flex-basis-outline-background-color: rgba(10, 132, 255, 0.3);
+  --flex-growing-delta-outline-background-color: rgba(255, 26, 217, 0.25);
+  --flex-shrinking-delta-outline-background-color: #322952;
+}
+
 .layout-container {
   height: 100%;
   width: 100%;
   overflow-y: auto;
   overflow-x: auto;
   min-width: 200px;
 }
 
@@ -307,50 +321,71 @@ html[dir="rtl"] .flex-item-list .devtool
     drop-shadow(0px -1px 0px var(--theme-body-background));
 }
 
 .flex-outline.column .flex-outline-final.clamped::after {
   transform: rotate(-.25turn);
 }
 
 .flex-outline-basis {
-  border-style: dotted;
-  border-width: 3px;
-  margin: 1px;
+  position: relative;
+  border: 3px dotted var(--flex-basis-outline-border-color);
+  margin: 2px;
   grid-column: basis-start / basis-end;
 }
 
+/* Fills the basis outline with a blue background color that is contained inside the
+   dotted border. This gives the impression the dotted border alternates between
+   white and blue. */
+.flex-outline-basis::before {
+  content: "";
+  position: absolute;
+  height: 100%;
+  width: 100%;
+  top: 0;
+  left: 0;
+  background-color: var(--flex-basis-outline-background-color);
+}
+
 .flex-outline-basis.zero-basis {
   border-width: 0 0 0 3px;
 }
 
 .flex-outline-delta {
   grid-column: delta-start / delta-end;
-  margin: 3px 0;
-  opacity: .5;
   position: relative;
 }
 
+.flex-outline.growing .flex-outline-delta {
+  background-color: var(--flex-growing-delta-outline-background-color);
+  right: 2px;
+}
+
+.flex-outline.shrinking .flex-outline-delta {
+  background-color: var(--flex-shrinking-delta-outline-background-color);
+  margin: 5px 5px 5px 0px;
+}
+
 .flex-outline-delta::before {
   content: "";
   position: absolute;
   left: 2px;
   right: 2px;
   top: calc(50% - .5px);
   height: 1px;
-  background: currentColor;
+  background: var(--theme-highlight-red);
 }
 
 .flex-outline-delta::after {
   content: "";
   position: absolute;
   width: 5px;
   height: 5px;
   top: 50%;
-  border: 1px solid currentColor;
+  border: 1px solid var(--theme-highlight-red);
 }
 
 .flex-outline.growing .flex-outline-delta:after {
   right: 2px;
   border-width: 1px 1px 0 0;
   transform-origin: top right;
   transform: rotate(.125turn);
 }
@@ -368,16 +403,17 @@ html[dir="rtl"] .flex-item-list .devtool
   grid-row: 1;
   display: grid;
 }
 
 .flex-outline-point.basis,
 .flex-outline-point.basisfinal {
   grid-column-end: basis-end;
   justify-self: end;
+  color: var(--theme-highlight-blue);
 }
 
 .flex-outline.shrinking .flex-outline-point.basis {
   grid-column-start: basis-end;
   justify-self: start;
 }
 
 .flex-outline-point.final {
--- a/devtools/server/actors/animation-type-longhand.js
+++ b/devtools/server/actors/animation-type-longhand.js
@@ -109,19 +109,19 @@ exports.ANIMATION_TYPE_FOR_LONGHANDS = [
     "outline-style",
     "overflow-clip-box-block",
     "overflow-clip-box-inline",
     "overflow-wrap",
     "overflow-x",
     "overflow-y",
     "overscroll-behavior-x",
     "overscroll-behavior-y",
-    "page-break-after",
-    "page-break-before",
-    "page-break-inside",
+    "break-after",
+    "break-before",
+    "break-inside",
     "paint-order",
     "pointer-events",
     "position",
     "quotes",
     "resize",
     "ruby-align",
     "ruby-position",
     "scroll-behavior",
--- a/devtools/server/actors/layout.js
+++ b/devtools/server/actors/layout.js
@@ -330,23 +330,22 @@ const LayoutActor = ActorClassWithSpec(l
    * selected node. The current node can be a grid/flex container or grid/flex item.
    * If it is a grid/flex item, returns the parent grid/flex container. Otherwise, returns
    * null if the current or parent node is not a grid/flex container.
    *
    * @param  {Node|NodeActor} node
    *         The node to start iterating at.
    * @param  {String} type
    *         Can be "grid" or "flex", the display type we are searching for.
-   * @param  {Boolean|null} onlyLookAtCurrentNode
-   *         Whether or not to consider only the current node's display (ie, don't walk
-   *         up the tree).
+   * @param  {Node|null} container
+   *         The container of the current node.
    * @return {GridActor|FlexboxActor|null} The GridActor or FlexboxActor of the
    * grid/flex container of the give node. Otherwise, returns null.
    */
-  getCurrentDisplay(node, type, onlyLookAtCurrentNode) {
+  getCurrentDisplay(node, type, container) {
     if (isNodeDead(node)) {
       return null;
     }
 
     // Given node can either be a Node or a NodeActor.
     if (node.rawNode) {
       node = node.rawNode;
     }
@@ -357,20 +356,28 @@ const LayoutActor = ActorClassWithSpec(l
 
     // If the node is an element, check first if it is itself a flex or a grid.
     if (currentNode.nodeType === currentNode.ELEMENT_NODE) {
       if (!displayType) {
         return null;
       }
 
       if (type == "flex") {
-        if (displayType == "inline-flex" || displayType == "flex") {
+        // If only the current node is being considered when finding its display, then
+        // return it as only a flex container.
+        if ((displayType == "inline-flex" || displayType == "flex") &&
+              !container) {
           return new FlexboxActor(this, currentNode);
-        } else if (onlyLookAtCurrentNode) {
-          return null;
+
+        // If considering the current node's container, then we are trying to determine
+        // the current node's flex item status.
+        } else if (container) {
+          if (this.isNodeFlexItemInContainer(currentNode, container)) {
+            return new FlexboxActor(this, container);
+          }
         }
       } else if (type == "grid" &&
                  (displayType == "inline-grid" || displayType == "grid")) {
         return new GridActor(this, currentNode);
       }
     }
 
     // Otherwise, check if this is a flex/grid item or the parent node is a flex/grid
@@ -382,17 +389,19 @@ const LayoutActor = ActorClassWithSpec(l
       if (!currentNode) {
         break;
       }
 
       displayType = this.walker.getNode(currentNode).displayType;
 
       if (type == "flex" &&
           (displayType == "inline-flex" || displayType == "flex")) {
-        return new FlexboxActor(this, currentNode);
+        if (this.isNodeFlexItemInContainer(node, currentNode)) {
+          return new FlexboxActor(this, currentNode);
+        }
       } else if (type == "grid" &&
                  (displayType == "inline-grid" || displayType == "grid")) {
         return new GridActor(this, currentNode);
       } else if (displayType == "contents") {
         // Continue walking up the tree since the parent node is a content element.
         continue;
       }
 
@@ -426,21 +435,23 @@ const LayoutActor = ActorClassWithSpec(l
    * @param  {Node|NodeActor} node
    *         The node to start iterating at.
    * @param  {Boolean|null} onlyLookAtParents
    *         Whether or not to only consider the parent node of the given node.
    * @return {FlexboxActor|null} The FlexboxActor of the flex container of the given node.
    * Otherwise, returns null.
    */
   getCurrentFlexbox(node, onlyLookAtParents) {
+    let container = null;
+
     if (onlyLookAtParents) {
-      node = node.rawNode.parentNode;
+      container = node.rawNode.parentNode;
     }
 
-    return this.getCurrentDisplay(node, "flex", onlyLookAtParents);
+    return this.getCurrentDisplay(node, "flex", container);
   },
 
   /**
    * Returns an array of GridActor objects for all the grid elements contained in the
    * given root node.
    *
    * @param  {Node|NodeActor} node
    *         The root node for grid elements
@@ -466,16 +477,45 @@ const LayoutActor = ActorClassWithSpec(l
 
     const frames = node.querySelectorAll("iframe, frame");
     for (const frame of frames) {
       gridActors = gridActors.concat(this.getGrids(frame.contentDocument));
     }
 
     return gridActors;
   },
+
+  /**
+   * Returns whether or not the given node is actually considered a flex item by its
+   * container.
+   *
+   * @param  {Node|NodeActor} supposedItem
+   *         The node that might be a flex item of its container.
+   * @param  {Node} container
+   *         The node's container.
+   * @return {Boolean} Whether or not the node we are looking at is a flex item of its
+   * container.
+   */
+  isNodeFlexItemInContainer(supposedItem, container) {
+    const containerDisplayType = this.walker.getNode(container).displayType;
+
+    if (containerDisplayType == "inline-flex" || containerDisplayType == "flex") {
+      const containerFlex = container.getAsFlexContainer();
+
+      for (const line of containerFlex.getLines()) {
+        for (const item of line.getItems()) {
+          if (item.node === supposedItem) {
+            return true;
+          }
+        }
+      }
+    }
+
+    return false;
+  },
 });
 
 function isNodeDead(node) {
   return !node || (node.rawNode && Cu.isDeadWrapper(node.rawNode));
 }
 
 exports.FlexboxActor = FlexboxActor;
 exports.FlexItemActor = FlexItemActor;
--- a/devtools/shared/css/generated/properties-db.js
+++ b/devtools/shared/css/generated/properties-db.js
@@ -2935,19 +2935,19 @@ exports.CSS_PROPERTIES = {
       "translate",
       "offset-path",
       "scroll-behavior",
       "scroll-snap-type-x",
       "scroll-snap-type-y",
       "overscroll-behavior-x",
       "overscroll-behavior-y",
       "isolation",
-      "page-break-after",
-      "page-break-before",
-      "page-break-inside",
+      "break-after",
+      "break-before",
+      "break-inside",
       "resize",
       "perspective",
       "perspective-origin",
       "backface-visibility",
       "transform-box",
       "transform-style",
       "transform-origin",
       "contain",
@@ -4770,16 +4770,66 @@ exports.CSS_PROPERTIES = {
     "values": [
       "border-box",
       "content-box",
       "inherit",
       "initial",
       "unset"
     ]
   },
+  "break-after": {
+    "isInherited": false,
+    "subproperties": [
+      "break-after"
+    ],
+    "supports": [],
+    "values": [
+      "always",
+      "auto",
+      "avoid",
+      "inherit",
+      "initial",
+      "left",
+      "page",
+      "right",
+      "unset"
+    ]
+  },
+  "break-before": {
+    "isInherited": false,
+    "subproperties": [
+      "break-before"
+    ],
+    "supports": [],
+    "values": [
+      "always",
+      "auto",
+      "avoid",
+      "inherit",
+      "initial",
+      "left",
+      "page",
+      "right",
+      "unset"
+    ]
+  },
+  "break-inside": {
+    "isInherited": false,
+    "subproperties": [
+      "break-inside"
+    ],
+    "supports": [],
+    "values": [
+      "auto",
+      "avoid",
+      "inherit",
+      "initial",
+      "unset"
+    ]
+  },
   "caption-side": {
     "isInherited": true,
     "subproperties": [
       "caption-side"
     ],
     "supports": [],
     "values": [
       "bottom",
@@ -7767,51 +7817,53 @@ exports.CSS_PROPERTIES = {
       "inherit",
       "initial",
       "unset"
     ]
   },
   "page-break-after": {
     "isInherited": false,
     "subproperties": [
-      "page-break-after"
+      "break-after"
     ],
     "supports": [],
     "values": [
       "always",
       "auto",
       "avoid",
       "inherit",
       "initial",
       "left",
+      "page",
       "right",
       "unset"
     ]
   },
   "page-break-before": {
     "isInherited": false,
     "subproperties": [
-      "page-break-before"
+      "break-before"
     ],
     "supports": [],
     "values": [
       "always",
       "auto",
       "avoid",
       "inherit",
       "initial",
       "left",
+      "page",
       "right",
       "unset"
     ]
   },
   "page-break-inside": {
     "isInherited": false,
     "subproperties": [
-      "page-break-inside"
+      "break-inside"
     ],
     "supports": [],
     "values": [
       "auto",
       "avoid",
       "inherit",
       "initial",
       "unset"
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -1200,28 +1200,49 @@ Navigator::GetMediaDevices(ErrorResult& 
 
 void
 Navigator::MozGetUserMedia(const MediaStreamConstraints& aConstraints,
                            NavigatorUserMediaSuccessCallback& aOnSuccess,
                            NavigatorUserMediaErrorCallback& aOnError,
                            CallerType aCallerType,
                            ErrorResult& aRv)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
   if (!mWindow || !mWindow->GetOuterWindow() ||
       mWindow->GetOuterWindow()->GetCurrentInnerWindow() != mWindow) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return;
   }
 
   MediaManager::GetUserMediaSuccessCallback onsuccess(&aOnSuccess);
   MediaManager::GetUserMediaErrorCallback onerror(&aOnError);
 
-  MediaManager* manager = MediaManager::Get();
-  aRv = manager->GetUserMedia(mWindow, aConstraints, std::move(onsuccess),
-                              std::move(onerror), aCallerType);
+  nsWeakPtr weakWindow = nsWeakPtr(do_GetWeakReference(mWindow));
+
+  MediaManager::Get()->GetUserMedia(mWindow, aConstraints, aCallerType)->Then(
+    GetMainThreadSerialEventTarget(), __func__,
+    [weakWindow, onsuccess = std::move(onsuccess)](const RefPtr<DOMMediaStream>& aStream) {
+      nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(weakWindow);
+      if (!window || !window->GetOuterWindow() ||
+          window->GetOuterWindow()->GetCurrentInnerWindow() != window) {
+        return; // Leave Promise pending after navigation by design.
+      }
+      MediaManager::CallOnSuccess(&onsuccess, *aStream);
+    },
+    [weakWindow, onerror = std::move(onerror)](const RefPtr<MediaMgrError>& aError) {
+      nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(weakWindow);
+      if (!window || !window->GetOuterWindow() ||
+          window->GetOuterWindow()->GetCurrentInnerWindow() != window) {
+        return; // Leave Promise pending after navigation by design.
+      }
+      auto error = MakeRefPtr<MediaStreamError>(window, *aError);
+      MediaManager::CallOnError(&onerror, *error);
+    }
+  );
 }
 
 void
 Navigator::MozGetUserMediaDevices(const MediaStreamConstraints& aConstraints,
                                   MozGetUserMediaDevicesSuccessCallback& aOnSuccess,
                                   NavigatorUserMediaErrorCallback& aOnError,
                                   uint64_t aInnerWindowID,
                                   const nsAString& aCallID,
--- a/dom/base/UseCounters.conf
+++ b/dom/base/UseCounters.conf
@@ -130,8 +130,11 @@ method console.profileEnd
 // document.open information
 custom DocumentOpen calls document.open in a way that creates a new Window object
 custom DocumentOpenReplace calls document.open in a way that creates a new Window object and replaces the old history entry.
 
 custom FilteredCrossOriginIFrame cross-origin <iframe> within a CSS/SVG filter
 
 // Custom Elements
 method CustomElementRegistry.define
+
+// Shadow DOM
+method Element.attachShadow
--- a/dom/base/nsWindowMemoryReporter.cpp
+++ b/dom/base/nsWindowMemoryReporter.cpp
@@ -556,26 +556,28 @@ nsWindowMemoryReporter::CollectReports(n
       path,
       nsIMemoryReporter::KIND_OTHER,
       nsIMemoryReporter::UNITS_COUNT,
       /* amount = */ 1,
       /* description = */ NS_LITERAL_CSTRING("A ghost window."),
       aData);
   }
 
+  // clang-format off
   MOZ_COLLECT_REPORT(
     "ghost-windows", KIND_OTHER, UNITS_COUNT, ghostWindows.Count(),
 "The number of ghost windows present (the number of nodes underneath "
 "explicit/window-objects/top(none)/ghost, modulo race conditions).  A ghost "
 "window is not shown in any tab, is not in a tab group with any "
 "non-detached windows, and has met these criteria for at least "
 "memory.ghost_window_timeout_seconds, or has survived a round of "
 "about:memory's minimize memory usage button.\n\n"
 "Ghost windows can happen legitimately, but they are often indicative of "
 "leaks in the browser or add-ons.");
+  // clang-format on
 
   WindowPaths windowPaths;
   WindowPaths topWindowPaths;
 
   // Collect window memory usage.
   SizeOfState fakeState(nullptr);   // this won't be used
   nsWindowSizes windowTotalSizes(fakeState);
   for (uint32_t i = 0; i < windows.Length(); i++) {
--- a/dom/fetch/FetchDriver.cpp
+++ b/dom/fetch/FetchDriver.cpp
@@ -721,17 +721,17 @@ FetchDriver::HttpFetch(const nsACString&
     AutoTArray<nsCString, 5> unsafeHeaders;
     mRequest->Headers()->GetUnsafeHeaders(unsafeHeaders);
     nsCOMPtr<nsILoadInfo> loadInfo = chan->GetLoadInfo();
     if (loadInfo) {
       loadInfo->SetCorsPreflightInfo(unsafeHeaders, false);
     }
   }
 
-  if (mIsTrackingFetch && nsContentUtils::IsTailingEnabled()) {
+  if (mIsTrackingFetch && nsContentUtils::IsTailingEnabled() && cos) {
     cos->AddClassFlags(nsIClassOfService::Throttleable |
                        nsIClassOfService::Tail);
   }
 
   if (mIsTrackingFetch && nsContentUtils::IsLowerNetworkPriority()) {
     nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(chan);
     if (p) {
       p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
--- a/dom/fetch/FetchStream.cpp
+++ b/dom/fetch/FetchStream.cpp
@@ -91,240 +91,200 @@ FetchStream::Create(JSContext* aCx, Fetc
     }
 
     // Note, this will create a ref-cycle between the holder and the stream.
     // The cycle is broken when the stream is closed or the worker begins
     // shutting down.
     stream->mWorkerRef = workerRef.forget();
   }
 
-  if (!JS::HasReadableStreamCallbacks(aCx)) {
-    JS::SetReadableStreamCallbacks(aCx,
-                                   &FetchStream::RequestDataCallback,
-                                   &FetchStream::WriteIntoReadRequestCallback,
-                                   &FetchStream::CancelCallback,
-                                   &FetchStream::ClosedCallback,
-                                   &FetchStream::ErroredCallback,
-                                   &FetchStream::FinalizeCallback);
-  }
-
   aRv.MightThrowJSException();
   JS::Rooted<JSObject*> body(aCx,
     JS::NewReadableExternalSourceStreamObject(aCx, stream));
   if (!body) {
     aRv.StealExceptionFromJSContext(aCx);
     return;
   }
 
   // This will be released in FetchStream::FinalizeCallback().  We are
   // guaranteed the jsapi will call FinalizeCallback when ReadableStream
   // js object is finalized.
   NS_ADDREF(stream.get());
 
   aStream.set(body);
 }
 
-/* static */ void
-FetchStream::RequestDataCallback(JSContext* aCx,
-                                 JS::HandleObject aStream,
-                                 void* aUnderlyingSource,
-                                 size_t aDesiredSize)
+void
+FetchStream::requestData(JSContext* aCx,
+                         JS::HandleObject aStream,
+                         size_t aDesiredSize)
 {
-  MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
 #if MOZ_DIAGNOSTIC_ASSERT_ENABLED
   bool disturbed;
   if (!JS::ReadableStreamIsDisturbed(aCx, aStream, &disturbed)) {
     JS_ClearPendingException(aCx);
   } else {
     MOZ_DIAGNOSTIC_ASSERT(disturbed);
   }
 #endif
 
-  RefPtr<FetchStream> stream = static_cast<FetchStream*>(aUnderlyingSource);
-  stream->AssertIsOnOwningThread();
+  AssertIsOnOwningThread();
 
-  MutexAutoLock lock(stream->mMutex);
+  MutexAutoLock lock(mMutex);
 
-  MOZ_DIAGNOSTIC_ASSERT(stream->mState == eInitializing ||
-                        stream->mState == eWaiting ||
-                        stream->mState == eChecking ||
-                        stream->mState == eReading);
+  MOZ_DIAGNOSTIC_ASSERT(mState == eInitializing ||
+                        mState == eWaiting ||
+                        mState == eChecking ||
+                        mState == eReading);
 
-  if (stream->mState == eReading) {
+  if (mState == eReading) {
     // We are already reading data.
     return;
   }
 
-  if (stream->mState == eChecking) {
+  if (mState == eChecking) {
     // If we are looking for more data, there is nothing else we should do:
     // let's move this checking operation in a reading.
-    MOZ_ASSERT(stream->mInputStream);
-    stream->mState = eReading;
+    MOZ_ASSERT(mInputStream);
+    mState = eReading;
     return;
   }
 
-  if (stream->mState == eInitializing) {
+  if (mState == eInitializing) {
     // The stream has been used for the first time.
-    stream->mStreamHolder->MarkAsRead();
+    mStreamHolder->MarkAsRead();
   }
 
-  stream->mState = eReading;
+  mState = eReading;
 
-  if (!stream->mInputStream) {
+  if (!mInputStream) {
     // This is the first use of the stream. Let's convert the
     // mOriginalInputStream into an nsIAsyncInputStream.
-    MOZ_ASSERT(stream->mOriginalInputStream);
+    MOZ_ASSERT(mOriginalInputStream);
 
     nsCOMPtr<nsIAsyncInputStream> asyncStream;
     nsresult rv =
-      NS_MakeAsyncNonBlockingInputStream(stream->mOriginalInputStream.forget(),
+      NS_MakeAsyncNonBlockingInputStream(mOriginalInputStream.forget(),
                                          getter_AddRefs(asyncStream));
     if (NS_WARN_IF(NS_FAILED(rv))) {
-      stream->ErrorPropagation(aCx, lock, aStream, rv);
+      ErrorPropagation(aCx, lock, aStream, rv);
       return;
     }
 
-    stream->mInputStream = asyncStream;
-    stream->mOriginalInputStream = nullptr;
+    mInputStream = asyncStream;
+    mOriginalInputStream = nullptr;
   }
 
-  MOZ_DIAGNOSTIC_ASSERT(stream->mInputStream);
-  MOZ_DIAGNOSTIC_ASSERT(!stream->mOriginalInputStream);
+  MOZ_DIAGNOSTIC_ASSERT(mInputStream);
+  MOZ_DIAGNOSTIC_ASSERT(!mOriginalInputStream);
 
-  nsresult rv =
-    stream->mInputStream->AsyncWait(stream, 0, 0,
-                                    stream->mOwningEventTarget);
+  nsresult rv = mInputStream->AsyncWait(this, 0, 0, mOwningEventTarget);
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    stream->ErrorPropagation(aCx, lock, aStream, rv);
+    ErrorPropagation(aCx, lock, aStream, rv);
     return;
   }
 
   // All good.
 }
 
-/* static */ void
-FetchStream::WriteIntoReadRequestCallback(JSContext* aCx,
-                                          JS::HandleObject aStream,
-                                          void* aUnderlyingSource,
-                                          void* aBuffer, size_t aLength,
-                                          size_t* aByteWritten)
+void
+FetchStream::writeIntoReadRequestBuffer(JSContext* aCx,
+                                        JS::HandleObject aStream,
+                                        void* aBuffer, size_t aLength,
+                                        size_t* aByteWritten)
 {
-  MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
   MOZ_DIAGNOSTIC_ASSERT(aBuffer);
   MOZ_DIAGNOSTIC_ASSERT(aByteWritten);
 
-  RefPtr<FetchStream> stream = static_cast<FetchStream*>(aUnderlyingSource);
-  stream->AssertIsOnOwningThread();
+  AssertIsOnOwningThread();
+
+  MutexAutoLock lock(mMutex);
 
-  MutexAutoLock lock(stream->mMutex);
-
-  MOZ_DIAGNOSTIC_ASSERT(stream->mInputStream);
-  MOZ_DIAGNOSTIC_ASSERT(stream->mState == eWriting);
-  stream->mState = eChecking;
+  MOZ_DIAGNOSTIC_ASSERT(mInputStream);
+  MOZ_DIAGNOSTIC_ASSERT(mState == eWriting);
+  mState = eChecking;
 
   uint32_t written;
   nsresult rv =
-    stream->mInputStream->Read(static_cast<char*>(aBuffer), aLength, &written);
+    mInputStream->Read(static_cast<char*>(aBuffer), aLength, &written);
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    stream->ErrorPropagation(aCx, lock, aStream, rv);
+    ErrorPropagation(aCx, lock, aStream, rv);
     return;
   }
 
   *aByteWritten = written;
 
   if (written == 0) {
-    stream->CloseAndReleaseObjects(aCx, lock, aStream);
+    CloseAndReleaseObjects(aCx, lock, aStream);
     return;
   }
 
-  rv = stream->mInputStream->AsyncWait(stream, 0, 0,
-                                       stream->mOwningEventTarget);
+  rv = mInputStream->AsyncWait(this, 0, 0, mOwningEventTarget);
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    stream->ErrorPropagation(aCx, lock, aStream, rv);
+    ErrorPropagation(aCx, lock, aStream, rv);
     return;
   }
 
   // All good.
 }
 
-/* static */ JS::Value
-FetchStream::CancelCallback(JSContext* aCx, JS::HandleObject aStream,
-                            void* aUnderlyingSource, JS::HandleValue aReason)
+JS::Value
+FetchStream::cancel(JSContext* aCx, JS::HandleObject aStream, JS::HandleValue aReason)
 {
-  MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
+  AssertIsOnOwningThread();
 
-  // This is safe because we created an extra reference in FetchStream::Create()
-  // that won't be released until FetchStream::FinalizeCallback() is called.
-  // We are guaranteed that won't happen until the js ReadableStream object
-  // is finalized.
-  FetchStream* stream = static_cast<FetchStream*>(aUnderlyingSource);
-  stream->AssertIsOnOwningThread();
-
-  if (stream->mState == eInitializing) {
+  if (mState == eInitializing) {
     // The stream has been used for the first time.
-    stream->mStreamHolder->MarkAsRead();
+    mStreamHolder->MarkAsRead();
   }
 
-  if (stream->mInputStream) {
-    stream->mInputStream->CloseWithStatus(NS_BASE_STREAM_CLOSED);
+  if (mInputStream) {
+    mInputStream->CloseWithStatus(NS_BASE_STREAM_CLOSED);
   }
 
   // It could be that we don't have mInputStream yet, but we still have the
   // original stream. We need to close that too.
-  if (stream->mOriginalInputStream) {
-    MOZ_ASSERT(!stream->mInputStream);
-    stream->mOriginalInputStream->Close();
+  if (mOriginalInputStream) {
+    MOZ_ASSERT(!mInputStream);
+    mOriginalInputStream->Close();
   }
 
-  stream->ReleaseObjects();
+  ReleaseObjects();
   return JS::UndefinedValue();
 }
 
-/* static */ void
-FetchStream::ClosedCallback(JSContext* aCx, JS::HandleObject aStream,
-                            void* aUnderlyingSource)
-{
-  MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
-}
-
-/* static */ void
-FetchStream::ErroredCallback(JSContext* aCx, JS::HandleObject aStream,
-                             void* aUnderlyingSource, JS::HandleValue aReason)
+void
+FetchStream::onClosed(JSContext* aCx, JS::HandleObject aStream)
 {
-  MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
-
-  // This is safe because we created an extra reference in FetchStream::Create()
-  // that won't be released until FetchStream::FinalizeCallback() is called.
-  // We are guaranteed that won't happen until the js ReadableStream object
-  // is finalized.
-  FetchStream* stream = static_cast<FetchStream*>(aUnderlyingSource);
-  stream->AssertIsOnOwningThread();
-
-  if (stream->mState == eInitializing) {
-    // The stream has been used for the first time.
-    stream->mStreamHolder->MarkAsRead();
-  }
-
-  if (stream->mInputStream) {
-    stream->mInputStream->CloseWithStatus(NS_BASE_STREAM_CLOSED);
-  }
-
-  stream->ReleaseObjects();
 }
 
 void
-FetchStream::FinalizeCallback(void* aUnderlyingSource)
+FetchStream::onErrored(JSContext* aCx, JS::HandleObject aStream, JS::HandleValue aReason)
 {
-  MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
+  AssertIsOnOwningThread();
+
+  if (mState == eInitializing) {
+    // The stream has been used for the first time.
+    mStreamHolder->MarkAsRead();
+  }
 
+  if (mInputStream) {
+    mInputStream->CloseWithStatus(NS_BASE_STREAM_CLOSED);
+  }
+
+  ReleaseObjects();
+}
+
+void
+FetchStream::finalize()
+{
   // This can be called in any thread.
 
   // This takes ownership of the ref created in FetchStream::Create().
-  RefPtr<FetchStream> stream =
-    dont_AddRef(static_cast<FetchStream*>(aUnderlyingSource));
+  RefPtr<FetchStream> stream = dont_AddRef(this);
 
   stream->ReleaseObjects();
 }
 
 FetchStream::FetchStream(nsIGlobalObject* aGlobal,
                          FetchStreamHolder* aStreamHolder,
                          nsIInputStream* aInputStream)
   : mMutex("FetchStream::mMutex")
@@ -428,17 +388,17 @@ FetchStream::OnInputStreamReady(nsIAsync
 
   // The WriteInto callback changes mState to eChecking.
   MOZ_DIAGNOSTIC_ASSERT(mState == eChecking);
 
   return NS_OK;
 }
 
 /* static */ nsresult
-FetchStream::RetrieveInputStream(void* aUnderlyingReadableStreamSource,
+FetchStream::RetrieveInputStream(JS::ReadableStreamUnderlyingSource* aUnderlyingReadableStreamSource,
                                  nsIInputStream** aInputStream)
 {
   MOZ_ASSERT(aUnderlyingReadableStreamSource);
   MOZ_ASSERT(aInputStream);
 
   RefPtr<FetchStream> stream =
     static_cast<FetchStream*>(aUnderlyingReadableStreamSource);
   stream->AssertIsOnOwningThread();
--- a/dom/fetch/FetchStream.h
+++ b/dom/fetch/FetchStream.h
@@ -23,71 +23,68 @@ namespace mozilla {
 namespace dom {
 
 class FetchStreamHolder;
 class WeakWorkerRef;
 
 class FetchStream final : public nsIInputStreamCallback
                         , public nsIObserver
                         , public nsSupportsWeakReference
+                        , private JS::ReadableStreamUnderlyingSource
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIINPUTSTREAMCALLBACK
   NS_DECL_NSIOBSERVER
 
   static void
   Create(JSContext* aCx, FetchStreamHolder* aStreamHolder,
          nsIGlobalObject* aGlobal, nsIInputStream* aInputStream,
          JS::MutableHandle<JSObject*> aStream, ErrorResult& aRv);
 
   void
   Close();
 
   static nsresult
-  RetrieveInputStream(void* aUnderlyingReadableStreamSource,
+  RetrieveInputStream(JS::ReadableStreamUnderlyingSource* aUnderlyingReadableStreamSource,
                       nsIInputStream** aInputStream);
 
 private:
   FetchStream(nsIGlobalObject* aGlobal, FetchStreamHolder* aStreamHolder,
               nsIInputStream* aInputStream);
   ~FetchStream();
 
 #ifdef DEBUG
   void
   AssertIsOnOwningThread();
 #else
   void
   AssertIsOnOwningThread() {}
 #endif
 
-  static void
-  RequestDataCallback(JSContext* aCx, JS::HandleObject aStream,
-                      void* aUnderlyingSource, size_t aDesiredSize);
+  void
+  requestData(JSContext* aCx, JS::HandleObject aStream, size_t aDesiredSize) override;
 
-  static void
-  WriteIntoReadRequestCallback(JSContext* aCx, JS::HandleObject aStream,
-                               void* aUnderlyingSource,
-                               void* aBuffer, size_t aLength,
-                               size_t* aByteWritten);
+  void
+  writeIntoReadRequestBuffer(JSContext* aCx, JS::HandleObject aStream,
+                             void* aBuffer, size_t aLength,
+                             size_t* aBytesWritten) override;
 
-  static JS::Value
-  CancelCallback(JSContext* aCx, JS::HandleObject aStream,
-                 void* aUnderlyingSource, JS::HandleValue aReason);
+  JS::Value
+  cancel(JSContext* aCx, JS::HandleObject aStream, JS::HandleValue aReason) override;
+
+  void
+  onClosed(JSContext* aCx, JS::HandleObject aStream) override;
 
-  static void
-  ClosedCallback(JSContext* aCx, JS::HandleObject aStream,
-                 void* aUnderlyingSource);
+  void
+  onErrored(JSContext* aCx, JS::HandleObject aStream, JS::HandleValue aReason) override;
 
-  static void
-  ErroredCallback(JSContext* aCx, JS::HandleObject aStream,
-                  void* aUnderlyingSource, JS::HandleValue reason);
+  void
+  finalize() override;
 
-  static void
-  FinalizeCallback(void* aUnderlyingSource);
 
   void
   ErrorPropagation(JSContext* aCx,
                    const MutexAutoLock& aProofOfLock,
                    JS::HandleObject aStream, nsresult aRv);
 
   void
   CloseAndReleaseObjects(JSContext* aCx,
--- a/dom/fetch/Response.cpp
+++ b/dom/fetch/Response.cpp
@@ -263,17 +263,17 @@ Response::Constructor(const GlobalObject
       JS::ReadableStreamMode streamMode;
       if (!JS::ReadableStreamGetMode(cx, readableStreamObj, &streamMode)) {
         aRv.StealExceptionFromJSContext(cx);
         return nullptr;
       }
       if (streamMode == JS::ReadableStreamMode::ExternalSource) {
         // If this is a DOM generated ReadableStream, we can extract the
         // inputStream directly.
-        void* underlyingSource = nullptr;
+        JS::ReadableStreamUnderlyingSource* underlyingSource = nullptr;
         if (!JS::ReadableStreamGetExternalUnderlyingSource(cx,
                                                            readableStreamObj,
                                                            &underlyingSource)) {
           aRv.StealExceptionFromJSContext(cx);
           return nullptr;
         }
 
         MOZ_ASSERT(underlyingSource);
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -85,24 +85,22 @@ void DOMMediaStream::TrackPort::DestroyI
 MediaStream* DOMMediaStream::TrackPort::GetSource() const {
   return mInputPort ? mInputPort->GetSource() : nullptr;
 }
 
 TrackID DOMMediaStream::TrackPort::GetSourceTrackId() const {
   return mInputPort ? mInputPort->GetSourceTrackId() : TRACK_INVALID;
 }
 
-already_AddRefed<Pledge<bool>> DOMMediaStream::TrackPort::BlockSourceTrackId(
+RefPtr<GenericPromise> DOMMediaStream::TrackPort::BlockSourceTrackId(
     TrackID aTrackId, BlockingMode aBlockingMode) {
-  if (mInputPort) {
-    return mInputPort->BlockSourceTrackId(aTrackId, aBlockingMode);
+  if (!mInputPort) {
+    return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   }
-  auto rejected = MakeRefPtr<Pledge<bool>>();
-  rejected->Reject(NS_ERROR_FAILURE);
-  return rejected.forget();
+  return mInputPort->BlockSourceTrackId(aTrackId, aBlockingMode);
 }
 
 NS_IMPL_CYCLE_COLLECTION(DOMMediaStream::TrackPort, mTrack)
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(DOMMediaStream::TrackPort, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(DOMMediaStream::TrackPort, Release)
 
 class DOMMediaStream::PlaybackTrackListener : public MediaStreamTrackConsumer {
  public:
@@ -823,19 +821,18 @@ already_AddRefed<MediaStreamTrack> DOMMe
   newTrack->SetEnabled(aTrack.Enabled());
   newTrack->SetMuted(aTrack.Muted());
   newTrack->SetReadyState(aTrack.ReadyState());
 
   if (aTrack.Ended()) {
     // For extra suspenders, make sure that we don't forward data by mistake
     // to the clone when the original has already ended.
     // We only block END_EXISTING to allow any pending clones to end.
-    RefPtr<Pledge<bool, nsresult>> blockingPledge =
-        inputPort->BlockSourceTrackId(inputTrackID, BlockingMode::END_EXISTING);
-    Unused << blockingPledge;
+    Unused << inputPort->BlockSourceTrackId(inputTrackID,
+                                            BlockingMode::END_EXISTING);
   }
 
   return newTrack.forget();
 }
 
 static DOMMediaStream::TrackPort* FindTrackPortAmongTracks(
     const MediaStreamTrack& aTrack,
     const nsTArray<RefPtr<DOMMediaStream::TrackPort>>& aTracks) {
@@ -1037,23 +1034,24 @@ nsresult DOMMediaStream::DispatchTrackEv
       MediaStreamTrackEvent::Constructor(this, aName, init);
 
   return DispatchTrustedEvent(event);
 }
 
 void DOMMediaStream::BlockPlaybackTrack(TrackPort* aTrack) {
   MOZ_ASSERT(aTrack);
   ++mTracksPendingRemoval;
-  RefPtr<Pledge<bool>> p = aTrack->BlockSourceTrackId(
-      aTrack->GetTrack()->mTrackID, BlockingMode::CREATION);
-  RefPtr<DOMMediaStream> self = this;
-  p->Then([self](const bool& aIgnore) { self->NotifyPlaybackTrackBlocked(); },
-          [](const nsresult& aIgnore) {
-            NS_ERROR("Could not remove track from MSG");
-          });
+  RefPtr<DOMMediaStream> that = this;
+  aTrack
+      ->BlockSourceTrackId(aTrack->GetTrack()->mTrackID, BlockingMode::CREATION)
+      ->Then(GetCurrentThreadSerialEventTarget(), __func__,
+             [this, that](bool aIgnore) { NotifyPlaybackTrackBlocked(); },
+             [](const nsresult& aIgnore) {
+               NS_ERROR("Could not remove track from MSG");
+             });
 }
 
 void DOMMediaStream::NotifyPlaybackTrackBlocked() {
   MOZ_ASSERT(mTracksPendingRemoval > 0,
              "A track reported finished blocking more times than we asked for");
   if (--mTracksPendingRemoval == 0) {
     // The MediaStreamGraph has reported a track was blocked and we are not
     // waiting for any further tracks to get blocked. It is now safe to
--- a/dom/media/DOMMediaStream.h
+++ b/dom/media/DOMMediaStream.h
@@ -44,21 +44,16 @@ class VideoTrackList;
 class MediaTrackListListener;
 }  // namespace dom
 
 namespace layers {
 class ImageContainer;
 class OverlayImage;
 }  // namespace layers
 
-namespace media {
-template <typename V, typename E>
-class Pledge;
-}  // namespace media
-
 #define NS_DOMMEDIASTREAM_IID                        \
   {                                                  \
     0x8cb65468, 0x66c0, 0x444e, {                    \
       0x89, 0x9f, 0x89, 0x1d, 0x9e, 0xd2, 0xbe, 0x7c \
     }                                                \
   }
 
 /**
@@ -271,21 +266,21 @@ class DOMMediaStream
      */
     TrackID GetSourceTrackId() const;
 
     MediaInputPort* GetInputPort() const { return mInputPort; }
     MediaStreamTrack* GetTrack() const { return mTrack; }
 
     /**
      * Blocks aTrackId from going into mInputPort unless the port has been
-     * destroyed. Returns a pledge that gets resolved when the MediaStreamGraph
+     * destroyed. Returns a promise that gets resolved when the MediaStreamGraph
      * has applied the block in the playback stream.
      */
-    already_AddRefed<media::Pledge<bool, nsresult>> BlockSourceTrackId(
-        TrackID aTrackId, BlockingMode aBlockingMode);
+    RefPtr<GenericPromise> BlockSourceTrackId(TrackID aTrackId,
+                                              BlockingMode aBlockingMode);
 
    private:
     RefPtr<MediaInputPort> mInputPort;
     RefPtr<MediaStreamTrack> mTrack;
 
     // Defines if we've been given ownership of the input port or if it's owned
     // externally. The owner is responsible for destroying the port.
     const InputPortOwnership mOwnership;
--- a/dom/media/MediaDevices.cpp
+++ b/dom/media/MediaDevices.cpp
@@ -43,162 +43,94 @@ class FuzzTimerCallBack final : public n
   }
 
  private:
   nsCOMPtr<MediaDevices> mMediaDevices;
 };
 
 NS_IMPL_ISUPPORTS(FuzzTimerCallBack, nsITimerCallback, nsINamed)
 
-class MediaDevices::GumResolver : public nsIDOMGetUserMediaSuccessCallback {
- public:
-  NS_DECL_ISUPPORTS
-
-  explicit GumResolver(Promise* aPromise) : mPromise(aPromise) {}
-
-  NS_IMETHOD
-  OnSuccess(nsISupports* aStream) override {
-    RefPtr<DOMMediaStream> stream = do_QueryObject(aStream);
-    if (!stream) {
-      return NS_ERROR_FAILURE;
-    }
-    mPromise->MaybeResolve(stream);
-    return NS_OK;
-  }
-
- private:
-  virtual ~GumResolver() {}
-  RefPtr<Promise> mPromise;
-};
-
-class MediaDevices::EnumDevResolver
-    : public nsIGetUserMediaDevicesSuccessCallback {
- public:
-  NS_DECL_ISUPPORTS
-
-  EnumDevResolver(Promise* aPromise, uint64_t aWindowId)
-      : mPromise(aPromise), mWindowId(aWindowId) {}
-
-  NS_IMETHOD
-  OnSuccess(nsIVariant* aDevices) override {
-    // Create array for nsIMediaDevice
-    nsTArray<nsCOMPtr<nsIMediaDevice>> devices;
-    // Contain the fumes
-    {
-      uint16_t vtype = aDevices->GetDataType();
-      if (vtype != nsIDataType::VTYPE_EMPTY_ARRAY) {
-        nsIID elementIID;
-        uint16_t elementType;
-        void* rawArray;
-        uint32_t arrayLen;
-        nsresult rv = aDevices->GetAsArray(&elementType, &elementIID, &arrayLen,
-                                           &rawArray);
-        NS_ENSURE_SUCCESS(rv, rv);
-        if (elementType != nsIDataType::VTYPE_INTERFACE) {
-          free(rawArray);
-          return NS_ERROR_FAILURE;
-        }
-
-        nsISupports** supportsArray = reinterpret_cast<nsISupports**>(rawArray);
-        for (uint32_t i = 0; i < arrayLen; ++i) {
-          nsCOMPtr<nsIMediaDevice> device(do_QueryInterface(supportsArray[i]));
-          devices.AppendElement(device);
-          NS_IF_RELEASE(
-              supportsArray[i]);  // explicitly decrease refcount for rawptr
-        }
-        free(rawArray);  // explicitly free memory from nsIVariant::GetAsArray
-      }
-    }
-    nsTArray<RefPtr<MediaDeviceInfo>> infos;
-    for (auto& device : devices) {
-      MediaDeviceKind kind = static_cast<MediaDevice*>(device.get())->mKind;
-      MOZ_ASSERT(kind == dom::MediaDeviceKind::Audioinput ||
-                 kind == dom::MediaDeviceKind::Videoinput ||
-                 kind == dom::MediaDeviceKind::Audiooutput);
-      nsString id;
-      nsString name;
-      device->GetId(id);
-      // Include name only if page currently has a gUM stream active or
-      // persistent permissions (audio or video) have been granted
-      if (MediaManager::Get()->IsActivelyCapturingOrHasAPermission(mWindowId) ||
-          Preferences::GetBool("media.navigator.permission.disabled", false)) {
-        device->GetName(name);
-      }
-      RefPtr<MediaDeviceInfo> info = new MediaDeviceInfo(id, kind, name);
-      infos.AppendElement(info);
-    }
-    mPromise->MaybeResolve(infos);
-    return NS_OK;
-  }
-
- private:
-  virtual ~EnumDevResolver() {}
-  RefPtr<Promise> mPromise;
-  uint64_t mWindowId;
-};
-
-class MediaDevices::GumRejecter : public nsIDOMGetUserMediaErrorCallback {
- public:
-  NS_DECL_ISUPPORTS
-
-  explicit GumRejecter(Promise* aPromise) : mPromise(aPromise) {}
-
-  NS_IMETHOD
-  OnError(nsISupports* aError) override {
-    RefPtr<MediaStreamError> error = do_QueryObject(aError);
-    if (!error) {
-      return NS_ERROR_FAILURE;
-    }
-    mPromise->MaybeReject(error);
-    return NS_OK;
-  }
-
- private:
-  virtual ~GumRejecter() {}
-  RefPtr<Promise> mPromise;
-};
-
 MediaDevices::~MediaDevices() {
   MediaManager* mediamanager = MediaManager::GetIfExists();
   if (mediamanager) {
     mediamanager->RemoveDeviceChangeCallback(this);
   }
 }
 
-NS_IMPL_ISUPPORTS(MediaDevices::GumResolver, nsIDOMGetUserMediaSuccessCallback)
-NS_IMPL_ISUPPORTS(MediaDevices::EnumDevResolver,
-                  nsIGetUserMediaDevicesSuccessCallback)
-NS_IMPL_ISUPPORTS(MediaDevices::GumRejecter, nsIDOMGetUserMediaErrorCallback)
-
 already_AddRefed<Promise> MediaDevices::GetUserMedia(
     const MediaStreamConstraints& aConstraints, CallerType aCallerType,
     ErrorResult& aRv) {
   RefPtr<Promise> p = Promise::Create(GetParentObject(), aRv);
-  NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
-
-  MediaManager::GetUserMediaSuccessCallback resolver(new GumResolver(p));
-  MediaManager::GetUserMediaErrorCallback rejecter(new GumRejecter(p));
-
-  aRv = MediaManager::Get()->GetUserMedia(GetOwner(), aConstraints,
-                                          std::move(resolver),
-                                          std::move(rejecter), aCallerType);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+  RefPtr<MediaDevices> self(this);
+  MediaManager::Get()
+      ->GetUserMedia(GetOwner(), aConstraints, aCallerType)
+      ->Then(GetCurrentThreadSerialEventTarget(), __func__,
+             [this, self, p](RefPtr<DOMMediaStream>&& aStream) {
+               if (!GetWindowIfCurrent()) {
+                 return;  // Leave Promise pending after navigation by design.
+               }
+               p->MaybeResolve(std::move(aStream));
+             },
+             [this, self, p](const RefPtr<MediaMgrError>& error) {
+               nsPIDOMWindowInner* window = GetWindowIfCurrent();
+               if (!window) {
+                 return;  // Leave Promise pending after navigation by design.
+               }
+               p->MaybeReject(MakeRefPtr<MediaStreamError>(window, *error));
+             });
   return p.forget();
 }
 
 already_AddRefed<Promise> MediaDevices::EnumerateDevices(CallerType aCallerType,
                                                          ErrorResult& aRv) {
+  MOZ_ASSERT(NS_IsMainThread());
   RefPtr<Promise> p = Promise::Create(GetParentObject(), aRv);
-  NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
-
-  RefPtr<EnumDevResolver> resolver =
-      new EnumDevResolver(p, GetOwner()->WindowID());
-  RefPtr<GumRejecter> rejecter = new GumRejecter(p);
-
-  aRv = MediaManager::Get()->EnumerateDevices(GetOwner(), resolver, rejecter,
-                                              aCallerType);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+  RefPtr<MediaDevices> self(this);
+  MediaManager::Get()
+      ->EnumerateDevices(GetOwner(), aCallerType)
+      ->Then(GetCurrentThreadSerialEventTarget(), __func__,
+             [this, self,
+              p](RefPtr<MediaManager::MediaDeviceSetRefCnt>&& aDevices) {
+               nsPIDOMWindowInner* window = GetWindowIfCurrent();
+               if (!window) {
+                 return;  // Leave Promise pending after navigation by design.
+               }
+               auto windowId = window->WindowID();
+               nsTArray<RefPtr<MediaDeviceInfo>> infos;
+               for (auto& device : *aDevices) {
+                 MOZ_ASSERT(device->mKind == dom::MediaDeviceKind::Audioinput ||
+                            device->mKind == dom::MediaDeviceKind::Videoinput ||
+                            device->mKind == dom::MediaDeviceKind::Audiooutput);
+                 // Include name only if page currently has a gUM stream active
+                 // or persistent permissions (audio or video) have been granted
+                 nsString label;
+                 if (MediaManager::Get()->IsActivelyCapturingOrHasAPermission(
+                         windowId) ||
+                     Preferences::GetBool("media.navigator.permission.disabled",
+                                          false)) {
+                   label = device->mName;
+                 }
+                 infos.AppendElement(MakeRefPtr<MediaDeviceInfo>(
+                     device->mID, device->mKind, label));
+               }
+               p->MaybeResolve(std::move(infos));
+             },
+             [this, self, p](const RefPtr<MediaMgrError>& error) {
+               nsPIDOMWindowInner* window = GetWindowIfCurrent();
+               if (!window) {
+                 return;  // Leave Promise pending after navigation by design.
+               }
+               p->MaybeReject(MakeRefPtr<MediaStreamError>(window, *error));
+             });
   return p.forget();
 }
 
 NS_IMPL_ADDREF_INHERITED(MediaDevices, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(MediaDevices, DOMEventTargetHelper)
 NS_INTERFACE_MAP_BEGIN(MediaDevices)
   NS_INTERFACE_MAP_ENTRY(MediaDevices)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -212,17 +212,16 @@ using dom::MediaTrackConstraints;
 using dom::MediaTrackConstraintSet;
 using dom::OwningBooleanOrMediaTrackConstraints;
 using dom::OwningStringOrStringSequence;
 using dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters;
 using dom::Promise;
 using dom::Sequence;
 using media::NewRunnableFrom;
 using media::NewTaskFrom;
-using media::Pledge;
 using media::Refcountable;
 
 static Atomic<bool> sHasShutdown;
 
 class SourceTrackListener;
 
 struct DeviceState {
   DeviceState(const RefPtr<MediaDevice>& aDevice, bool aOffWhileDisabled,
@@ -297,28 +296,30 @@ static CaptureState CombineCaptureState(
 }
 
 static uint16_t FromCaptureState(CaptureState aState) {
   MOZ_ASSERT(aState == CaptureState::Off || aState == CaptureState::Enabled ||
              aState == CaptureState::Disabled);
   return static_cast<uint16_t>(aState);
 }
 
-static void CallOnError(MediaManager::GetUserMediaErrorCallback* aCallback,
-                        MediaStreamError& aError) {
+void MediaManager::CallOnError(
+    const MediaManager::GetUserMediaErrorCallback* aCallback,
+    MediaStreamError& aError) {
   MOZ_ASSERT(aCallback);
   if (aCallback->HasWebIDLCallback()) {
     aCallback->GetWebIDLCallback()->Call(aError);
   } else {
     aCallback->GetXPCOMCallback()->OnError(&aError);
   }
 }
 
-static void CallOnSuccess(MediaManager::GetUserMediaSuccessCallback* aCallback,
-                          DOMMediaStream& aStream) {
+void MediaManager::CallOnSuccess(
+    const MediaManager::GetUserMediaSuccessCallback* aCallback,
+    DOMMediaStream& aStream) {
   MOZ_ASSERT(aCallback);
   if (aCallback->HasWebIDLCallback()) {
     aCallback->GetWebIDLCallback()->Call(aStream);
   } else {
     aCallback->GetXPCOMCallback()->OnSuccess(&aStream);
   }
 }
 
@@ -329,20 +330,18 @@ static void CallOnSuccess(MediaManager::
  * don't hold a reference to it during late shutdown.
  *
  * There's also a hard reference to the SourceListener through its
  * SourceStreamListener and the MediaStreamGraph. MediaStreamGraph
  * clears this on XPCOM_WILL_SHUTDOWN, before MediaManager enters shutdown.
  */
 class SourceListener : public SupportsWeakPtr<SourceListener> {
  public:
-  typedef MozPromise<bool /* aIgnored */, Maybe<nsString>, true>
-      ApplyConstraintsPromise;
   typedef MozPromise<bool /* aIgnored */, RefPtr<MediaMgrError>, true>
-      InitPromise;
+      SourceListenerPromise;
 
   MOZ_DECLARE_WEAKREFERENCE_TYPENAME(SourceListener)
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_MAIN_THREAD_DESTRUCTION_AND_RECORDING(
       SourceListener, recordreplay::Behavior::Preserve)
 
   SourceListener();
 
   /**
@@ -354,17 +353,17 @@ class SourceListener : public SupportsWe
    * Marks this listener as active and adds itself as a listener to aStream.
    */
   void Activate(SourceMediaStream* aStream, MediaDevice* aAudioDevice,
                 MediaDevice* aVideoDevice);
 
   /**
    * Posts a task to initialize and start all associated devices.
    */
-  RefPtr<InitPromise> InitializeAsync();
+  RefPtr<SourceListenerPromise> InitializeAsync();
 
   /**
    * Stops all live tracks, finishes the associated MediaStream and cleans up.
    */
   void Stop();
 
   /**
    * Removes this SourceListener from its associated MediaStream and marks it
@@ -446,19 +445,18 @@ class SourceListener : public SupportsWe
   bool Stopped() const { return mStopped; }
 
   bool CapturingVideo() const;
 
   bool CapturingAudio() const;
 
   CaptureState CapturingSource(MediaSourceEnum aSource) const;
 
-  RefPtr<ApplyConstraintsPromise> ApplyConstraintsToTrack(
-      nsPIDOMWindowInner* aWindow, TrackID aTrackID,
-      const dom::MediaTrackConstraints& aConstraints,
+  RefPtr<SourceListenerPromise> ApplyConstraintsToTrack(
+      TrackID aTrackID, const dom::MediaTrackConstraints& aConstraints,
       dom::CallerType aCallerType);
 
   PrincipalHandle GetPrincipalHandle() const;
 
  private:
   virtual ~SourceListener() = default;
 
   /**
@@ -805,58 +803,16 @@ class GetUserMediaWindowListener {
   // The task will reset this to false. MainThread only.
   bool mChromeNotificationTaskPosted;
 
   nsTArray<RefPtr<SourceListener>> mInactiveListeners;
   nsTArray<RefPtr<SourceListener>> mActiveListeners;
 };
 
 /**
- * Send an error back to content. Do this only on the main thread.
- */
-class ErrorCallbackRunnable : public Runnable {
- public:
-  ErrorCallbackRunnable(
-      const nsMainThreadPtrHandle<MediaManager::GetUserMediaErrorCallback>&
-          aOnFailure,
-      MediaMgrError& aError, uint64_t aWindowID)
-      : Runnable("ErrorCallbackRunnable"),
-        mOnFailure(aOnFailure),
-        mError(&aError),
-        mWindowID(aWindowID),
-        mManager(MediaManager::GetInstance()) {}
-
-  NS_IMETHOD
-  Run() override {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    // Only run if the window is still active.
-    if (!(mManager->IsWindowStillActive(mWindowID))) {
-      return NS_OK;
-    }
-    // This is safe since we're on main-thread, and the windowlist can only
-    // be invalidated from the main-thread (see OnNavigation)
-    if (auto* window = nsGlobalWindowInner::GetInnerWindowWithId(mWindowID)) {
-      RefPtr<MediaStreamError> error =
-          new MediaStreamError(window->AsInner(), *mError);
-      CallOnError(mOnFailure, *error);
-    }
-    return NS_OK;
-  }
-
- private:
-  ~ErrorCallbackRunnable() override = default;
-
-  nsMainThreadPtrHandle<MediaManager::GetUserMediaErrorCallback> mOnFailure;
-  RefPtr<MediaMgrError> mError;
-  uint64_t mWindowID;
-  RefPtr<MediaManager> mManager;  // get ref to this when creating the runnable
-};
-
-/**
  * nsIMediaDevice implementation.
  */
 NS_IMPL_ISUPPORTS(MediaDevice, nsIMediaDevice)
 
 MediaDevice::MediaDevice(const RefPtr<MediaEngineSource>& aSource,
                          const nsString& aName, const nsString& aID,
                          const nsString& aRawID)
     : mSource(aSource),
@@ -1122,55 +1078,62 @@ static const MediaTrackConstraints& GetI
  * at once, we could convert everything to nsTArray<RefPtr<blah> >'s,
  * though that would complicate the constructors some.  Currently the
  * GetUserMedia spec does not allow for more than 2 streams to be obtained in
  * one call, to simplify handling of constraints.
  */
 class GetUserMediaStreamRunnable : public Runnable {
  public:
   GetUserMediaStreamRunnable(
-      const nsMainThreadPtrHandle<MediaManager::GetUserMediaSuccessCallback>&
-          aOnSuccess,
-      const nsMainThreadPtrHandle<MediaManager::GetUserMediaErrorCallback>&
-          aOnFailure,
+      MozPromiseHolder<MediaManager::StreamPromise>&& aHolder,
       uint64_t aWindowID, GetUserMediaWindowListener* aWindowListener,
       SourceListener* aSourceListener, const ipc::PrincipalInfo& aPrincipalInfo,
       const MediaStreamConstraints& aConstraints, MediaDevice* aAudioDevice,
       MediaDevice* aVideoDevice, PeerIdentity* aPeerIdentity, bool aIsChrome)
       : Runnable("GetUserMediaStreamRunnable"),
-        mOnSuccess(aOnSuccess),
-        mOnFailure(aOnFailure),
+        mHolder(std::move(aHolder)),
         mConstraints(aConstraints),
         mAudioDevice(aAudioDevice),
         mVideoDevice(aVideoDevice),
         mWindowID(aWindowID),
         mWindowListener(aWindowListener),
         mSourceListener(aSourceListener),
         mPrincipalInfo(aPrincipalInfo),
         mPeerIdentity(aPeerIdentity),
         mManager(MediaManager::GetInstance()) {}
 
-  ~GetUserMediaStreamRunnable() {}
+  ~GetUserMediaStreamRunnable() {
+    mHolder.RejectIfExists(
+        MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError),
+        __func__);
+  }
 
   class TracksCreatedListener : public MediaStreamTrackListener {
    public:
     TracksCreatedListener(
         MediaManager* aManager,
-        const nsMainThreadPtrHandle<MediaManager::GetUserMediaSuccessCallback>&
-            aSuccess,
-        GetUserMediaWindowListener* aWindowListener, DOMMediaStream* aStream,
-        MediaStreamTrack* aTrack)
+        MozPromiseHolder<MediaManager::StreamPromise>&& aHolder,
+        GetUserMediaWindowListener* aWindowListener, uint64_t aWindowID,
+        DOMMediaStream* aStream, MediaStreamTrack* aTrack)
         : mWindowListener(aWindowListener),
-          mOnSuccess(aSuccess),
+          mHolder(std::move(aHolder)),
           mManager(aManager),
+          mWindowID(aWindowID),
           mGraph(aTrack->GraphImpl()),
           mStream(new nsMainThreadPtrHolder<DOMMediaStream>(
               "TracksCreatedListener::mStream", aStream)),
           mTrack(new nsMainThreadPtrHolder<MediaStreamTrack>(
               "TracksCreatedListener::mTrack", aTrack)) {}
+
+    ~TracksCreatedListener() {
+      mHolder.RejectIfExists(
+          MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError),
+          __func__);
+    }
+
     void NotifyOutput(MediaStreamGraph* aGraph,
                       StreamTime aCurrentTrackTime) override {
       // It's enough to know that one of the tracks have output, as both tracks
       // are guaranteed to be created in the graph at this point.
 
       if (mDispatchedTracksCreated) {
         return;
       }
@@ -1182,17 +1145,17 @@ class GetUserMediaStreamRunnable : publi
 
             if (!mManager->IsWindowListenerStillActive(mWindowListener)) {
               return;
             }
 
             // This is safe since we're on main-thread, and the windowlist can
             // only be invalidated from the main-thread (see OnNavigation)
             LOG(("Returning success for getUserMedia()"));
-            CallOnSuccess(mOnSuccess, *mStream);
+            mHolder.Resolve(RefPtr<DOMMediaStream>(mStream), __func__);
           });
       // DispatchToMainThreadAfterStreamStateUpdate will make the runnable run
       // in stable state. But since the runnable runs JS we need to make a
       // double dispatch.
       mGraph->DispatchToMainThreadAfterStreamStateUpdate(NS_NewRunnableFunction(
           "TracksCreatedListener::NotifyOutput Stable State Notifier",
           [graph = mGraph, r = std::move(r)]() mutable {
             graph->Dispatch(r.forget());
@@ -1201,19 +1164,19 @@ class GetUserMediaStreamRunnable : publi
     void NotifyRemoved() override {
       mGraph->Dispatch(NS_NewRunnableFunction(
           "TracksCreatedListener::NotifyRemoved CycleBreaker",
           [self = RefPtr<TracksCreatedListener>(this)]() {
             self->mTrack->RemoveListener(self);
           }));
     }
     const RefPtr<GetUserMediaWindowListener> mWindowListener;
-    const nsMainThreadPtrHandle<MediaManager::GetUserMediaSuccessCallback>
-        mOnSuccess;
+    MozPromiseHolder<MediaManager::StreamPromise> mHolder;
     const RefPtr<MediaManager> mManager;
+    uint64_t mWindowID;
     const RefPtr<MediaStreamGraphImpl> mGraph;
     // Keep the DOMMediaStream alive until the success callback has been called,
     // otherwise we might immediately destroy the DOMMediaStream and
     // shut down the underlying MediaStream prematurely.
     // This creates a cycle which is broken when we're destroyed, i.e., either
     // when we've called the success callback and thus removed the listener from
     // the graph, or on graph shutdown.
     nsMainThreadPtrHandle<DOMMediaStream> mStream;
@@ -1281,71 +1244,28 @@ class GetUserMediaStreamRunnable : publi
               mPeerIdentity(aPeerIdentity) {}
 
         MediaSourceEnum GetMediaSource() const override { return mSource; }
 
         const PeerIdentity* GetPeerIdentity() const override {
           return mPeerIdentity;
         }
 
-        already_AddRefed<PledgeVoid> ApplyConstraints(
-            nsPIDOMWindowInner* aWindow,
-            const MediaTrackConstraints& aConstraints,
-            dom::CallerType aCallerType) override {
-          RefPtr<PledgeVoid> p = new PledgeVoid();
+        RefPtr<MediaStreamTrackSource::ApplyConstraintsPromise>
+        ApplyConstraints(const MediaTrackConstraints& aConstraints,
+                         dom::CallerType aCallerType) override {
+          MOZ_ASSERT(NS_IsMainThread());
           if (sHasShutdown || !mListener) {
             // Track has been stopped, or we are in shutdown. In either case
             // there's no observable outcome, so pretend we succeeded.
-            p->Resolve(false);
-            return p.forget();
+            return MediaStreamTrackSource::ApplyConstraintsPromise::
+                CreateAndResolve(false, __func__);
           }
-
-          mListener
-              ->ApplyConstraintsToTrack(aWindow, mTrackID, aConstraints,
-                                        aCallerType)
-              ->Then(GetMainThreadSerialEventTarget(), __func__,
-                     [p]() {
-                       if (!MediaManager::Exists()) {
-                         return;
-                       }
-
-                       p->Resolve(false);
-                     },
-                     [p, weakWindow = nsWeakPtr(do_GetWeakReference(aWindow)),
-                      listener = mListener,
-                      trackID = mTrackID](Maybe<nsString>&& aBadConstraint) {
-                       if (!MediaManager::Exists()) {
-                         return;
-                       }
-
-                       if (!weakWindow->IsAlive()) {
-                         return;
-                       }
-
-                       if (aBadConstraint.isNothing()) {
-                         // Unexpected error during reconfig that left the
-                         // source stopped. We resolve the promise and end the
-                         // track.
-                         if (listener) {
-                           listener->StopTrack(trackID);
-                         }
-                         p->Resolve(false);
-                         return;
-                       }
-
-                       nsCOMPtr<nsPIDOMWindowInner> window =
-                           do_QueryReferent(weakWindow);
-                       auto error = MakeRefPtr<MediaStreamError>(
-                           window, MediaMgrError::Name::OverconstrainedError,
-                           NS_LITERAL_STRING(""),
-                           aBadConstraint.valueOr(nsString()));
-                       p->Reject(error);
-                     });
-
-          return p.forget();
+          return mListener->ApplyConstraintsToTrack(mTrackID, aConstraints,
+                                                    aCallerType);
         }
 
         void GetSettings(dom::MediaTrackSettings& aOutSettings) override {
           if (mListener) {
             mListener->GetSettingsFor(mTrackID, aOutSettings);
           }
         }
 
@@ -1422,37 +1342,36 @@ class GetUserMediaStreamRunnable : publi
             GetInvariant(mConstraints.mVideo));
         domStream->AddTrackInternal(track);
       }
     }
 
     if (!domStream || !stream || sHasShutdown) {
       LOG(("Returning error for getUserMedia() - no stream"));
 
-      if (auto* window = nsGlobalWindowInner::GetInnerWindowWithId(mWindowID)) {
-        RefPtr<MediaStreamError> error = new MediaStreamError(
-            window->AsInner(), MediaStreamError::Name::AbortError,
-            sHasShutdown ? NS_LITERAL_STRING("In shutdown")
-                         : NS_LITERAL_STRING("No stream."));
-        CallOnError(mOnFailure, *error);
-      }
+      mHolder.Reject(MakeRefPtr<MediaMgrError>(
+                         MediaMgrError::Name::AbortError,
+                         sHasShutdown ? NS_LITERAL_STRING("In shutdown")
+                                      : NS_LITERAL_STRING("No stream.")),
+                     __func__);
       return NS_OK;
     }
 
     // Activate our source listener. We'll call Start() on the source when we
     // get a callback that the MediaStream has started consuming. The listener
     // is freed when the page is invalidated (on navigation or close).
     mWindowListener->Activate(mSourceListener, stream, mAudioDevice,
                               mVideoDevice);
 
     nsTArray<RefPtr<MediaStreamTrack>> tracks(2);
     domStream->GetTracks(tracks);
     RefPtr<MediaStreamTrack> track = tracks[0];
     auto tracksCreatedListener = MakeRefPtr<TracksCreatedListener>(
-        mManager, mOnSuccess, mWindowListener, domStream, track);
+        mManager, std::move(mHolder), mWindowListener, mWindowID, domStream,
+        track);
 
     // Dispatch to the media thread to ask it to start the sources,
     // because that can take a while.
     // Pass ownership of domStream through the lambda to the nested chrome
     // notification lambda to ensure it's kept alive until that lambda runs or
     // is discarded.
     mSourceListener->InitializeAsync()->Then(
         GetMainThreadSerialEventTarget(), __func__,
@@ -1462,35 +1381,30 @@ class GetUserMediaStreamRunnable : publi
               ("GetUserMediaStreamRunnable::Run: starting success callback "
                "following InitializeAsync()"));
           // Initiating and starting devices succeeded.
           track->AddListener(tracksCreatedListener);
           windowListener->ChromeAffectingStateChanged();
           manager->SendPendingGUMRequest();
         },
         [manager = mManager, windowID = mWindowID,
-         onFailure =
-             std::move(mOnFailure)](const RefPtr<MediaMgrError>& error) {
+         holder = std::move(mHolder)](RefPtr<MediaMgrError>&& aError) mutable {
+          MOZ_ASSERT(NS_IsMainThread());
           LOG(
               ("GetUserMediaStreamRunnable::Run: starting failure callback "
                "following InitializeAsync()"));
           // Initiating and starting devices failed.
 
           // Only run if the window is still active for our window listener.
           if (!(manager->IsWindowStillActive(windowID))) {
             return;
           }
           // This is safe since we're on main-thread, and the windowlist can
           // only be invalidated from the main-thread (see OnNavigation)
-          if (auto* window =
-                  nsGlobalWindowInner::GetInnerWindowWithId(windowID)) {
-            auto streamError =
-                MakeRefPtr<MediaStreamError>(window->AsInner(), *error);
-            CallOnError(onFailure, *streamError);
-          }
+          holder.Reject(std::move(aError), __func__);
         });
 
     if (!IsPincipalInfoPrivate(mPrincipalInfo)) {
       // Call GetPrincipalKey again, this time w/persist = true, to promote
       // deviceIds to persistent, in case they're not already. Fire'n'forget.
       media::GetPrincipalKey(mPrincipalInfo, true)
           ->Then(GetCurrentThreadSerialEventTarget(), __func__,
                  [](const PrincipalKeyPromise::ResolveOrRejectValue& aValue) {
@@ -1500,18 +1414,17 @@ class GetUserMediaStreamRunnable : publi
                           "will be broken"));
                    }
                  });
     }
     return NS_OK;
   }
 
  private:
-  nsMainThreadPtrHandle<MediaManager::GetUserMediaSuccessCallback> mOnSuccess;
-  nsMainThreadPtrHandle<MediaManager::GetUserMediaErrorCallback> mOnFailure;
+  MozPromiseHolder<MediaManager::StreamPromise> mHolder;
   MediaStreamConstraints mConstraints;
   RefPtr<MediaDevice> mAudioDevice;
   RefPtr<MediaDevice> mVideoDevice;
   uint64_t mWindowID;
   RefPtr<GetUserMediaWindowListener> mWindowListener;
   RefPtr<SourceListener> mSourceListener;
   ipc::PrincipalInfo mPrincipalInfo;
   RefPtr<PeerIdentity> mPeerIdentity;
@@ -1554,34 +1467,28 @@ static void GetMediaDevices(MediaEngine*
       for (auto& device : devices) {
         LOG(("%s: appending device=%s", __func__,
              NS_ConvertUTF16toUTF8(device->mName).get()));
       }
     }
   }
 }
 
-// TODO: Remove once upgraded to GCC 4.8+ on linux. Bogus error on static func:
-// error: 'this' was not captured for this lambda function
-
-static auto& MediaManager_ToJSArray = MediaManager::ToJSArray;
-static auto& MediaManager_AnonymizeDevices = MediaManager::AnonymizeDevices;
-
 RefPtr<MediaManager::BadConstraintsPromise> MediaManager::SelectSettings(
-    MediaStreamConstraints& aConstraints, bool aIsChrome,
+    const MediaStreamConstraints& aConstraints, bool aIsChrome,
     const RefPtr<MediaDeviceSetRefCnt>& aSources) {
   MOZ_ASSERT(NS_IsMainThread());
 
   // Algorithm accesses device capabilities code and must run on media thread.
   // Modifies passed-in aSources.
 
   return MediaManager::PostTask<BadConstraintsPromise>(
       __func__, [aConstraints, aSources, aIsChrome](
                     MozPromiseHolder<BadConstraintsPromise>& holder) mutable {
-        auto& sources = **aSources;
+        auto& sources = *aSources;
 
         // Since the advanced part of the constraints algorithm needs to know
         // when a candidate set is overconstrained (zero members), we must split
         // up the list into videos and audios, and put it back together again at
         // the end.
 
         nsTArray<RefPtr<MediaDevice>> videos;
         nsTArray<RefPtr<MediaDevice>> audios;
@@ -1628,63 +1535,62 @@ RefPtr<MediaManager::BadConstraintsPromi
  * Depending on whether a picture or stream was asked for, either
  * ProcessGetUserMedia is called, and the results are sent back to the DOM.
  *
  * Do not run this on the main thread. The success and error callbacks *MUST*
  * be dispatched on the main thread!
  */
 class GetUserMediaTask : public Runnable {
  public:
-  GetUserMediaTask(
-      const MediaStreamConstraints& aConstraints,
-      const nsMainThreadPtrHandle<MediaManager::GetUserMediaSuccessCallback>&
-          aOnSuccess,
-      const nsMainThreadPtrHandle<MediaManager::GetUserMediaErrorCallback>&
-          aOnFailure,
-      uint64_t aWindowID, GetUserMediaWindowListener* aWindowListener,
-      SourceListener* aSourceListener, MediaEnginePrefs& aPrefs,
-      const ipc::PrincipalInfo& aPrincipalInfo, bool aIsChrome,
-      MediaManager::MediaDeviceSet* aMediaDeviceSet, bool aShouldFocusSource)
+  GetUserMediaTask(const MediaStreamConstraints& aConstraints,
+                   MozPromiseHolder<MediaManager::StreamPromise>&& aHolder,
+                   uint64_t aWindowID,
+                   GetUserMediaWindowListener* aWindowListener,
+                   SourceListener* aSourceListener,
+                   const MediaEnginePrefs& aPrefs,
+                   const ipc::PrincipalInfo& aPrincipalInfo, bool aIsChrome,
+                   RefPtr<MediaManager::MediaDeviceSetRefCnt>&& aMediaDeviceSet,
+                   bool aShouldFocusSource)
       : Runnable("GetUserMediaTask"),
         mConstraints(aConstraints),
-        mOnSuccess(aOnSuccess),
-        mOnFailure(aOnFailure),
+        mHolder(std::move(aHolder)),
         mWindowID(aWindowID),
         mWindowListener(aWindowListener),
         mSourceListener(aSourceListener),
         mPrefs(aPrefs),
         mPrincipalInfo(aPrincipalInfo),
         mIsChrome(aIsChrome),
         mShouldFocusSource(aShouldFocusSource),
         mDeviceChosen(false),
         mMediaDeviceSet(aMediaDeviceSet),
         mManager(MediaManager::GetInstance()) {}
 
-  ~GetUserMediaTask() {}
-
-  void Fail(MediaMgrError::Name aName,
-            const nsAString& aMessage = EmptyString(),
-            const nsAString& aConstraint = EmptyString()) {
-    RefPtr<MediaMgrError> error =
-        new MediaMgrError(aName, aMessage, aConstraint);
-    auto errorRunnable =
-        MakeRefPtr<ErrorCallbackRunnable>(mOnFailure, *error, mWindowID);
-
-    NS_DispatchToMainThread(errorRunnable.forget());
-    // Do after ErrorCallbackRunnable Run()s, as it checks active window list
+  ~GetUserMediaTask() {
+    if (!mHolder.IsEmpty()) {
+      Fail(MediaMgrError::Name::NotAllowedError);
+    }
+  }
+
+  void Fail(MediaMgrError::Name aName, const nsString& aMessage = EmptyString(),
+            const nsString& aConstraint = EmptyString()) {
+    NS_DispatchToMainThread(NS_NewRunnableFunction(
+        "GetUserMediaTask::Fail",
+        [aName, aMessage, aConstraint, holder = std::move(mHolder)]() mutable {
+          holder.Reject(MakeRefPtr<MediaMgrError>(aName, aMessage, aConstraint),
+                        __func__);
+        }));
+    // Do after the above runs, as it checks active window list
     NS_DispatchToMainThread(NewRunnableMethod<RefPtr<SourceListener>>(
         "GetUserMediaWindowListener::Remove", mWindowListener,
         &GetUserMediaWindowListener::Remove, mSourceListener));
   }
 
   NS_IMETHOD
   Run() override {
     MOZ_ASSERT(!NS_IsMainThread());
-    MOZ_ASSERT(mOnSuccess);
-    MOZ_ASSERT(mOnFailure);
     MOZ_ASSERT(mDeviceChosen);
     LOG(("GetUserMediaTask::Run()"));
 
     // Allocate a video or audio device and return a MediaStream via
     // a GetUserMediaStreamRunnable.
 
     nsresult rv;
     const char* errorMsg = nullptr;
@@ -1751,42 +1657,34 @@ class GetUserMediaTask : public Runnable
       return NS_OK;
     }
     PeerIdentity* peerIdentity = nullptr;
     if (!mConstraints.mPeerIdentity.IsEmpty()) {
       peerIdentity = new PeerIdentity(mConstraints.mPeerIdentity);
     }
 
     NS_DispatchToMainThread(do_AddRef(new GetUserMediaStreamRunnable(
-        mOnSuccess, mOnFailure, mWindowID, mWindowListener, mSourceListener,
+        std::move(mHolder), mWindowID, mWindowListener, mSourceListener,
         mPrincipalInfo, mConstraints, mAudioDevice, mVideoDevice, peerIdentity,
         mIsChrome)));
     return NS_OK;
   }
 
   nsresult Denied(MediaMgrError::Name aName,
-                  const nsAString& aMessage = EmptyString()) {
-    MOZ_ASSERT(mOnSuccess);
-    MOZ_ASSERT(mOnFailure);
-
+                  const nsString& aMessage = EmptyString()) {
     // We add a disabled listener to the StreamListeners array until accepted
     // If this was the only active MediaStream, remove the window from the list.
     if (NS_IsMainThread()) {
-      if (auto* window = nsGlobalWindowInner::GetInnerWindowWithId(mWindowID)) {
-        RefPtr<MediaStreamError> error =
-            new MediaStreamError(window->AsInner(), aName, aMessage);
-        CallOnError(mOnFailure, *error);
-      }
+      mHolder.Reject(MakeRefPtr<MediaMgrError>(aName, aMessage), __func__);
       // Should happen *after* error runs for consistency, but may not matter
       mWindowListener->Remove(mSourceListener);
     } else {
       // This will re-check the window being alive on main-thread
       Fail(aName, aMessage);
     }
-
     return NS_OK;
   }
 
   nsresult SetContraints(const MediaStreamConstraints& aConstraints) {
     mConstraints = aConstraints;
     return NS_OK;
   }
 
@@ -1804,32 +1702,31 @@ class GetUserMediaTask : public Runnable
     return NS_OK;
   }
 
   uint64_t GetWindowID() { return mWindowID; }
 
  private:
   MediaStreamConstraints mConstraints;
 
-  nsMainThreadPtrHandle<MediaManager::GetUserMediaSuccessCallback> mOnSuccess;
-  nsMainThreadPtrHandle<MediaManager::GetUserMediaErrorCallback> mOnFailure;
+  MozPromiseHolder<MediaManager::StreamPromise> mHolder;
   uint64_t mWindowID;
   RefPtr<GetUserMediaWindowListener> mWindowListener;
   RefPtr<SourceListener> mSourceListener;
   RefPtr<MediaDevice> mAudioDevice;
   RefPtr<MediaDevice> mVideoDevice;
-  MediaEnginePrefs mPrefs;
+  const MediaEnginePrefs mPrefs;
   ipc::PrincipalInfo mPrincipalInfo;
   bool mIsChrome;
   bool mShouldFocusSource;
 
   bool mDeviceChosen;
 
  public:
-  nsAutoPtr<MediaManager::MediaDeviceSet> mMediaDeviceSet;
+  RefPtr<MediaManager::MediaDeviceSetRefCnt> mMediaDeviceSet;
 
  private:
   RefPtr<MediaManager> mManager;  // get ref to this when creating the runnable
 };
 
 #if defined(ANDROID)
 class GetUserMediaRunnableWrapper : public Runnable {
  public:
@@ -1930,46 +1827,46 @@ RefPtr<MediaManager::MediaDeviceSetPromi
       fakeBackend = new MediaEngineDefault();
     }
     if (realDeviceRequested) {
       MediaManager* manager = MediaManager::GetIfExists();
       MOZ_RELEASE_ASSERT(manager);  // Must exist while media thread is alive
       realBackend = manager->GetBackend(aWindowId);
     }
 
-    auto result = MakeRefPtr<MediaDeviceSetRefCnt>(new MediaDeviceSet);
+    auto result = MakeRefPtr<MediaDeviceSetRefCnt>();
 
     if (hasVideo) {
       MediaDeviceSet videos;
       LOG(("EnumerateRawDevices Task: Getting video sources with %s backend",
            aVideoInputEnumType == DeviceEnumerationType::Fake ? "fake"
                                                               : "real"));
       GetMediaDevices(aVideoInputEnumType == DeviceEnumerationType::Fake
                           ? fakeBackend
                           : realBackend,
                       aWindowId, aVideoInputType, videos, videoLoopDev.get());
-      (*result)->AppendElements(videos);
+      result->AppendElements(videos);
     }
     if (hasAudio) {
       MediaDeviceSet audios;
       LOG(("EnumerateRawDevices Task: Getting audio sources with %s backend",
            aAudioInputEnumType == DeviceEnumerationType::Fake ? "fake"
                                                               : "real"));
       GetMediaDevices(aAudioInputEnumType == DeviceEnumerationType::Fake
                           ? fakeBackend
                           : realBackend,
                       aWindowId, aAudioInputType, audios, audioLoopDev.get());
-      (*result)->AppendElements(audios);
+      result->AppendElements(audios);
     }
     if (hasAudioOutput) {
       MediaDeviceSet outputs;
       MOZ_ASSERT(realBackend);
       realBackend->EnumerateDevices(aWindowId, MediaSourceEnum::Other,
                                     MediaSinkEnum::Speaker, &outputs);
-      (*result)->AppendElements(outputs);
+      result->AppendElements(outputs);
     }
 
     holder->Resolve(std::move(result), __func__);
   });
 
   if (realDeviceRequested &&
       Preferences::GetBool("media.navigator.permission.device", false)) {
     // Need to ask permission to retrieve list of all devices;
@@ -2270,77 +2167,77 @@ int MediaManager::AddDeviceChangeCallbac
       manager->GetBackend(0)->SetFakeDeviceChangeEvents();
   }));
 
   return DeviceChangeCallback::AddDeviceChangeCallback(aCallback);
 }
 
 void MediaManager::OnDeviceChange() {
   RefPtr<MediaManager> self(this);
-  NS_DispatchToMainThread(media::NewRunnableFrom([self]() mutable {
-    MOZ_ASSERT(NS_IsMainThread());
-    if (sHasShutdown) {
-      return NS_OK;
-    }
-    self->DeviceChangeCallback::OnDeviceChange();
-
-    // On some Windows machine, if we call EnumerateRawDevices immediately after
-    // receiving devicechange event, sometimes we would get outdated devices
-    // list.
-    PR_Sleep(PR_MillisecondsToInterval(100));
-    RefPtr<MediaDeviceSetPromise> p = self->EnumerateRawDevices(
-        0, MediaSourceEnum::Camera, MediaSourceEnum::Microphone,
-        MediaSinkEnum::Speaker);
-    p->Then(GetCurrentThreadSerialEventTarget(), __func__,
-            [self](RefPtr<MediaDeviceSetRefCnt>&& aDevices) mutable {
-              MediaManager* mgr = MediaManager::GetIfExists();
-              if (!mgr) {
-                return;
-              }
-
-              nsTArray<nsString> deviceIDs;
-
-              for (auto& device : **aDevices) {
-                nsString id;
-                device->GetId(id);
-                id.ReplaceSubstring(NS_LITERAL_STRING("default: "),
-                                    NS_LITERAL_STRING(""));
-                if (!deviceIDs.Contains(id)) {
-                  deviceIDs.AppendElement(id);
-                }
-              }
-
-              for (auto& id : self->mDeviceIDs) {
-                if (deviceIDs.Contains(id)) {
-                  continue;
-                }
-
-                // Stop the coresponding SourceListener
-                nsGlobalWindowInner::InnerWindowByIdTable* windowsById =
-                    nsGlobalWindowInner::GetWindowsTable();
-                if (!windowsById) {
-                  continue;
-                }
-
-                for (auto iter = windowsById->Iter(); !iter.Done();
-                     iter.Next()) {
-                  nsGlobalWindowInner* window = iter.Data();
-                  self->IterateWindowListeners(
-                      window->AsInner(),
-                      [&id](GetUserMediaWindowListener* aListener) {
-                        aListener->StopRawID(id);
-                      });
-                }
-              }
-
-              self->mDeviceIDs = deviceIDs;
-            },
-            [](RefPtr<MediaStreamError>&& reason) {});
-    return NS_OK;
-  }));
+  NS_DispatchToMainThread(
+      NS_NewRunnableFunction("MediaManager::OnDeviceChange", [self]() {
+        MOZ_ASSERT(NS_IsMainThread());
+        if (sHasShutdown) {
+          return;
+        }
+        self->DeviceChangeCallback::OnDeviceChange();
+
+        // On some Windows machine, if we call EnumerateRawDevices immediately
+        // after receiving devicechange event, sometimes we would get outdated
+        // devices list.
+        PR_Sleep(PR_MillisecondsToInterval(100));
+        self->EnumerateRawDevices(0, MediaSourceEnum::Camera,
+                                  MediaSourceEnum::Microphone,
+                                  MediaSinkEnum::Speaker)
+            ->Then(GetCurrentThreadSerialEventTarget(), __func__,
+                   [self](RefPtr<MediaDeviceSetRefCnt>&& aDevices) {
+                     MediaManager* mgr = MediaManager::GetIfExists();
+                     if (!mgr) {
+                       return;
+                     }
+
+                     nsTArray<nsString> deviceIDs;
+
+                     for (auto& device : *aDevices) {
+                       nsString id;
+                       device->GetId(id);
+                       id.ReplaceSubstring(NS_LITERAL_STRING("default: "),
+                                           NS_LITERAL_STRING(""));
+                       if (!deviceIDs.Contains(id)) {
+                         deviceIDs.AppendElement(id);
+                       }
+                     }
+
+                     for (auto& id : self->mDeviceIDs) {
+                       if (deviceIDs.Contains(id)) {
+                         continue;
+                       }
+
+                       // Stop the coresponding SourceListener
+                       nsGlobalWindowInner::InnerWindowByIdTable* windowsById =
+                           nsGlobalWindowInner::GetWindowsTable();
+                       if (!windowsById) {
+                         continue;
+                       }
+
+                       for (auto iter = windowsById->Iter(); !iter.Done();
+                            iter.Next()) {
+                         nsGlobalWindowInner* window = iter.Data();
+                         self->IterateWindowListeners(
+                             window->AsInner(),
+                             [&id](GetUserMediaWindowListener* aListener) {
+                               aListener->StopRawID(id);
+                             });
+                       }
+                     }
+
+                     self->mDeviceIDs = deviceIDs;
+                   },
+                   [](RefPtr<MediaMgrError>&& reason) {});
+      }));
 }
 
 nsresult MediaManager::GenerateUUID(nsAString& aResult) {
   nsresult rv;
   nsCOMPtr<nsIUUIDGenerator> uuidgen =
       do_GetService("@mozilla.org/uuid-generator;1", &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -2415,66 +2312,56 @@ static void ReduceConstraint(
   aConstraint.SetAsMediaTrackConstraints().mMediaSource = mediaSource;
 }
 
 /**
  * The entry point for this file. A call from Navigator::mozGetUserMedia
  * will end up here. MediaManager is a singleton that is responsible
  * for handling all incoming getUserMedia calls from every window.
  */
-nsresult MediaManager::GetUserMedia(
+RefPtr<MediaManager::StreamPromise> MediaManager::GetUserMedia(
     nsPIDOMWindowInner* aWindow,
     const MediaStreamConstraints& aConstraintsPassedIn,
-    GetUserMediaSuccessCallback&& aOnSuccess,
-    GetUserMediaErrorCallback&& aOnFailure, dom::CallerType aCallerType) {
+    dom::CallerType aCallerType) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aWindow);
-  MOZ_ASSERT(aOnFailure.GetISupports());
-  MOZ_ASSERT(aOnSuccess.GetISupports());
-  nsMainThreadPtrHandle<GetUserMediaSuccessCallback> onSuccess(
-      new nsMainThreadPtrHolder<GetUserMediaSuccessCallback>(
-          "GetUserMedia::SuccessCallback", std::move(aOnSuccess)));
-  nsMainThreadPtrHandle<GetUserMediaErrorCallback> onFailure(
-      new nsMainThreadPtrHolder<GetUserMediaErrorCallback>(
-          "GetUserMedia::FailureCallback", std::move(aOnFailure)));
   uint64_t windowID = aWindow->WindowID();
 
   MediaStreamConstraints c(aConstraintsPassedIn);  // use a modifiable copy
 
   // Do all the validation we can while we're sync (to return an
   // already-rejected promise on failure).
 
   if (!IsOn(c.mVideo) && !IsOn(c.mAudio)) {
-    RefPtr<MediaStreamError> error = new MediaStreamError(
-        aWindow, MediaStreamError::Name::TypeError,
-        NS_LITERAL_STRING("audio and/or video is required"));
-    CallOnError(onFailure, *error);
-    return NS_OK;
+    return StreamPromise::CreateAndReject(
+        MakeRefPtr<MediaMgrError>(
+            MediaMgrError::Name::TypeError,
+            NS_LITERAL_STRING("audio and/or video is required")),
+        __func__);
   }
 
   if (!IsFullyActive(aWindow)) {
-    RefPtr<MediaStreamError> error = new MediaStreamError(
-        aWindow, MediaStreamError::Name::InvalidStateError);
-    CallOnError(onFailure, *error);
-    return NS_OK;
+    return StreamPromise::CreateAndReject(
+        MakeRefPtr<MediaMgrError>(MediaMgrError::Name::InvalidStateError),
+        __func__);
   }
 
   if (sHasShutdown) {
-    RefPtr<MediaStreamError> error =
-        new MediaStreamError(aWindow, MediaStreamError::Name::AbortError,
-                             NS_LITERAL_STRING("In shutdown"));
-    CallOnError(onFailure, *error);
-    return NS_OK;
+    return StreamPromise::CreateAndReject(
+        MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError,
+                                  NS_LITERAL_STRING("In shutdown")),
+        __func__);
   }
 
   // Determine permissions early (while we still have a stack).
 
   nsIURI* docURI = aWindow->GetDocumentURI();
   if (!docURI) {
-    return NS_ERROR_UNEXPECTED;
+    return StreamPromise::CreateAndReject(
+        MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError), __func__);
   }
   bool isChrome = (aCallerType == dom::CallerType::System);
   bool privileged =
       isChrome ||
       Preferences::GetBool("media.navigator.permission.disabled", false);
   bool isHTTPS = false;
   bool isHandlingUserInput = EventStateManager::IsHandlingUserInput();
   ;
@@ -2513,30 +2400,36 @@ nsresult MediaManager::GetUserMedia(
   } else {
     Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_SECURE_ORIGIN,
                           (uint32_t)GetUserMediaSecurityState::Other);
   }
 
   nsCOMPtr<nsIPrincipal> principal =
       nsGlobalWindowInner::Cast(aWindow)->GetPrincipal();
   if (NS_WARN_IF(!principal)) {
-    return NS_ERROR_FAILURE;
+    return StreamPromise::CreateAndReject(
+        MakeRefPtr<MediaMgrError>(MediaMgrError::Name::SecurityError),
+        __func__);
   }
 
   nsIDocument* doc = aWindow->GetExtantDoc();
   if (NS_WARN_IF(!doc)) {
-    return NS_ERROR_FAILURE;
+    return StreamPromise::CreateAndReject(
+        MakeRefPtr<MediaMgrError>(MediaMgrError::Name::SecurityError),
+        __func__);
   }
 
   // This principal needs to be sent to different threads and so via IPC.
   // For this reason it's better to convert it to PrincipalInfo right now.
   ipc::PrincipalInfo principalInfo;
   rv = PrincipalToPrincipalInfo(principal, &principalInfo);
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
+    return StreamPromise::CreateAndReject(
+        MakeRefPtr<MediaMgrError>(MediaMgrError::Name::SecurityError),
+        __func__);
   }
 
   const bool resistFingerprinting =
       nsContentUtils::ResistFingerprinting(aCallerType);
 
   if (resistFingerprinting) {
     ReduceConstraint(c.mVideo);
     ReduceConstraint(c.mAudio);
@@ -2574,31 +2467,30 @@ nsresult MediaManager::GetUserMedia(
         // Deny screensharing request if support is disabled, or
         // the requesting document is not from a host on the whitelist.
         if (!Preferences::GetBool(
                 ((videoType == MediaSourceEnum::Browser)
                      ? "media.getusermedia.browser.enabled"
                      : "media.getusermedia.screensharing.enabled"),
                 false) ||
             (!privileged && !aWindow->IsSecureContext())) {
-          RefPtr<MediaStreamError> error = new MediaStreamError(
-              aWindow, MediaStreamError::Name::NotAllowedError);
-          CallOnError(onFailure, *error);
-          return NS_OK;
+          return StreamPromise::CreateAndReject(
+              MakeRefPtr<MediaMgrError>(MediaMgrError::Name::NotAllowedError),
+              __func__);
         }
         break;
 
       case MediaSourceEnum::Microphone:
       case MediaSourceEnum::Other:
       default: {
-        RefPtr<MediaStreamError> error = new MediaStreamError(
-            aWindow, MediaStreamError::Name::OverconstrainedError,
-            NS_LITERAL_STRING(""), NS_LITERAL_STRING("mediaSource"));
-        CallOnError(onFailure, *error);
-        return NS_OK;
+        return StreamPromise::CreateAndReject(
+            MakeRefPtr<MediaMgrError>(MediaMgrError::Name::OverconstrainedError,
+                                      NS_LITERAL_STRING(""),
+                                      NS_LITERAL_STRING("mediaSource")),
+            __func__);
       }
     }
 
     if (vc.mAdvanced.WasPassed() && videoType != MediaSourceEnum::Camera) {
       // iterate through advanced, forcing all unset mediaSources to match
       // "root"
       const char* unset = EnumToASCII(dom::MediaSourceEnumValues::strings,
                                       MediaSourceEnum::Camera);
@@ -2644,30 +2536,29 @@ nsresult MediaManager::GetUserMedia(
     switch (audioType) {
       case MediaSourceEnum::Microphone:
         break;
 
       case MediaSourceEnum::AudioCapture:
         // Only enable AudioCapture if the pref is enabled. If it's not, we can
         // deny right away.
         if (!Preferences::GetBool("media.getusermedia.audiocapture.enabled")) {
-          RefPtr<MediaStreamError> error = new MediaStreamError(
-              aWindow, MediaStreamError::Name::NotAllowedError);
-          CallOnError(onFailure, *error);
-          return NS_OK;
+          return StreamPromise::CreateAndReject(
+              MakeRefPtr<MediaMgrError>(MediaMgrError::Name::NotAllowedError),
+              __func__);
         }
         break;
 
       case MediaSourceEnum::Other:
       default: {
-        RefPtr<MediaStreamError> error = new MediaStreamError(
-            aWindow, MediaStreamError::Name::OverconstrainedError,
-            NS_LITERAL_STRING(""), NS_LITERAL_STRING("mediaSource"));
-        CallOnError(onFailure, *error);
-        return NS_OK;
+        return StreamPromise::CreateAndReject(
+            MakeRefPtr<MediaMgrError>(MediaMgrError::Name::OverconstrainedError,
+                                      NS_LITERAL_STRING(""),
+                                      NS_LITERAL_STRING("mediaSource")),
+            __func__);
       }
     }
     if (ac.mAdvanced.WasPassed()) {
       // iterate through advanced, forcing all unset mediaSources to match
       // "root"
       const char* unset = EnumToASCII(dom::MediaSourceEnumValues::strings,
                                       MediaSourceEnum::Camera);
       for (MediaTrackConstraintSet& cs : ac.mAdvanced.Value()) {
@@ -2697,74 +2588,73 @@ nsresult MediaManager::GetUserMedia(
 
   RefPtr<SourceListener> sourceListener = new SourceListener();
   windowListener->Register(sourceListener);
 
   if (!privileged) {
     // Check if this site has had persistent permissions denied.
     nsCOMPtr<nsIPermissionManager> permManager =
         do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
-    NS_ENSURE_SUCCESS(rv, rv);
+    MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
 
     uint32_t audioPerm = nsIPermissionManager::UNKNOWN_ACTION;
     if (IsOn(c.mAudio)) {
       if (audioType == MediaSourceEnum::Microphone) {
         if (Preferences::GetBool("media.getusermedia.microphone.deny", false) ||
             !dom::FeaturePolicyUtils::IsFeatureAllowed(
                 doc, NS_LITERAL_STRING("microphone"))) {
           audioPerm = nsIPermissionManager::DENY_ACTION;
         } else {
           rv = permManager->TestExactPermissionFromPrincipal(
               principal, "microphone", &audioPerm);
-          NS_ENSURE_SUCCESS(rv, rv);
+          MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
         }
       } else {
         rv = permManager->TestExactPermissionFromPrincipal(principal, "screen",
                                                            &audioPerm);
-        NS_ENSURE_SUCCESS(rv, rv);
+        MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
       }
     }
 
     uint32_t videoPerm = nsIPermissionManager::UNKNOWN_ACTION;
     if (IsOn(c.mVideo)) {
       if (videoType == MediaSourceEnum::Camera) {
         if (Preferences::GetBool("media.getusermedia.camera.deny", false) ||
             !dom::FeaturePolicyUtils::IsFeatureAllowed(
                 doc, NS_LITERAL_STRING("camera"))) {
           videoPerm = nsIPermissionManager::DENY_ACTION;
         } else {
           rv = permManager->TestExactPermissionFromPrincipal(
               principal, "camera", &videoPerm);
-          NS_ENSURE_SUCCESS(rv, rv);
+          MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
         }
       } else {
         rv = permManager->TestExactPermissionFromPrincipal(principal, "screen",
                                                            &videoPerm);
-        NS_ENSURE_SUCCESS(rv, rv);
+        MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
       }
     }
 
     if ((!IsOn(c.mAudio) && !IsOn(c.mVideo)) ||
         (IsOn(c.mAudio) && audioPerm == nsIPermissionManager::DENY_ACTION) ||
         (IsOn(c.mVideo) && videoPerm == nsIPermissionManager::DENY_ACTION)) {
-      RefPtr<MediaStreamError> error = new MediaStreamError(
-          aWindow, MediaStreamError::Name::NotAllowedError);
-      CallOnError(onFailure, *error);
       windowListener->Remove(sourceListener);
-      return NS_OK;
+      return StreamPromise::CreateAndReject(
+          MakeRefPtr<MediaMgrError>(MediaMgrError::Name::NotAllowedError),
+          __func__);
     }
   }
 
   // Get list of all devices, with origin-specific device ids.
 
   MediaEnginePrefs prefs = mPrefs;
 
   nsString callID;
   rv = GenerateUUID(callID);
-  NS_ENSURE_SUCCESS(rv, rv);
+  MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
 
   bool hasVideo = videoType != MediaSourceEnum::Other;
   bool hasAudio = audioType != MediaSourceEnum::Other;
   DeviceEnumerationType videoEnumerationType = DeviceEnumerationType::Normal;
   DeviceEnumerationType audioEnumerationType = DeviceEnumerationType::Normal;
 
   // Handle loopback and fake requests. For gUM we don't consider resist
   // fingerprinting as users should be prompted anyway.
@@ -2807,169 +2697,189 @@ nsresult MediaManager::GetUserMedia(
        ", videoEnumerationType=%" PRIu8 ", audioEnumerationType=%" PRIu8
        ", askPermission=%s",
        __func__, windowID, static_cast<uint8_t>(videoType),
        static_cast<uint8_t>(audioType),
        static_cast<uint8_t>(videoEnumerationType),
        static_cast<uint8_t>(audioEnumerationType),
        askPermission ? "true" : "false"));
 
-  RefPtr<MediaDeviceSetPromise> p =
-      EnumerateDevicesImpl(windowID, videoType, audioType, MediaSinkEnum::Other,
-                           videoEnumerationType, audioEnumerationType);
   RefPtr<MediaManager> self = this;
-  p->Then(
-      GetCurrentThreadSerialEventTarget(), __func__,
-      [self, onSuccess, onFailure, windowID, c, windowListener, sourceListener,
-       askPermission, prefs, isHTTPS, isHandlingUserInput, callID,
-       principalInfo, isChrome,
-       resistFingerprinting](RefPtr<MediaDeviceSetRefCnt>&& aDevices) mutable {
-        LOG(
-            ("GetUserMedia: post enumeration promise success callback "
-             "starting"));
-
-        // Ensure that our windowID is still good.
-        if (!nsGlobalWindowInner::GetInnerWindowWithId(windowID) ||
-            !self->IsWindowListenerStillActive(windowListener)) {
-          LOG(("GetUserMedia: bad window (%" PRIu64 ") in post enumeration "
-               "success callback!",
-               windowID));
-          return;
-        }
-
-        // Apply any constraints. This modifies the passed-in list.
-        RefPtr<BadConstraintsPromise> p2 =
-            self->SelectSettings(c, isChrome, aDevices);
-
-        p2->Then(
-            GetCurrentThreadSerialEventTarget(), __func__,
-            [self, onSuccess, onFailure, windowID, c, windowListener,
-             sourceListener, askPermission, prefs, isHTTPS, isHandlingUserInput,
-             callID, principalInfo, isChrome, aDevices,
-             resistFingerprinting](const char* badConstraint) mutable {
-              LOG(
-                  ("GetUserMedia: starting post enumeration promise2 success "
-                   "callback!"));
-
-              // Ensure that the window is still good.
-              auto* globalWindow =
-                  nsGlobalWindowInner::GetInnerWindowWithId(windowID);
-              RefPtr<nsPIDOMWindowInner> window =
-                  globalWindow ? globalWindow->AsInner() : nullptr;
-              if (!window ||
-                  !self->IsWindowListenerStillActive(windowListener)) {
-                LOG(("GetUserMedia: bad window (%" PRIu64
-                     ") in post enumeration "
-                     "success callback 2!",
-                     windowID));
-                return;
-              }
-
-              if (badConstraint) {
-                LOG(
-                    ("GetUserMedia: bad constraint found in post enumeration "
-                     "promise2 "
-                     "success callback! Calling error handler!"));
-                nsString constraint;
-                constraint.AssignASCII(badConstraint);
-                RefPtr<MediaStreamError> error = new MediaStreamError(
-                    window, MediaStreamError::Name::OverconstrainedError,
-                    NS_LITERAL_STRING(""), constraint);
-                CallOnError(onFailure, *error);
-                return;
-              }
-              if (!(*aDevices)->Length()) {
-                LOG(
-                    ("GetUserMedia: no devices found in post enumeration "
-                     "promise2 "
-                     "success callback! Calling error handler!"));
-                RefPtr<MediaStreamError> error = new MediaStreamError(
-                    window,
-                    // When privacy.resistFingerprinting = true, no available
-                    // device implies content script is requesting a fake
-                    // device, so report NotAllowedError.
-                    resistFingerprinting
-                        ? MediaStreamError::Name::NotAllowedError
-                        : MediaStreamError::Name::NotFoundError);
-                CallOnError(onFailure, *error);
-                return;
-              }
-
-              nsCOMPtr<nsIMutableArray> devicesCopy =
-                  nsArray::Create();  // before we give up devices below
-              if (!askPermission) {
-                for (auto& device : **aDevices) {
-                  nsresult rv = devicesCopy->AppendElement(device);
-                  if (NS_WARN_IF(NS_FAILED(rv))) {
-                    return;
-                  }
-                }
-              }
-
-              bool focusSource;
-              focusSource = mozilla::Preferences::GetBool(
-                  "media.getusermedia.window.focus_source.enabled", true);
-
-              // Pass callbacks and listeners along to GetUserMediaTask.
-              RefPtr<GetUserMediaTask> task(new GetUserMediaTask(
-                  c, onSuccess, onFailure, windowID, windowListener,
-                  sourceListener, prefs, principalInfo, isChrome,
-                  aDevices->release(), focusSource));
-              // Store the task w/callbacks.
-              self->mActiveCallbacks.Put(callID, task.forget());
-
-              // Add a WindowID cross-reference so OnNavigation can tear things
-              // down
-              nsTArray<nsString>* array;
-              if (!self->mCallIds.Get(windowID, &array)) {
-                array = new nsTArray<nsString>();
-                self->mCallIds.Put(windowID, array);
-              }
-              array->AppendElement(callID);
-
-              nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-              if (!askPermission) {
-                obs->NotifyObservers(devicesCopy,
-                                     "getUserMedia:privileged:allow",
-                                     callID.BeginReading());
-              } else {
-                RefPtr<GetUserMediaRequest> req = new GetUserMediaRequest(
-                    window, callID, c, isHTTPS, isHandlingUserInput);
-                if (!Preferences::GetBool("media.navigator.permission.force") &&
-                    array->Length() > 1) {
-                  // there is at least 1 pending gUM request
-                  // For the scarySources test case, always send the request
-                  self->mPendingGUMRequest.AppendElement(req.forget());
-                } else {
-                  obs->NotifyObservers(req, "getUserMedia:request", nullptr);
-                }
-              }
+  return EnumerateDevicesImpl(windowID, videoType, audioType,
+                              MediaSinkEnum::Other, videoEnumerationType,
+                              audioEnumerationType)
+      ->Then(
+          GetCurrentThreadSerialEventTarget(), __func__,
+          [self, windowID, c, windowListener, sourceListener, askPermission,
+           prefs, isHTTPS, isHandlingUserInput, callID, principalInfo, isChrome,
+           resistFingerprinting](RefPtr<MediaDeviceSetRefCnt>&& aDevices)
+              -> RefPtr<StreamPromise> {
+            LOG(
+                ("GetUserMedia: post enumeration promise success callback "
+                 "starting"));
+
+            // Ensure that our windowID is still good.
+            auto* globalWindow =
+                nsGlobalWindowInner::GetInnerWindowWithId(windowID);
+            RefPtr<nsPIDOMWindowInner> window =
+                globalWindow ? globalWindow->AsInner() : nullptr;
+            if (!window || !self->IsWindowListenerStillActive(windowListener)) {
+              LOG(("GetUserMedia: bad window (%" PRIu64 ") in post enumeration "
+                   "success callback!",
+                   windowID));
+              return StreamPromise::CreateAndReject(
+                  MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError),
+                  __func__);
+            }
+
+            // Apply any constraints. This modifies the passed-in list.
+            return self->SelectSettings(c, isChrome, aDevices)
+                ->Then(
+                    GetCurrentThreadSerialEventTarget(), __func__,
+                    [self, windowID, c, windowListener, sourceListener,
+                     askPermission, prefs, isHTTPS, isHandlingUserInput, callID,
+                     principalInfo, isChrome, aDevices,
+                     resistFingerprinting](const char* badConstraint) mutable {
+                      LOG(
+                          ("GetUserMedia: starting post enumeration promise2 "
+                           "success "
+                           "callback!"));
+
+                      // Ensure that the window is still good.
+                      auto* globalWindow =
+                          nsGlobalWindowInner::GetInnerWindowWithId(windowID);
+                      RefPtr<nsPIDOMWindowInner> window =
+                          globalWindow ? globalWindow->AsInner() : nullptr;
+                      if (!window ||
+                          !self->IsWindowListenerStillActive(windowListener)) {
+                        LOG(("GetUserMedia: bad window (%" PRIu64
+                             ") in post enumeration "
+                             "success callback 2!",
+                             windowID));
+                        return StreamPromise::CreateAndReject(
+                            MakeRefPtr<MediaMgrError>(
+                                MediaMgrError::Name::AbortError),
+                            __func__);
+                      }
+
+                      if (badConstraint) {
+                        LOG(
+                            ("GetUserMedia: bad constraint found in post "
+                             "enumeration promise2 "
+                             "success callback! Calling error handler!"));
+                        nsString constraint;
+                        constraint.AssignASCII(badConstraint);
+                        return StreamPromise::CreateAndReject(
+                            MakeRefPtr<MediaMgrError>(
+                                MediaMgrError::Name::OverconstrainedError,
+                                NS_LITERAL_STRING(""), constraint),
+                            __func__);
+                      }
+                      if (!aDevices->Length()) {
+                        LOG(
+                            ("GetUserMedia: no devices found in post "
+                             "enumeration promise2 "
+                             "success callback! Calling error handler!"));
+                        // When privacy.resistFingerprinting = true, no
+                        // available device implies content script is requesting
+                        // a fake device, so report NotAllowedError.
+                        auto error = resistFingerprinting
+                                         ? MediaMgrError::Name::NotAllowedError
+                                         : MediaMgrError::Name::NotFoundError;
+                        return StreamPromise::CreateAndReject(
+                            MakeRefPtr<MediaMgrError>(error), __func__);
+                      }
+
+                      // before we give up devices below
+                      nsCOMPtr<nsIMutableArray> devicesCopy = nsArray::Create();
+                      if (!askPermission) {
+                        for (auto& device : *aDevices) {
+                          nsresult rv = devicesCopy->AppendElement(device);
+                          if (NS_WARN_IF(NS_FAILED(rv))) {
+                            return StreamPromise::CreateAndReject(
+                                MakeRefPtr<MediaMgrError>(
+                                    MediaMgrError::Name::AbortError),
+                                __func__);
+                          }
+                        }
+                      }
+
+                      bool focusSource = mozilla::Preferences::GetBool(
+                          "media.getusermedia.window.focus_source.enabled",
+                          true);
+
+                      // Incremental hack to compile. To be replaced by deeper
+                      // refactoring. MediaManager allows
+                      // "neither-resolve-nor-reject" semantics, so we cannot
+                      // use MozPromiseHolder here.
+                      auto holder = MozPromiseHolder<StreamPromise>();
+                      RefPtr<StreamPromise> p = holder.Ensure(__func__);
+
+                      // Pass callbacks and listeners along to GetUserMediaTask.
+                      auto task = MakeRefPtr<GetUserMediaTask>(
+                          c, std::move(holder), windowID, windowListener,
+                          sourceListener, prefs, principalInfo, isChrome,
+                          std::move(aDevices), focusSource);
+
+                      // Store the task w/callbacks.
+                      self->mActiveCallbacks.Put(callID, task.forget());
+
+                      // Add a WindowID cross-reference so OnNavigation can tear
+                      // things down
+                      nsTArray<nsString>* array;
+                      if (!self->mCallIds.Get(windowID, &array)) {
+                        array = new nsTArray<nsString>();
+                        self->mCallIds.Put(windowID, array);
+                      }
+                      array->AppendElement(callID);
+
+                      nsCOMPtr<nsIObserverService> obs =
+                          services::GetObserverService();
+                      if (!askPermission) {
+                        obs->NotifyObservers(devicesCopy,
+                                             "getUserMedia:privileged:allow",
+                                             callID.BeginReading());
+                      } else {
+                        RefPtr<GetUserMediaRequest> req =
+                            new GetUserMediaRequest(window, callID, c, isHTTPS,
+                                                    isHandlingUserInput);
+                        if (!Preferences::GetBool(
+                                "media.navigator.permission.force") &&
+                            array->Length() > 1) {
+                          // there is at least 1 pending gUM request
+                          // For the scarySources test case, always send the
+                          // request
+                          self->mPendingGUMRequest.AppendElement(req.forget());
+                        } else {
+                          obs->NotifyObservers(req, "getUserMedia:request",
+                                               nullptr);
+                        }
+                      }
 
 #ifdef MOZ_WEBRTC
-              EnableWebRtcLog();
+                      EnableWebRtcLog();
 #endif
-            },
-            [onFailure, windowID](nsresult reason) mutable {
-              LOG(
-                  ("GetUserMedia: post enumeration promse2 failure callback "
-                   "called!"));
-              nsPIDOMWindowInner* window =
-                  nsGlobalWindowInner::GetInnerWindowWithId(windowID)
-                      ->AsInner();
-              auto error = MakeRefPtr<MediaStreamError>(
-                  window, MediaStreamError::Name::AbortError);
-              CallOnError(onFailure, *error);
-            });
-      },
-      [onFailure](RefPtr<MediaStreamError>&& reason) mutable {
-        LOG((
-            "GetUserMedia: post enumeration promise failure callback called!"));
-        CallOnError(onFailure, *reason);
-      });
-  return NS_OK;
+                      return p;
+                    },
+                    [](nsresult rv) {
+                      LOG(
+                          ("GetUserMedia: post enumeration SelectSettings "
+                           "failure callback called!"));
+                      return StreamPromise::CreateAndReject(
+                          MakeRefPtr<MediaMgrError>(
+                              MediaMgrError::Name::AbortError),
+                          __func__);
+                    });
+          },
+          [](RefPtr<MediaMgrError>&& aError) {
+            LOG(
+                ("GetUserMedia: post enumeration EnumerateDevicesImpl "
+                 "failure callback called!"));
+            return StreamPromise::CreateAndReject(std::move(aError), __func__);
+          });
 }
 
 /* static */ void MediaManager::AnonymizeDevices(MediaDeviceSet& aDevices,
                                                  const nsACString& aOriginKey) {
   if (!aOriginKey.IsEmpty()) {
     for (RefPtr<MediaDevice>& device : aDevices) {
       nsString id;
       device->GetId(id);
@@ -3074,130 +2984,115 @@ RefPtr<MediaManager::MediaDeviceSetPromi
 
   nsCOMPtr<nsIPrincipal> principal =
       nsGlobalWindowInner::Cast(window)->GetPrincipal();
   MOZ_ASSERT(principal);
 
   ipc::PrincipalInfo principalInfo;
   nsresult rv = PrincipalToPrincipalInfo(principal, &principalInfo);
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    nsPIDOMWindowInner* window =
-        nsGlobalWindowInner::GetInnerWindowWithId(aWindowId)->AsInner();
     return MediaDeviceSetPromise::CreateAndReject(
-        MakeRefPtr<MediaStreamError>(window,
-                                     MediaStreamError::Name::NotAllowedError),
+        MakeRefPtr<MediaMgrError>(MediaMgrError::Name::NotAllowedError),
         __func__);
   }
 
   bool persist = IsActivelyCapturingOrHasAPermission(aWindowId);
 
-  // GetPrincipalKey is an async API that returns a promise.
-  // We use .Then() to pass in a lambda to run back on this same
-  // thread later once GetPrincipalKey resolves. Needed variables are "captured"
+  // GetPrincipalKey is an async API that returns a promise. We use .Then() to
+  // pass in a lambda to run back on this same thread later once
+  // GetPrincipalKey resolves. Needed variables are "captured"
   // (passed by value) safely into the lambda.
   return media::GetPrincipalKey(principalInfo, persist)
       ->Then(
           GetMainThreadSerialEventTarget(), __func__,
           [aWindowId, aVideoInputType, aAudioInputType, aVideoInputEnumType,
            aAudioInputEnumType, aAudioOutputType](
               const nsCString& aOriginKey) -> RefPtr<MediaDeviceSetPromise> {
             MOZ_ASSERT(NS_IsMainThread());
             MediaManager* mgr = MediaManager::GetIfExists();
             MOZ_ASSERT(mgr);
             if (!mgr->IsWindowStillActive(aWindowId)) {
-              nsPIDOMWindowInner* window =
-                  nsGlobalWindowInner::GetInnerWindowWithId(aWindowId)
-                      ->AsInner();
               return MediaDeviceSetPromise::CreateAndReject(
-                  MakeRefPtr<MediaStreamError>(
-                      window, MediaStreamError::Name::AbortError),
+                  MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError),
                   __func__);
             }
 
             return mgr
                 ->EnumerateRawDevices(aWindowId, aVideoInputType,
                                       aAudioInputType, aAudioOutputType,
                                       aVideoInputEnumType, aAudioInputEnumType)
                 ->Then(
                     GetMainThreadSerialEventTarget(), __func__,
                     [aWindowId, aOriginKey, aVideoInputEnumType,
                      aAudioInputEnumType, aVideoInputType,
                      aAudioInputType](RefPtr<MediaDeviceSetRefCnt>&& aDevices)
                         -> RefPtr<MediaDeviceSetPromise> {
                       // Only run if window is still on our active list.
                       MediaManager* mgr = MediaManager::GetIfExists();
                       if (!mgr || !mgr->IsWindowStillActive(aWindowId)) {
-                        nsPIDOMWindowInner* window =
-                            nsGlobalWindowInner::GetInnerWindowWithId(aWindowId)
-                                ->AsInner();
                         return MediaDeviceSetPromise::CreateAndReject(
-                            MakeRefPtr<MediaStreamError>(
-                                window, MediaStreamError::Name::AbortError),
+                            MakeRefPtr<MediaMgrError>(
+                                MediaMgrError::Name::AbortError),
                             __func__);
                       }
 
                       // If we fetched any real cameras or mics, remove the
                       // "default" part of their IDs.
                       if (aVideoInputType == MediaSourceEnum::Camera &&
                           aAudioInputType == MediaSourceEnum::Microphone &&
                           (aVideoInputEnumType != DeviceEnumerationType::Fake ||
                            aAudioInputEnumType !=
                                DeviceEnumerationType::Fake)) {
                         mgr->mDeviceIDs.Clear();
-                        for (auto& device : **aDevices) {
+                        for (auto& device : *aDevices) {
                           nsString id;
                           device->GetId(id);
                           id.ReplaceSubstring(NS_LITERAL_STRING("default: "),
                                               NS_LITERAL_STRING(""));
                           if (!mgr->mDeviceIDs.Contains(id)) {
                             mgr->mDeviceIDs.AppendElement(id);
                           }
                         }
                       }
 
                       if (!mgr->IsWindowStillActive(aWindowId)) {
-                        nsPIDOMWindowInner* window =
-                            nsGlobalWindowInner::GetInnerWindowWithId(aWindowId)
-                                ->AsInner();
                         return MediaDeviceSetPromise::CreateAndReject(
-                            MakeRefPtr<MediaStreamError>(
-                                window, MediaStreamError::Name::AbortError),
+                            MakeRefPtr<MediaMgrError>(
+                                MediaMgrError::Name::AbortError),
                             __func__);
                       }
 
-                      MediaManager_AnonymizeDevices(**aDevices, aOriginKey);
+                      MediaManager::AnonymizeDevices(*aDevices, aOriginKey);
                       return MediaDeviceSetPromise::CreateAndResolve(
                           std::move(aDevices), __func__);
                     },
-                    [](RefPtr<MediaStreamError>&& reason) {
+                    [](RefPtr<MediaMgrError>&& aError) {
                       return MediaDeviceSetPromise::CreateAndReject(
-                          std::move(reason), __func__);
+                          std::move(aError), __func__);
                     });
           },
-          [aWindowId](nsresult rs) {
+          [](nsresult rs) {
             NS_WARNING(
                 "EnumerateDevicesImpl failed to get Principal Key. Enumeration "
                 "will not continue.");
-            nsPIDOMWindowInner* window =
-                nsGlobalWindowInner::GetInnerWindowWithId(aWindowId)->AsInner();
             return MediaDeviceSetPromise::CreateAndReject(
-                MakeRefPtr<MediaStreamError>(
-                    window, MediaStreamError::Name::AbortError),
+                MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError),
                 __func__);
           });
 }
 
-nsresult MediaManager::EnumerateDevices(
-    nsPIDOMWindowInner* aWindow,
-    nsIGetUserMediaDevicesSuccessCallback* aOnSuccess,
-    nsIDOMGetUserMediaErrorCallback* aOnFailure, dom::CallerType aCallerType) {
+RefPtr<MediaManager::MediaDeviceSetPromise> MediaManager::EnumerateDevices(
+    nsPIDOMWindowInner* aWindow, dom::CallerType aCallerType) {
   MOZ_ASSERT(NS_IsMainThread());
-  NS_ENSURE_TRUE(!sHasShutdown, NS_ERROR_FAILURE);
-  nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onSuccess(aOnSuccess);
-  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onFailure(aOnFailure);
+  if (sHasShutdown) {
+    return MediaDeviceSetPromise::CreateAndReject(
+        MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError,
+                                  NS_LITERAL_STRING("In shutdown")),
+        __func__);
+  }
   uint64_t windowId = aWindow->WindowID();
 
   nsIPrincipal* principal = aWindow->GetExtantDoc()->NodePrincipal();
 
   RefPtr<GetUserMediaWindowListener> windowListener =
       GetWindowListener(windowId);
   if (windowListener) {
     PrincipalHandle existingPrincipalHandle =
@@ -3243,43 +3138,36 @@ nsresult MediaManager::EnumerateDevices(
       audioEnumerationType = DeviceEnumerationType::Fake;
     }
   }
 
   MediaSinkEnum audioOutputType = MediaSinkEnum::Other;
   if (Preferences::GetBool("media.setsinkid.enabled")) {
     audioOutputType = MediaSinkEnum::Speaker;
   }
-  RefPtr<MediaDeviceSetPromise> p = EnumerateDevicesImpl(
-      windowId, MediaSourceEnum::Camera, MediaSourceEnum::Microphone,
-      audioOutputType, videoEnumerationType, audioEnumerationType);
-  p->Then(GetCurrentThreadSerialEventTarget(), __func__,
-          [onSuccess, windowListener,
-           sourceListener](RefPtr<MediaDeviceSetRefCnt>&& aDevices) mutable {
-            DebugOnly<bool> rv = windowListener->Remove(sourceListener);
-            MOZ_ASSERT(rv);
-            nsCOMPtr<nsIWritableVariant> array =
-                MediaManager_ToJSArray(**aDevices);
-            onSuccess->OnSuccess(array);
-          },
-          [onFailure, windowListener, sourceListener,
-           windowId](RefPtr<MediaStreamError>&& reason) mutable {
-            MediaManager* mgr = MediaManager::GetIfExists();
-            if (!mgr || !mgr->IsWindowStillActive(windowId)) {
-              // If an error happened, like navigate away
-              // leave the promise pending.
-              return;
-            }
-            // This may fail, if a new doc has been set the OnNavigation method
-            // should have removed all previous active listeners. Attempt to
-            // clean it here, just in case, but ignore the return value.
-            windowListener->Remove(sourceListener);
-            onFailure->OnError(reason);
-          });
-  return NS_OK;
+  return EnumerateDevicesImpl(windowId, MediaSourceEnum::Camera,
+                              MediaSourceEnum::Microphone, audioOutputType,
+                              videoEnumerationType, audioEnumerationType)
+      ->Then(GetCurrentThreadSerialEventTarget(), __func__,
+             [windowListener,
+              sourceListener](RefPtr<MediaDeviceSetRefCnt>&& aDevices) {
+               DebugOnly<bool> rv = windowListener->Remove(sourceListener);
+               MOZ_ASSERT(rv);
+               return MediaDeviceSetPromise::CreateAndResolve(
+                   std::move(aDevices), __func__);
+             },
+             [windowListener, sourceListener](RefPtr<MediaMgrError>&& aError) {
+               // This may fail, if a new doc has been set the OnNavigation
+               // method should have removed all previous active listeners.
+               // Attempt to clean it here, just in case, but ignore the return
+               // value.
+               Unused << windowListener->Remove(sourceListener);
+               return MediaDeviceSetPromise::CreateAndReject(std::move(aError),
+                                                             __func__);
+             });
 }
 
 RefPtr<SinkInfoPromise> MediaManager::GetSinkDevice(nsPIDOMWindowInner* aWindow,
                                                     const nsString& aDeviceId) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aWindow);
 
   // We have to add the window id here because enumerate methods
@@ -3305,17 +3193,17 @@ RefPtr<SinkInfoPromise> MediaManager::Ge
   bool isSecure = aWindow->IsSecureContext();
 
   return EnumerateDevicesImpl(aWindow->WindowID(), MediaSourceEnum::Other,
                               MediaSourceEnum::Other, MediaSinkEnum::Speaker,
                               DeviceEnumerationType::Normal,
                               DeviceEnumerationType::Normal)
       ->Then(GetCurrentThreadSerialEventTarget(), __func__,
              [aDeviceId, isSecure](RefPtr<MediaDeviceSetRefCnt>&& aDevices) {
-               for (RefPtr<MediaDevice>& device : **aDevices) {
+               for (RefPtr<MediaDevice>& device : *aDevices) {
                  if (aDeviceId.IsEmpty() && device->mSinkInfo->Preferred()) {
                    return SinkInfoPromise::CreateAndResolve(device->mSinkInfo,
                                                             __func__);
                  }
                  if (device->mID.Equals(aDeviceId)) {
                    // TODO: Check if the application is authorized to play audio
                    // through this device (Bug 1493982).
                    if (isSecure || device->mSinkInfo->Preferred()) {
@@ -3324,17 +3212,17 @@ RefPtr<SinkInfoPromise> MediaManager::Ge
                    }
                    return SinkInfoPromise::CreateAndReject(
                        NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR, __func__);
                  }
                }
                return SinkInfoPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE,
                                                        __func__);
              },
-             [](RefPtr<MediaStreamError>&& reason) {
+             [](RefPtr<MediaMgrError>&& aError) {
                return SinkInfoPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE,
                                                        __func__);
              });
 }
 
 /*
  * GetUserMediaDevices - called by the UI-part of getUserMedia from chrome JS.
  */
@@ -3356,17 +3244,17 @@ nsresult MediaManager::GetUserMediaDevic
     return NS_ERROR_UNEXPECTED;
   }
 
   for (auto& callID : *callIDs) {
     RefPtr<GetUserMediaTask> task;
     if (!aCallID.Length() || aCallID == callID) {
       if (mActiveCallbacks.Get(callID, getter_AddRefs(task))) {
         nsCOMPtr<nsIWritableVariant> array =
-            MediaManager_ToJSArray(*task->mMediaDeviceSet);
+            MediaManager::ToJSArray(*task->mMediaDeviceSet);
         aOnSuccess.Call(array);
         return NS_OK;
       }
     }
   }
   return NS_ERROR_UNEXPECTED;
 }
 
@@ -4135,126 +4023,133 @@ void SourceListener::Activate(SourceMedi
         aVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Camera &&
             Preferences::GetBool(
                 "media.getusermedia.camera.off_while_disabled.enabled", true),
         MakeRefPtr<SourceTrackListener>(this, kVideoTrack));
     mStream->AddTrackListener(mVideoDeviceState->mListener, kVideoTrack);
   }
 }
 
-RefPtr<SourceListener::InitPromise> SourceListener::InitializeAsync() {
+RefPtr<SourceListener::SourceListenerPromise>
+SourceListener::InitializeAsync() {
   MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
   MOZ_DIAGNOSTIC_ASSERT(!mStopped);
 
-  RefPtr<InitPromise> init = MediaManager::PostTask<InitPromise>(
-      __func__,
-      [stream = mStream, principal = GetPrincipalHandle(),
-       audioDevice = mAudioDeviceState ? mAudioDeviceState->mDevice : nullptr,
-       videoDevice = mVideoDeviceState ? mVideoDeviceState->mDevice : nullptr](
-          MozPromiseHolder<InitPromise>& aHolder) {
-        if (audioDevice) {
-          nsresult rv = audioDevice->SetTrack(stream, kAudioTrack, principal);
-          if (NS_SUCCEEDED(rv)) {
-            rv = audioDevice->Start();
-          }
-          if (NS_FAILED(rv)) {
-            nsString log;
-            if (rv == NS_ERROR_NOT_AVAILABLE) {
-              log.AssignLiteral("Concurrent mic process limit.");
-              aHolder.Reject(MakeRefPtr<MediaMgrError>(
-                                 MediaMgrError::Name::NotReadableError, log),
-                             __func__);
-              return;
-            }
-            log.AssignLiteral("Starting audio failed");
-            aHolder.Reject(
-                MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError, log),
-                __func__);
-            return;
-          }
-        }
-
-        if (videoDevice) {
-          nsresult rv = videoDevice->SetTrack(stream, kVideoTrack, principal);
-          if (NS_SUCCEEDED(rv)) {
-            rv = videoDevice->Start();
-          }
-          if (NS_FAILED(rv)) {
-            if (audioDevice) {
-              if (NS_WARN_IF(NS_FAILED(audioDevice->Stop()))) {
-                MOZ_ASSERT_UNREACHABLE("Stopping audio failed");
-              }
-            }
-            nsString log;
-            log.AssignLiteral("Starting video failed");
-            aHolder.Reject(
-                MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError, log),
-                __func__);
-            return;
-          }
-        }
-
-        // Start() queued the tracks to be added synchronously to avoid races
-        stream->FinishAddTracks();
-        LOG(("started all sources"));
-
-        aHolder.Resolve(true, __func__);
-      });
-
-  return init->Then(
-      GetMainThreadSerialEventTarget(), __func__,
-      [self = RefPtr<SourceListener>(this), this]() {
-        if (mStopped) {
-          // We were shut down during the async init
-          return InitPromise::CreateAndResolve(true, __func__);
-        }
-
-        for (DeviceState* state :
-             {mAudioDeviceState.get(), mVideoDeviceState.get()}) {
-          if (!state) {
-            continue;
-          }
-          MOZ_DIAGNOSTIC_ASSERT(!state->mTrackEnabled);
-          MOZ_DIAGNOSTIC_ASSERT(!state->mDeviceEnabled);
-          MOZ_DIAGNOSTIC_ASSERT(!state->mStopped);
-
-          state->mDeviceEnabled = true;
-          state->mTrackEnabled = true;
-          state->mTrackEnabledTime = TimeStamp::Now();
+  return MediaManager::PostTask<SourceListenerPromise>(
+             __func__,
+             [stream = mStream, principal = GetPrincipalHandle(),
+              audioDevice =
+                  mAudioDeviceState ? mAudioDeviceState->mDevice : nullptr,
+              videoDevice =
+                  mVideoDeviceState ? mVideoDeviceState->mDevice : nullptr](
+                 MozPromiseHolder<SourceListenerPromise>& aHolder) {
+               if (audioDevice) {
+                 nsresult rv =
+                     audioDevice->SetTrack(stream, kAudioTrack, principal);
+                 if (NS_SUCCEEDED(rv)) {
+                   rv = audioDevice->Start();
+                 }
+                 if (NS_FAILED(rv)) {
+                   nsString log;
+                   if (rv == NS_ERROR_NOT_AVAILABLE) {
+                     log.AssignLiteral("Concurrent mic process limit.");
+                     aHolder.Reject(
+                         MakeRefPtr<MediaMgrError>(
+                             MediaMgrError::Name::NotReadableError, log),
+                         __func__);
+                     return;
+                   }
+                   log.AssignLiteral("Starting audio failed");
+                   aHolder.Reject(MakeRefPtr<MediaMgrError>(
+                                      MediaMgrError::Name::AbortError, log),
+                                  __func__);
+                   return;
+                 }
+               }
+
+               if (videoDevice) {
+                 nsresult rv =
+                     videoDevice->SetTrack(stream, kVideoTrack, principal);
+                 if (NS_SUCCEEDED(rv)) {
+                   rv = videoDevice->Start();
+                 }
+                 if (NS_FAILED(rv)) {
+                   if (audioDevice) {
+                     if (NS_WARN_IF(NS_FAILED(audioDevice->Stop()))) {
+                       MOZ_ASSERT_UNREACHABLE("Stopping audio failed");
+                     }
+                   }
+                   nsString log;
+                   log.AssignLiteral("Starting video failed");
+                   aHolder.Reject(MakeRefPtr<MediaMgrError>(
+                                      MediaMgrError::Name::AbortError, log),
+                                  __func__);
+                   return;
+                 }
+               }
+
+               // Start() queued the tracks to be added synchronously to avoid
+               // races
+               stream->FinishAddTracks();
+               LOG(("started all sources"));
+
+               aHolder.Resolve(true, __func__);
+             })
+      ->Then(GetMainThreadSerialEventTarget(), __func__,
+             [self = RefPtr<SourceListener>(this), this]() {
+               if (mStopped) {
+                 // We were shut down during the async init
+                 return SourceListenerPromise::CreateAndResolve(true, __func__);
+               }
+
+               for (DeviceState* state :
+                    {mAudioDeviceState.get(), mVideoDeviceState.get()}) {
+                 if (!state) {
+                   continue;
+                 }
+                 MOZ_DIAGNOSTIC_ASSERT(!state->mTrackEnabled);
+                 MOZ_DIAGNOSTIC_ASSERT(!state->mDeviceEnabled);
+                 MOZ_DIAGNOSTIC_ASSERT(!state->mStopped);
+
+                 state->mDeviceEnabled = true;
+                 state->mTrackEnabled = true;
+                 state->mTrackEnabledTime = TimeStamp::Now();
 
           if (state->mDevice->GetMediaSource() !=
               MediaSourceEnum::AudioCapture) {
             // For AudioCapture mStream is a dummy stream, so we don't try to
             // enable pulling - there won't be a track to enable it for.
             mStream->SetPullingEnabled(
                 state == mAudioDeviceState.get() ? kAudioTrack : kVideoTrack,
                 true);
           }
-        }
-        return InitPromise::CreateAndResolve(true, __func__);
-      },
-      [self = RefPtr<SourceListener>(this),
-       this](RefPtr<MediaMgrError>&& aResult) {
-        if (mStopped) {
-          return InitPromise::CreateAndReject(std::move(aResult), __func__);
-        }
-
-        for (DeviceState* state :
-             {mAudioDeviceState.get(), mVideoDeviceState.get()}) {
-          if (!state) {
-            continue;
-          }
-          MOZ_DIAGNOSTIC_ASSERT(!state->mTrackEnabled);
-          MOZ_DIAGNOSTIC_ASSERT(!state->mDeviceEnabled);
-          MOZ_DIAGNOSTIC_ASSERT(!state->mStopped);
-
-          state->mStopped = true;
-        }
-        return InitPromise::CreateAndReject(std::move(aResult), __func__);
-      });
+               }
+               return SourceListenerPromise::CreateAndResolve(true, __func__);
+             },
+             [self = RefPtr<SourceListener>(this),
+              this](RefPtr<MediaMgrError>&& aResult) {
+               if (mStopped) {
+                 return SourceListenerPromise::CreateAndReject(
+                     std::move(aResult), __func__);
+               }
+
+               for (DeviceState* state :
+                    {mAudioDeviceState.get(), mVideoDeviceState.get()}) {
+                 if (!state) {
+                   continue;
+                 }
+                 MOZ_DIAGNOSTIC_ASSERT(!state->mTrackEnabled);
+                 MOZ_DIAGNOSTIC_ASSERT(!state->mDeviceEnabled);
+                 MOZ_DIAGNOSTIC_ASSERT(!state->mStopped);
+
+                 state->mStopped = true;
+               }
+               return SourceListenerPromise::CreateAndReject(std::move(aResult),
+                                                             __func__);
+             });
 }
 
 void SourceListener::Stop() {
   MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
 
   if (mStopped) {
     return;
   }
@@ -4646,64 +4541,67 @@ CaptureState SourceListener::CapturingSo
 
   if (state.mDeviceEnabled) {
     return CaptureState::Enabled;
   }
 
   return CaptureState::Disabled;
 }
 
-RefPtr<SourceListener::ApplyConstraintsPromise>
+RefPtr<SourceListener::SourceListenerPromise>
 SourceListener::ApplyConstraintsToTrack(
-    nsPIDOMWindowInner* aWindow, TrackID aTrackID,
-    const MediaTrackConstraints& aConstraints, dom::CallerType aCallerType) {
+    TrackID aTrackID, const MediaTrackConstraints& aConstraints,
+    dom::CallerType aCallerType) {
   MOZ_ASSERT(NS_IsMainThread());
   DeviceState& state = GetDeviceStateFor(aTrackID);
 
   if (mStopped || state.mStopped) {
     LOG(("gUM %s track %d applyConstraints, but source is stopped",
          aTrackID == kAudioTrack ? "audio" : "video", aTrackID));
-    return ApplyConstraintsPromise::CreateAndResolve(false, __func__);
+    return SourceListenerPromise::CreateAndResolve(false, __func__);
   }
 
   MediaManager* mgr = MediaManager::GetIfExists();
   if (!mgr) {
-    return ApplyConstraintsPromise::CreateAndResolve(false, __func__);
+    return SourceListenerPromise::CreateAndResolve(false, __func__);
   }
 
-  return MediaManager::PostTask<ApplyConstraintsPromise>(
-      __func__,
-      [device = state.mDevice, aConstraints,
-       isChrome = aCallerType == dom::CallerType::System](
-          MozPromiseHolder<ApplyConstraintsPromise>& aHolder) mutable {
+  return MediaManager::PostTask<SourceListenerPromise>(
+      __func__, [device = state.mDevice, aConstraints,
+                 isChrome = aCallerType == dom::CallerType::System](
+                    MozPromiseHolder<SourceListenerPromise>& aHolder) mutable {
         MOZ_ASSERT(MediaManager::IsInMediaThread());
         MediaManager* mgr = MediaManager::GetIfExists();
         MOZ_RELEASE_ASSERT(mgr);  // Must exist while media thread is alive
         const char* badConstraint = nullptr;
         nsresult rv =
             device->Reconfigure(aConstraints, mgr->mPrefs, &badConstraint);
-        if (rv == NS_ERROR_INVALID_ARG) {
-          // Reconfigure failed due to constraints
-          if (!badConstraint) {
-            nsTArray<RefPtr<MediaDevice>> devices;
-            devices.AppendElement(device);
-            badConstraint = MediaConstraintsHelper::SelectSettings(
-                NormalizedConstraints(aConstraints), devices, isChrome);
+        if (NS_FAILED(rv)) {
+          if (rv == NS_ERROR_INVALID_ARG) {
+            // Reconfigure failed due to constraints
+            if (!badConstraint) {
+              nsTArray<RefPtr<MediaDevice>> devices;
+              devices.AppendElement(device);
+              badConstraint = MediaConstraintsHelper::SelectSettings(
+                  NormalizedConstraints(aConstraints), devices, isChrome);
+            }
+          } else {
+            // Unexpected. ApplyConstraints* cannot fail with any other error.
+            badConstraint = "";
+            LOG(("ApplyConstraintsToTrack-Task: Unexpected fail %" PRIx32,
+                 static_cast<uint32_t>(rv)));
           }
 
-          aHolder.Reject(Some(NS_ConvertASCIItoUTF16(badConstraint)), __func__);
+          aHolder.Reject(
+              MakeRefPtr<MediaMgrError>(
+                  MediaMgrError::Name::OverconstrainedError,
+                  NS_LITERAL_STRING(""), NS_ConvertASCIItoUTF16(badConstraint)),
+              __func__);
           return;
         }
-
-        if (NS_FAILED(rv)) {
-          // Reconfigure failed unexpectedly
-          aHolder.Reject(Nothing(), __func__);
-          return;
-        }
-
         // Reconfigure was successful
         aHolder.Resolve(false, __func__);
       });
 }
 
 PrincipalHandle SourceListener::GetPrincipalHandle() const {
   return mPrincipalHandle;
 }
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -202,32 +202,42 @@ class MediaManager final : public nsIMed
 
   typedef dom::CallbackObjectHolder<dom::NavigatorUserMediaSuccessCallback,
                                     nsIDOMGetUserMediaSuccessCallback>
       GetUserMediaSuccessCallback;
   typedef dom::CallbackObjectHolder<dom::NavigatorUserMediaErrorCallback,
                                     nsIDOMGetUserMediaErrorCallback>
       GetUserMediaErrorCallback;
 
-  nsresult GetUserMedia(nsPIDOMWindowInner* aWindow,
-                        const dom::MediaStreamConstraints& aConstraints,
-                        GetUserMediaSuccessCallback&& onSuccess,
-                        GetUserMediaErrorCallback&& onError,
-                        dom::CallerType aCallerType);
+  static void CallOnError(const GetUserMediaErrorCallback* aCallback,
+                          dom::MediaStreamError& aError);
+  static void CallOnSuccess(const GetUserMediaSuccessCallback* aCallback,
+                            DOMMediaStream& aStream);
+
+  typedef nsTArray<RefPtr<MediaDevice>> MediaDeviceSet;
+  typedef media::Refcountable<MediaDeviceSet> MediaDeviceSetRefCnt;
+
+  typedef MozPromise<RefPtr<DOMMediaStream>, RefPtr<MediaMgrError>, true>
+      StreamPromise;
+  typedef MozPromise<RefPtr<MediaDeviceSetRefCnt>, RefPtr<MediaMgrError>, true>
+      MediaDeviceSetPromise;
+  typedef MozPromise<const char*, nsresult, false> BadConstraintsPromise;
+
+  RefPtr<StreamPromise> GetUserMedia(
+      nsPIDOMWindowInner* aWindow,
+      const dom::MediaStreamConstraints& aConstraints,
+      dom::CallerType aCallerType);
 
   nsresult GetUserMediaDevices(
       nsPIDOMWindowInner* aWindow,
       const dom::MediaStreamConstraints& aConstraints,
       dom::MozGetUserMediaDevicesSuccessCallback& aOnSuccess,
       uint64_t aInnerWindowID = 0, const nsAString& aCallID = nsString());
-
-  nsresult EnumerateDevices(nsPIDOMWindowInner* aWindow,
-                            nsIGetUserMediaDevicesSuccessCallback* aOnSuccess,
-                            nsIDOMGetUserMediaErrorCallback* aOnFailure,
-                            dom::CallerType aCallerType);
+  RefPtr<MediaDeviceSetPromise> EnumerateDevices(nsPIDOMWindowInner* aWindow,
+                                                 dom::CallerType aCallerType);
 
   nsresult EnumerateDevices(nsPIDOMWindowInner* aWindow,
                             dom::Promise& aPromise);
 
   // Get the sink that corresponds to the given device id.
   // It is resposible to check if an application is
   // authorized to play audio through the requested device.
   // The returned promise will be resolved with the device
@@ -246,19 +256,16 @@ class MediaManager final : public nsIMed
   RefPtr<SinkInfoPromise> GetSinkDevice(nsPIDOMWindowInner* aWindow,
                                         const nsString& aDeviceId);
 
   void OnNavigation(uint64_t aWindowID);
   bool IsActivelyCapturingOrHasAPermission(uint64_t aWindowId);
 
   MediaEnginePrefs mPrefs;
 
-  typedef nsTArray<RefPtr<MediaDevice>> MediaDeviceSet;
-  typedef media::Refcountable<UniquePtr<MediaDeviceSet>> MediaDeviceSetRefCnt;
-
   virtual int AddDeviceChangeCallback(DeviceChangeCallback* aCallback) override;
   virtual void OnDeviceChange() override;
 
  private:
   static nsresult GenerateUUID(nsAString& aResult);
   static nsresult AnonymizeId(nsAString& aId, const nsACString& aOriginKey);
 
  public:  // TODO: make private once we upgrade to GCC 4.8+ on linux.
@@ -270,36 +277,31 @@ class MediaManager final : public nsIMed
  private:
   enum class DeviceEnumerationType : uint8_t {
     Normal,  // Enumeration should not return loopback or fake devices
     Fake,    // Enumeration should return fake device(s)
     Loopback /* Enumeration should return loopback device(s) (possibly in
              addition to normal devices) */
   };
 
-  typedef MozPromise<RefPtr<MediaDeviceSetRefCnt>,
-                     RefPtr<dom::MediaStreamError>, true>
-      MediaDeviceSetPromise;
-  typedef MozPromise<const char*, nsresult, false> BadConstraintsPromise;
-
   RefPtr<MediaDeviceSetPromise> EnumerateRawDevices(
       uint64_t aWindowId, dom::MediaSourceEnum aVideoInputType,
       dom::MediaSourceEnum aAudioInputType, MediaSinkEnum aAudioOutputType,
       DeviceEnumerationType aVideoInputEnumType = DeviceEnumerationType::Normal,
       DeviceEnumerationType aAudioInputEnumType =
           DeviceEnumerationType::Normal);
 
   RefPtr<MediaDeviceSetPromise> EnumerateDevicesImpl(
       uint64_t aWindowId, dom::MediaSourceEnum aVideoInputType,
       dom::MediaSourceEnum aAudioInputType, MediaSinkEnum aAudioOutputType,
       DeviceEnumerationType aVideoInputEnumType,
       DeviceEnumerationType aAudioInputEnumType);
 
   RefPtr<BadConstraintsPromise> SelectSettings(
-      dom::MediaStreamConstraints& aConstraints, bool aIsChrome,
+      const dom::MediaStreamConstraints& aConstraints, bool aIsChrome,
       const RefPtr<MediaDeviceSetRefCnt>& aSources);
 
   void GetPref(nsIPrefBranch* aBranch, const char* aPref, const char* aData,
                int32_t* aVal);
   void GetPrefBool(nsIPrefBranch* aBranch, const char* aPref, const char* aData,
                    bool* aVal);
   void GetPrefs(nsIPrefBranch* aBranch, const char* aData);
 
--- a/dom/media/MediaStreamError.h
+++ b/dom/media/MediaStreamError.h
@@ -46,21 +46,21 @@ class BaseMediaMgrError {
     OverconstrainedError,
     SecurityError,
     TypeError,
   };
 
  protected:
   BaseMediaMgrError(Name aName, const nsAString& aMessage,
                     const nsAString& aConstraint);
+
+ public:
   nsString mNameString;
   nsString mMessage;
   const nsString mConstraint;
-
- private:
   const Name mName;
 };
 
 class MediaMgrError final : public nsISupports, public BaseMediaMgrError {
  public:
   explicit MediaMgrError(Name aName, const nsAString& aMessage = EmptyString(),
                          const nsAString& aConstraint = EmptyString())
       : BaseMediaMgrError(aName, aMessage, aConstraint) {}
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -3006,17 +3006,17 @@ void MediaInputPort::SetGraphImpl(MediaS
 }
 
 void MediaInputPort::BlockSourceTrackIdImpl(TrackID aTrackId,
                                             BlockingMode aBlockingMode) {
   mBlockedTracks.AppendElement(
       Pair<TrackID, BlockingMode>(aTrackId, aBlockingMode));
 }
 
-already_AddRefed<Pledge<bool>> MediaInputPort::BlockSourceTrackId(
+RefPtr<GenericPromise> MediaInputPort::BlockSourceTrackId(
     TrackID aTrackId, BlockingMode aBlockingMode) {
   class Message : public ControlMessage {
    public:
     Message(MediaInputPort* aPort, TrackID aTrackId, BlockingMode aBlockingMode,
             already_AddRefed<nsIRunnable> aRunnable)
         : ControlMessage(aPort->GetDestination()),
           mPort(aPort),
           mTrackId(aTrackId),
@@ -3033,25 +3033,27 @@ already_AddRefed<Pledge<bool>> MediaInpu
     RefPtr<MediaInputPort> mPort;
     TrackID mTrackId;
     BlockingMode mBlockingMode;
     nsCOMPtr<nsIRunnable> mRunnable;
   };
 
   MOZ_ASSERT(IsTrackIDExplicit(aTrackId), "Only explicit TrackID is allowed");
 
-  auto pledge = MakeRefPtr<Pledge<bool>>();
-  nsCOMPtr<nsIRunnable> runnable = NewRunnableFrom([pledge]() {
-    MOZ_ASSERT(NS_IsMainThread());
-    pledge->Resolve(true);
-    return NS_OK;
-  });
+  MozPromiseHolder<GenericPromise> holder;
+  RefPtr<GenericPromise> p = holder.Ensure(__func__);
+  nsCOMPtr<nsIRunnable> runnable =
+      NewRunnableFrom([h = std::move(holder)]() mutable {
+        MOZ_ASSERT(NS_IsMainThread());
+        h.Resolve(true, __func__);
+        return NS_OK;
+      });
   GraphImpl()->AppendMessage(
       MakeUnique<Message>(this, aTrackId, aBlockingMode, runnable.forget()));
-  return pledge.forget();
+  return p;
 }
 
 already_AddRefed<MediaInputPort> ProcessedMediaStream::AllocateInputPort(
     MediaStream* aStream, TrackID aTrackID, TrackID aDestTrackID,
     uint16_t aInputNumber, uint16_t aOutputNumber,
     nsTArray<TrackID>* aBlockedTracks) {
   // This method creates two references to the MediaInputPort: one for
   // the main thread, and one for the MediaStreamGraph.
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -44,21 +44,16 @@ class nsAutoRefTraits<SpeexResamplerStat
 namespace mozilla {
 
 extern LazyLogModule gMediaStreamGraphLog;
 
 namespace dom {
 enum class AudioContextOperation;
 }
 
-namespace media {
-template <typename V, typename E>
-class Pledge;
-}
-
 /*
  * MediaStreamGraph is a framework for synchronized audio/video processing
  * and playback. It is designed to be used by other browser components such as
  * HTML media elements, media capture APIs, real-time media streaming APIs,
  * multitrack media APIs, and advanced audio APIs.
  *
  * The MediaStreamGraph uses a dedicated thread to process media --- the media
  * graph thread. This ensures that we can process media through the graph
@@ -961,18 +956,18 @@ class MediaInputPort final {
   TrackID GetDestinationTrackId() const { return mDestTrack; }
 
   /**
    * Block aTrackId in the source stream from being passed through the port.
    * Consumers will interpret this track as ended.
    * Returns a pledge that resolves on the main thread after the track block has
    * been applied by the MSG.
    */
-  already_AddRefed<media::Pledge<bool, nsresult>> BlockSourceTrackId(
-      TrackID aTrackId, BlockingMode aBlockingMode);
+  RefPtr<GenericPromise> BlockSourceTrackId(TrackID aTrackId,
+                                            BlockingMode aBlockingMode);
 
  private:
   void BlockSourceTrackIdImpl(TrackID aTrackId, BlockingMode aBlockingMode);
 
  public:
   // Returns true if aTrackId has not been blocked for any reason and this port
   // has not been locked to another track.
   bool PassTrackThrough(TrackID aTrackId) const {
--- a/dom/media/MediaStreamTrack.cpp
+++ b/dom/media/MediaStreamTrack.cpp
@@ -40,23 +40,22 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Me
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaStreamTrackSource)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 auto MediaStreamTrackSource::ApplyConstraints(
-    nsPIDOMWindowInner* aWindow, const dom::MediaTrackConstraints& aConstraints,
-    CallerType aCallerType) -> already_AddRefed<PledgeVoid> {
-  RefPtr<PledgeVoid> p = new PledgeVoid();
-  p->Reject(new MediaStreamError(aWindow,
-                                 MediaStreamError::Name::OverconstrainedError,
-                                 NS_LITERAL_STRING("")));
-  return p.forget();
+    const dom::MediaTrackConstraints& aConstraints, CallerType aCallerType)
+    -> RefPtr<ApplyConstraintsPromise> {
+  return ApplyConstraintsPromise::CreateAndReject(
+      MakeRefPtr<MediaMgrError>(MediaMgrError::Name::OverconstrainedError,
+                                NS_LITERAL_STRING("")),
+      __func__);
 }
 
 /**
  * MSGListener monitors state changes of the media flowing through the
  * MediaStreamGraph.
  *
  *
  * For changes to PrincipalHandle the following applies:
@@ -303,19 +302,17 @@ void MediaStreamTrack::Stop() {
 
   mSource->UnregisterSink(mSink.get());
 
   MOZ_ASSERT(mOwningStream,
              "Every MediaStreamTrack needs an owning DOMMediaStream");
   DOMMediaStream::TrackPort* port = mOwningStream->FindOwnedTrackPort(*this);
   MOZ_ASSERT(port,
              "A MediaStreamTrack must exist in its owning DOMMediaStream");
-  RefPtr<Pledge<bool>> p =
-      port->BlockSourceTrackId(mInputTrackID, BlockingMode::CREATION);
-  Unused << p;
+  Unused << port->BlockSourceTrackId(mInputTrackID, BlockingMode::CREATION);
 
   mReadyState = MediaStreamTrackState::Ended;
 
   NotifyEnded();
 }
 
 void MediaStreamTrack::GetConstraints(dom::MediaTrackConstraints& aResult) {
   aResult = mConstraints;
@@ -343,44 +340,51 @@ already_AddRefed<Promise> MediaStreamTra
     nsString str;
     aConstraints.ToJSON(str);
 
     LOG(LogLevel::Info, ("MediaStreamTrack %p ApplyConstraints() with "
                          "constraints %s",
                          this, NS_ConvertUTF16toUTF8(str).get()));
   }
 
-  typedef media::Pledge<bool, MediaStreamError*> PledgeVoid;
-
   nsPIDOMWindowInner* window = mOwningStream->GetParentObject();
   nsIGlobalObject* go = window ? window->AsGlobal() : nullptr;
 
   RefPtr<Promise> promise = Promise::Create(go, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
   // Forward constraints to the source.
   //
   // After GetSource().ApplyConstraints succeeds (after it's been to
   // media-thread and back), and no sooner, do we set mConstraints to the newly
   // applied values.
 
   // Keep a reference to this, to make sure it's still here when we get back.
-  RefPtr<MediaStreamTrack> that = this;
-  RefPtr<PledgeVoid> p =
-      GetSource().ApplyConstraints(window, aConstraints, aCallerType);
-  p->Then(
-      [this, that, promise, aConstraints](bool& aDummy) mutable {
-        mConstraints = aConstraints;
-        promise->MaybeResolve(false);
-      },
-      [promise](MediaStreamError*& reason) mutable {
-        promise->MaybeReject(reason);
-      });
+  RefPtr<MediaStreamTrack> self(this);
+  GetSource()
+      .ApplyConstraints(aConstraints, aCallerType)
+      ->Then(
+          GetCurrentThreadSerialEventTarget(), __func__,
+          [this, self, promise, aConstraints](bool aDummy) {
+            nsPIDOMWindowInner* window = mOwningStream->GetParentObject();
+            if (!window || !window->IsCurrentInnerWindow()) {
+               return;  // Leave Promise pending after navigation by design.
+            }
+            mConstraints = aConstraints;
+            promise->MaybeResolve(false);
+          },
+          [this, self, promise](const RefPtr<MediaMgrError>& aError) {
+            nsPIDOMWindowInner* window = mOwningStream->GetParentObject();
+            if (!window || !window->IsCurrentInnerWindow()) {
+               return;  // Leave Promise pending after navigation by design.
+            }
+            promise->MaybeReject(MakeRefPtr<MediaStreamError>(window, *aError));
+          });
   return promise.forget();
 }
 
 MediaStreamGraph* MediaStreamTrack::Graph() {
   return GetOwnedStream()->Graph();
 }
 
 MediaStreamGraphImpl* MediaStreamTrack::GraphImpl() {
--- a/dom/media/MediaStreamTrack.h
+++ b/dom/media/MediaStreamTrack.h
@@ -30,22 +30,22 @@ class MediaStreamGraphImpl;
 class MediaStreamTrackListener;
 class DirectMediaStreamTrackListener;
 class PeerConnectionImpl;
 class PeerConnectionMedia;
 class PeerIdentity;
 class ProcessedMediaStream;
 class RemoteSourceStreamInfo;
 class SourceStreamInfo;
+class MediaMgrError;
 
 namespace dom {
 
 class AudioStreamTrack;
 class VideoStreamTrack;
-class MediaStreamError;
 class TrackSink;
 enum class CallerType : uint32_t;
 
 /**
  * Common interface through which a MediaStreamTrack can communicate with its
  * producer on the main thread.
  *
  * Kept alive by a strong ref in all MediaStreamTracks (original and clones)
@@ -150,24 +150,24 @@ class MediaStreamTrackSource : public ns
    * Forwards a photo request to backends that support it. Other backends return
    * NS_ERROR_NOT_IMPLEMENTED to indicate that a MediaStreamGraph-based fallback
    * should be used.
    */
   virtual nsresult TakePhoto(MediaEnginePhotoCallback*) const {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
-  typedef media::Pledge<bool, dom::MediaStreamError*> PledgeVoid;
+  typedef MozPromise<bool /* aIgnored */, RefPtr<MediaMgrError>, true>
+      ApplyConstraintsPromise;
 
   /**
    * We provide a fallback solution to ApplyConstraints() here.
    * Sources that support ApplyConstraints() will have to override it.
    */
-  virtual already_AddRefed<PledgeVoid> ApplyConstraints(
-      nsPIDOMWindowInner* aWindow,
+  virtual RefPtr<ApplyConstraintsPromise> ApplyConstraints(
       const dom::MediaTrackConstraints& aConstraints, CallerType aCallerType);
 
   /**
    * Same for GetSettings (no-op).
    */
   virtual void GetSettings(dom::MediaTrackSettings& aResult){};
 
   /**
--- a/dom/media/systemservices/MediaParent.h
+++ b/dom/media/systemservices/MediaParent.h
@@ -62,18 +62,16 @@ class Parent : public RefCountedParent, 
 
   Parent();
 
  private:
   virtual ~Parent();
 
   RefPtr<OriginKeyStore> mOriginKeyStore;
   bool mDestroyed;
-
-  CoatCheck<Pledge<nsCString>> mOutstandingPledges;
 };
 
 template <class Parent>
 mozilla::ipc::IPCResult IPCResult(Parent* aSelf, bool aSuccess);
 
 template <>
 inline mozilla::ipc::IPCResult IPCResult(Parent<PMediaParent>* aSelf,
                                          bool aSuccess) {
--- a/dom/media/systemservices/MediaUtils.h
+++ b/dom/media/systemservices/MediaUtils.h
@@ -19,134 +19,16 @@
 #include "nsISupportsImpl.h"
 #include "nsThreadUtils.h"
 
 class nsIEventTarget;
 
 namespace mozilla {
 namespace media {
 
-/*
- * media::Pledge - A promise-like pattern for c++ that takes lambda functions.
- *
- * Asynchronous APIs that proxy to another thread or to the chrome process and
- * back may find it useful to return a pledge to callers who then use
- * pledge.Then(func) to specify a lambda function to be invoked with the result
- * later back on this same thread.
- *
- * Callers will enjoy that lambdas allow "capturing" of local variables, much
- * like closures in JavaScript (safely by-copy by default).
- *
- * Callers will also enjoy that they do not need to be thread-safe (their code
- * runs on the same thread after all).
- *
- * Advantageously, pledges are non-threadsafe by design (because locking and
- * event queues are redundant). This means none of the lambdas you pass in,
- * or variables you lambda-capture into them, need be threasafe or support
- * threadsafe refcounting. After all, they'll run later on the same thread.
- *
- *   RefPtr<media::Pledge<Foo>> p = GetFooAsynchronously(); // returns a pledge
- *   p->Then([](const Foo& foo) {
- *     // use foo here (same thread. Need not be thread-safe!)
- *   });
- *
- * See media::CoatCheck below for an example of GetFooAsynchronously().
- */
-
-class PledgeBase {
- public:
-  NS_INLINE_DECL_REFCOUNTING(PledgeBase);
-
- protected:
-  virtual ~PledgeBase(){};
-};
-
-template <typename ValueType, typename ErrorType = nsresult>
-class Pledge : public PledgeBase {
-  // TODO: Remove workaround once mozilla allows std::function from <functional>
-  // wo/std::function support, do template + virtual trick to accept lambdas
-  class FunctorsBase {
-   public:
-    FunctorsBase() {}
-    virtual void Succeed(ValueType& result) = 0;
-    virtual void Fail(ErrorType& error) = 0;
-    virtual ~FunctorsBase(){};
-  };
-
- public:
-  explicit Pledge() : mDone(false), mRejected(false) {}
-  Pledge(const Pledge& aOther) = delete;
-  Pledge& operator=(const Pledge&) = delete;
-
-  template <typename OnSuccessType>
-  void Then(OnSuccessType&& aOnSuccess) {
-    Then(std::forward<OnSuccessType>(aOnSuccess), [](ErrorType&) {});
-  }
-
-  template <typename OnSuccessType, typename OnFailureType>
-  void Then(OnSuccessType&& aOnSuccess, OnFailureType&& aOnFailure) {
-    class Functors : public FunctorsBase {
-     public:
-      Functors(OnSuccessType&& aOnSuccessRef, OnFailureType&& aOnFailureRef)
-          : mOnSuccess(std::move(aOnSuccessRef)),
-            mOnFailure(std::move(aOnFailureRef)) {}
-
-      void Succeed(ValueType& result) { mOnSuccess(result); }
-      void Fail(ErrorType& error) { mOnFailure(error); };
-
-      OnSuccessType mOnSuccess;
-      OnFailureType mOnFailure;
-    };
-    mFunctors = MakeUnique<Functors>(std::forward<OnSuccessType>(aOnSuccess),
-                                     std::forward<OnFailureType>(aOnFailure));
-    if (mDone) {
-      if (!mRejected) {
-        mFunctors->Succeed(mValue);
-      } else {
-        mFunctors->Fail(mError);
-      }
-    }
-  }
-
-  void Resolve(const ValueType& aValue) {
-    mValue = aValue;
-    Resolve();
-  }
-
-  void Reject(ErrorType rv) {
-    if (!mDone) {
-      mDone = mRejected = true;
-      mError = rv;
-      if (mFunctors) {
-        mFunctors->Fail(mError);
-      }
-    }
-  }
-
- protected:
-  void Resolve() {
-    if (!mDone) {
-      mDone = true;
-      MOZ_ASSERT(!mRejected);
-      if (mFunctors) {
-        mFunctors->Succeed(mValue);
-      }
-    }
-  }
-
-  ValueType mValue;
-
- private:
-  ~Pledge(){};
-  bool mDone;
-  bool mRejected;
-  ErrorType mError;
-  UniquePtr<FunctorsBase> mFunctors;
-};
-
 /* media::NewRunnableFrom() - Create a Runnable from a lambda.
  *
  * Passing variables (closures) to an async function is clunky with Runnable:
  *
  *   void Foo()
  *   {
  *     class FooRunnable : public Runnable
  *     {
@@ -195,111 +77,16 @@ class LambdaRunnable : public Runnable {
 template <typename OnRunType>
 already_AddRefed<LambdaRunnable<OnRunType>> NewRunnableFrom(
     OnRunType&& aOnRun) {
   typedef LambdaRunnable<OnRunType> LambdaType;
   RefPtr<LambdaType> lambda = new LambdaType(std::forward<OnRunType>(aOnRun));
   return lambda.forget();
 }
 
-/* media::CoatCheck - There and back again. Park an object in exchange for an
- * id.
- *
- * A common problem with calling asynchronous functions that do work on other
- * threads or processes is how to pass in a heap object for use once the
- * function completes, without requiring that object to have threadsafe
- * refcounting, contain mutexes, be marshaled, or leak if things fail
- * (or worse, intermittent use-after-free because of lifetime issues).
- *
- * One solution is to set up a coat-check on the caller side, park your object
- * in exchange for an id, and send the id. Common in IPC, but equally useful
- * for same-process thread-hops, because by never leaving the thread there's
- * no need for objects to be threadsafe or use threadsafe refcounting. E.g.
- *
- *   class FooDoer
- *   {
- *     CoatCheck<Foo> mOutstandingFoos;
- *
- *   public:
- *     void DoFoo()
- *     {
- *       RefPtr<Foo> foo = new Foo();
- *       uint32_t requestId = mOutstandingFoos.Append(*foo);
- *       sChild->SendFoo(requestId);
- *     }
- *
- *     void RecvFooResponse(uint32_t requestId)
- *     {
- *       RefPtr<Foo> foo = mOutstandingFoos.Remove(requestId);
- *       if (foo) {
- *         // use foo
- *       }
- *     }
- *   };
- *
- * If you read media::Pledge earlier, here's how this is useful for pledges:
- *
- *   class FooGetter
- *   {
- *     CoatCheck<Pledge<Foo>> mOutstandingPledges;
- *
- *   public:
- *     already_addRefed<Pledge<Foo>> GetFooAsynchronously()
- *     {
- *       RefPtr<Pledge<Foo>> p = new Pledge<Foo>();
- *       uint32_t requestId = mOutstandingPledges.Append(*p);
- *       sChild->SendFoo(requestId);
- *       return p.forget();
- *     }
- *
- *     void RecvFooResponse(uint32_t requestId, const Foo& fooResult)
- *     {
- *       RefPtr<Foo> p = mOutstandingPledges.Remove(requestId);
- *       if (p) {
- *         p->Resolve(fooResult);
- *       }
- *     }
- *   };
- *
- * This helper is currently optimized for very small sets (i.e. not optimized).
- * It is also not thread-safe as the whole point is to stay on the same thread.
- */
-
-template <class T>
-class CoatCheck {
- public:
-  typedef std::pair<uint32_t, RefPtr<T>> Element;
-
-  uint32_t Append(T& t) {
-    uint32_t id = GetNextId();
-    mElements.AppendElement(Element(id, RefPtr<T>(&t)));
-    return id;
-  }
-
-  already_AddRefed<T> Remove(uint32_t aId) {
-    for (auto& element : mElements) {
-      if (element.first == aId) {
-        RefPtr<T> ref;
-        ref.swap(element.second);
-        mElements.RemoveElement(element);
-        return ref.forget();
-      }
-    }
-    MOZ_ASSERT_UNREACHABLE("Received id with no matching parked object!");
-    return nullptr;
-  }
-
- private:
-  static uint32_t GetNextId() {
-    static uint32_t counter = 0;
-    return ++counter;
-  };
-  AutoTArray<Element, 3> mElements;
-};
-
 /* media::Refcountable - Add threadsafe ref-counting to something that isn't.
  *
  * Often, reference counting is the most practical way to share an object with
  * another thread without imposing lifetime restrictions, even if there's
  * otherwise no concurrent access happening on the object.  For instance, an
  * algorithm on another thread may find it more expedient to modify a passed-in
  * object, rather than pass expensive copies back and forth.
  *
--- a/dom/media/webaudio/AudioContext.cpp
+++ b/dom/media/webaudio/AudioContext.cpp
@@ -659,17 +659,17 @@ uint32_t AudioContext::MaxChannelCount()
 }
 
 uint32_t AudioContext::ActiveNodeCount() const { return mActiveNodes.Count(); }
 
 MediaStreamGraph* AudioContext::Graph() const {
   return Destination()->Stream()->Graph();
 }
 
-MediaStream* AudioContext::DestinationStream() const {
+AudioNodeStream* AudioContext::DestinationStream() const {
   if (Destination()) {
     return Destination()->Stream();
   }
   return nullptr;
 }
 
 double AudioContext::CurrentTime() {
   MediaStream* stream = Destination()->Stream();
@@ -944,19 +944,18 @@ void AudioContext::SuspendInternal(void*
   nsTArray<MediaStream*> streams;
   // If mSuspendCalled is true then we already suspended all our streams,
   // so don't suspend them again (since suspend(); suspend(); resume(); should
   // cancel both suspends). But we still need to do ApplyAudioContextOperation
   // to ensure our new promise is resolved.
   if (!mSuspendCalled) {
     streams = GetAllStreams();
   }
-  Graph()->ApplyAudioContextOperation(DestinationStream()->AsAudioNodeStream(),
-                                      streams, AudioContextOperation::Suspend,
-                                      aPromise);
+  Graph()->ApplyAudioContextOperation(DestinationStream(), streams,
+                                      AudioContextOperation::Suspend, aPromise);
 
   mSuspendCalled = true;
 }
 
 already_AddRefed<Promise> AudioContext::Resume(ErrorResult& aRv) {
   nsCOMPtr<nsIGlobalObject> parentObject = do_QueryInterface(GetParentObject());
   RefPtr<Promise> promise;
   promise = Promise::Create(parentObject, aRv);
@@ -996,19 +995,18 @@ void AudioContext::ResumeInternal() {
   nsTArray<MediaStream*> streams;
   // If mSuspendCalled is false then we already resumed all our streams,
   // so don't resume them again (since suspend(); resume(); resume(); should
   // be OK). But we still need to do ApplyAudioContextOperation
   // to ensure our new promise is resolved.
   if (mSuspendCalled) {
     streams = GetAllStreams();
   }
-  Graph()->ApplyAudioContextOperation(DestinationStream()->AsAudioNodeStream(),
-                                      streams, AudioContextOperation::Resume,
-                                      nullptr);
+  Graph()->ApplyAudioContextOperation(DestinationStream(), streams,
+                                      AudioContextOperation::Resume, nullptr);
   mSuspendCalled = false;
 }
 
 void AudioContext::DispatchBlockedEvent() {
   if (!StaticPrefs::MediaBlockEventEnabled()) {
     return;
   }
 
@@ -1055,26 +1053,26 @@ already_AddRefed<Promise> AudioContext::
   if (Destination()) {
     Destination()->DestroyAudioChannelAgent();
   }
 
   mPromiseGripArray.AppendElement(promise);
 
   // This can be called when freeing a document, and the streams are dead at
   // this point, so we need extra null-checks.
-  MediaStream* ds = DestinationStream();
+  AudioNodeStream* ds = DestinationStream();
   if (ds) {
     nsTArray<MediaStream*> streams;
     // If mSuspendCalled or mCloseCalled are true then we already suspended
     // all our streams, so don't suspend them again. But we still need to do
     // ApplyAudioContextOperation to ensure our new promise is resolved.
     if (!mSuspendCalled && !mCloseCalled) {
       streams = GetAllStreams();
     }
-    Graph()->ApplyAudioContextOperation(ds->AsAudioNodeStream(), streams,
+    Graph()->ApplyAudioContextOperation(ds, streams,
                                         AudioContextOperation::Close, promise);
   }
   mCloseCalled = true;
 
   return promise.forget();
 }
 
 void AudioContext::RegisterNode(AudioNode* aNode) {
--- a/dom/media/webaudio/AudioContext.h
+++ b/dom/media/webaudio/AudioContext.h
@@ -267,17 +267,17 @@ class AudioContext final : public DOMEve
   // OfflineAudioContext methods
   already_AddRefed<Promise> StartRendering(ErrorResult& aRv);
   IMPL_EVENT_HANDLER(complete)
   unsigned long Length();
 
   bool IsOffline() const { return mIsOffline; }
 
   MediaStreamGraph* Graph() const;
-  MediaStream* DestinationStream() const;
+  AudioNodeStream* DestinationStream() const;
 
   // Nodes register here if they will produce sound even if they have silent
   // or no input connections.  The AudioContext will keep registered nodes
   // alive until the context is collected.  This takes care of "playing"
   // references and "tail-time" references.
   void RegisterActiveNode(AudioNode* aNode);
   // Nodes unregister when they have finished producing sound for the
   // foreseeable future.
--- a/dom/media/webaudio/AudioWorkletImpl.cpp
+++ b/dom/media/webaudio/AudioWorkletImpl.cpp
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 
 #include "AudioWorkletImpl.h"
 
 #include "AudioContext.h"
+#include "AudioNodeStream.h"
 #include "mozilla/dom/AudioWorkletBinding.h"
 #include "mozilla/dom/AudioWorkletGlobalScope.h"
 #include "mozilla/dom/Worklet.h"
 #include "mozilla/dom/WorkletThread.h"
 #include "nsGlobalWindowInner.h"
 
 namespace mozilla {
 
@@ -26,23 +27,26 @@ namespace mozilla {
   }
   nsCOMPtr<nsIPrincipal> principal =
       nsGlobalWindowInner::Cast(window)->GetPrincipal();
   if (NS_WARN_IF(!principal)) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
-  RefPtr<AudioWorkletImpl> impl = new AudioWorkletImpl(window, principal);
+  RefPtr<AudioWorkletImpl> impl =
+      new AudioWorkletImpl(window, principal, aContext->DestinationStream());
   return MakeAndAddRef<dom::Worklet>(window, std::move(impl));
 }
 
 AudioWorkletImpl::AudioWorkletImpl(nsPIDOMWindowInner* aWindow,
-                                   nsIPrincipal* aPrincipal)
-    : WorkletImpl(aWindow, aPrincipal) {}
+                                   nsIPrincipal* aPrincipal,
+                                   AudioNodeStream* aDestinationStream)
+    : WorkletImpl(aWindow, aPrincipal),
+      mDestinationStream(aDestinationStream) {}
 
 AudioWorkletImpl::~AudioWorkletImpl() = default;
 
 JSObject* AudioWorkletImpl::WrapWorklet(JSContext* aCx, dom::Worklet* aWorklet,
                                         JS::Handle<JSObject*> aGivenProto) {
   MOZ_ASSERT(NS_IsMainThread());
   return dom::AudioWorklet_Binding::Wrap(aCx, aWorklet, aGivenProto);
 }
--- a/dom/media/webaudio/AudioWorkletImpl.h
+++ b/dom/media/webaudio/AudioWorkletImpl.h
@@ -6,16 +6,18 @@
 
 #ifndef AudioWorkletImpl_h
 #define AudioWorkletImpl_h
 
 #include "mozilla/dom/WorkletImpl.h"
 
 namespace mozilla {
 
+class AudioNodeStream;
+
 namespace dom {
 class AudioContext;
 }
 
 class AudioWorkletImpl final : public WorkletImpl {
  public:
   // Methods for parent thread only:
 
@@ -25,15 +27,18 @@ class AudioWorkletImpl final : public Wo
   JSObject* WrapWorklet(JSContext* aCx, dom::Worklet* aWorklet,
                         JS::Handle<JSObject*> aGivenProto) override;
 
  protected:
   // Execution thread only.
   already_AddRefed<dom::WorkletGlobalScope> ConstructGlobalScope() override;
 
  private:
-  AudioWorkletImpl(nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal);
+  AudioWorkletImpl(nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal,
+                   AudioNodeStream* aDestinationStream);
   ~AudioWorkletImpl();
+
+  const RefPtr<AudioNodeStream> mDestinationStream;
 };
 
 }  // namespace mozilla
 
 #endif  // AudioWorkletImpl_h
--- a/dom/media/webspeech/recognition/SpeechRecognition.cpp
+++ b/dom/media/webspeech/recognition/SpeechRecognition.cpp
@@ -671,23 +671,42 @@ void SpeechRecognition::Start(const Opti
     for (const RefPtr<AudioStreamTrack>& track : tracks) {
       if (!track->Ended()) {
         NotifyTrackAdded(track);
         break;
       }
     }
   } else {
     AutoNoJSAPI nojsapi;
-    MediaManager* manager = MediaManager::Get();
-    MediaManager::GetUserMediaSuccessCallback onsuccess(
-        new GetUserMediaSuccessCallback(this));
-    MediaManager::GetUserMediaErrorCallback onerror(
-        new GetUserMediaErrorCallback(this));
-    manager->GetUserMedia(GetOwner(), constraints, std::move(onsuccess),
-                          std::move(onerror), aCallerType);
+    RefPtr<SpeechRecognition> self(this);
+    MediaManager::Get()
+        ->GetUserMedia(GetOwner(), constraints, aCallerType)
+        ->Then(GetCurrentThreadSerialEventTarget(), __func__,
+               [this, self](RefPtr<DOMMediaStream>&& aStream) {
+                 mStream = std::move(aStream);
+                 mStream->RegisterTrackListener(this);
+                 nsTArray<RefPtr<AudioStreamTrack>> tracks;
+                 mStream->GetAudioTracks(tracks);
+                 for (const RefPtr<AudioStreamTrack>& track : tracks) {
+                   if (!track->Ended()) {
+                     NotifyTrackAdded(track);
+                   }
+                 }
+               },
+               [this, self](RefPtr<MediaMgrError>&& error) {
+                 SpeechRecognitionErrorCode errorCode;
+
+                 if (error->mName == MediaMgrError::Name::NotAllowedError) {
+                   errorCode = SpeechRecognitionErrorCode::Not_allowed;
+                 } else {
+                   errorCode = SpeechRecognitionErrorCode::Audio_capture;
+                 }
+                 DispatchError(SpeechRecognition::EVENT_AUDIO_ERROR, errorCode,
+                               error->mMessage);
+               });
   }
 
   RefPtr<SpeechEvent> event = new SpeechEvent(this, EVENT_START);
   NS_DispatchToMainThread(event);
 }
 
 bool SpeechRecognition::SetRecognitionService(ErrorResult& aRv) {
   // See:
@@ -966,58 +985,11 @@ SpeechEvent::SpeechEvent(SpeechRecogniti
 SpeechEvent::~SpeechEvent() { delete mAudioSegment; }
 
 NS_IMETHODIMP
 SpeechEvent::Run() {
   mRecognition->ProcessEvent(this);
   return NS_OK;
 }
 
-NS_IMPL_ISUPPORTS(SpeechRecognition::GetUserMediaSuccessCallback,
-                  nsIDOMGetUserMediaSuccessCallback)
-
-NS_IMETHODIMP
-SpeechRecognition::GetUserMediaSuccessCallback::OnSuccess(
-    nsISupports* aStream) {
-  RefPtr<DOMMediaStream> stream = do_QueryObject(aStream);
-  if (!stream) {
-    return NS_ERROR_NO_INTERFACE;
-  }
-  mRecognition->mStream = stream;
-  mRecognition->mStream->RegisterTrackListener(mRecognition);
-  nsTArray<RefPtr<AudioStreamTrack>> tracks;
-  mRecognition->mStream->GetAudioTracks(tracks);
-  for (const RefPtr<AudioStreamTrack>& track : tracks) {
-    if (!track->Ended()) {
-      mRecognition->NotifyTrackAdded(track);
-    }
-  }
-  return NS_OK;
-}
-
-NS_IMPL_ISUPPORTS(SpeechRecognition::GetUserMediaErrorCallback,
-                  nsIDOMGetUserMediaErrorCallback)
-
-NS_IMETHODIMP
-SpeechRecognition::GetUserMediaErrorCallback::OnError(nsISupports* aError) {
-  RefPtr<MediaStreamError> error = do_QueryObject(aError);
-  if (!error) {
-    return NS_OK;
-  }
-  SpeechRecognitionErrorCode errorCode;
-
-  nsAutoString name;
-  error->GetName(name);
-  if (name.EqualsLiteral("PERMISSION_DENIED")) {
-    errorCode = SpeechRecognitionErrorCode::Not_allowed;
-  } else {
-    errorCode = SpeechRecognitionErrorCode::Audio_capture;
-  }
-
-  nsAutoString message;
-  error->GetMessage(message);
-  mRecognition->DispatchError(SpeechRecognition::EVENT_AUDIO_ERROR, errorCode,
-                              message);
-  return NS_OK;
-}
 
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/media/webspeech/recognition/SpeechRecognition.h
+++ b/dom/media/webspeech/recognition/SpeechRecognition.h
@@ -156,44 +156,16 @@ class SpeechRecognition final : public D
   };
 
   void SetState(FSMState state);
   bool StateBetween(FSMState begin, FSMState end);
 
   bool SetRecognitionService(ErrorResult& aRv);
   bool ValidateAndSetGrammarList(ErrorResult& aRv);
 
-  class GetUserMediaSuccessCallback : public nsIDOMGetUserMediaSuccessCallback {
-   public:
-    NS_DECL_ISUPPORTS
-    NS_DECL_NSIDOMGETUSERMEDIASUCCESSCALLBACK
-
-    explicit GetUserMediaSuccessCallback(SpeechRecognition* aRecognition)
-        : mRecognition(aRecognition) {}
-
-   private:
-    virtual ~GetUserMediaSuccessCallback() {}
-
-    RefPtr<SpeechRecognition> mRecognition;
-  };
-
-  class GetUserMediaErrorCallback : public nsIDOMGetUserMediaErrorCallback {
-   public:
-    NS_DECL_ISUPPORTS
-    NS_DECL_NSIDOMGETUSERMEDIAERRORCALLBACK
-
-    explicit GetUserMediaErrorCallback(SpeechRecognition* aRecognition)
-        : mRecognition(aRecognition) {}
-
-   private:
-    virtual ~GetUserMediaErrorCallback() {}
-
-    RefPtr<SpeechRecognition> mRecognition;
-  };
-
   NS_IMETHOD StartRecording(RefPtr<AudioStreamTrack>& aDOMStream);
   NS_IMETHOD StopRecording();
 
   uint32_t ProcessAudioSegment(AudioSegment* aSegment, TrackRate aTrackRate);
   void NotifyError(SpeechEvent* aEvent);
 
   void ProcessEvent(SpeechEvent* aEvent);
   void Transition(SpeechEvent* aEvent);
--- a/dom/serviceworkers/PServiceWorkerRegistration.ipdl
+++ b/dom/serviceworkers/PServiceWorkerRegistration.ipdl
@@ -18,12 +18,13 @@ parent:
 
   async Unregister() returns (bool aSuccess, CopyableErrorResult aRv);
   async Update() returns (IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult aResult);
 
 child:
   async __delete__();
 
   async UpdateState(IPCServiceWorkerRegistrationDescriptor aDescriptor);
+  async FireUpdateFound();
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/serviceworkers/RemoteServiceWorkerRegistrationImpl.cpp
+++ b/dom/serviceworkers/RemoteServiceWorkerRegistrationImpl.cpp
@@ -164,10 +164,18 @@ RemoteServiceWorkerRegistrationImpl::Rev
 void
 RemoteServiceWorkerRegistrationImpl::UpdateState(const ServiceWorkerRegistrationDescriptor& aDescriptor)
 {
   if (mOuter) {
     mOuter->UpdateState(aDescriptor);
   }
 }
 
+void
+RemoteServiceWorkerRegistrationImpl::FireUpdateFound()
+{
+  if (mOuter) {
+    mOuter->MaybeDispatchUpdateFoundRunnable();
+  }
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/serviceworkers/RemoteServiceWorkerRegistrationImpl.h
+++ b/dom/serviceworkers/RemoteServiceWorkerRegistrationImpl.h
@@ -44,15 +44,18 @@ public:
   explicit RemoteServiceWorkerRegistrationImpl(const ServiceWorkerRegistrationDescriptor& aDescriptor);
 
   void
   RevokeActor(ServiceWorkerRegistrationChild* aActor);
 
   void
   UpdateState(const ServiceWorkerRegistrationDescriptor& aDescriptor);
 
+  void
+  FireUpdateFound();
+
   NS_INLINE_DECL_REFCOUNTING(RemoteServiceWorkerRegistrationImpl, override)
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_remoteserviceworkerregistrationimpl_h__
--- a/dom/serviceworkers/ServiceWorkerRegistration.cpp
+++ b/dom/serviceworkers/ServiceWorkerRegistration.cpp
@@ -43,17 +43,16 @@ const uint64_t kInvalidUpdateFoundId = 0
 ServiceWorkerRegistration::ServiceWorkerRegistration(nsIGlobalObject* aGlobal,
                                                      const ServiceWorkerRegistrationDescriptor& aDescriptor,
                                                      ServiceWorkerRegistration::Inner* aInner)
   : DOMEventTargetHelper(aGlobal)
   , mDescriptor(aDescriptor)
   , mInner(aInner)
   , mScheduledUpdateFoundId(kInvalidUpdateFoundId)
   , mDispatchedUpdateFoundId(kInvalidUpdateFoundId)
-  , mPendingUpdatePromises(0)
 {
   MOZ_DIAGNOSTIC_ASSERT(mInner);
 
   KeepAliveIfHasListenersFor(NS_LITERAL_STRING("updatefound"));
 
   UpdateState(mDescriptor);
   mInner->SetServiceWorkerRegistration(this);
 }
@@ -219,32 +218,28 @@ ServiceWorkerRegistration::Update(ErrorR
 
   RefPtr<Promise> outer = Promise::Create(global, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   RefPtr<ServiceWorkerRegistration> self = this;
 
-  mPendingUpdatePromises += 1;
-
   mInner->Update(
     [outer, self](const ServiceWorkerRegistrationDescriptor& aDesc) {
-      auto scopeExit = MakeScopeExit([&] { self->UpdatePromiseSettled(); });
       nsIGlobalObject* global = self->GetParentObject();
       MOZ_DIAGNOSTIC_ASSERT(global);
       RefPtr<ServiceWorkerRegistration> ref =
         global->GetOrCreateServiceWorkerRegistration(aDesc);
       if (!ref) {
         outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
         return;
       }
       outer->MaybeResolve(ref);
     }, [outer, self] (ErrorResult& aRv) {
-      auto scopeExit = MakeScopeExit([&] { self->UpdatePromiseSettled(); });
       outer->MaybeReject(aRv);
     });
 
   return outer.forget();
 }
 
 already_AddRefed<Promise>
 ServiceWorkerRegistration::Unregister(ErrorResult& aRv)
@@ -377,16 +372,20 @@ ServiceWorkerRegistration::WhenVersionRe
 
   mVersionCallbackList.AppendElement(
     MakeUnique<VersionCallback>(aVersion, std::move(aCallback)));
 }
 
 void
 ServiceWorkerRegistration::MaybeScheduleUpdateFound(const Maybe<ServiceWorkerDescriptor>& aInstallingDescriptor)
 {
+  // This function sets mScheduledUpdateFoundId to note when we were told about
+  // a new installing worker. We rely on a call to
+  // MaybeDispatchUpdateFoundRunnable (called indirectly from UpdateJobCallback)
+  // to actually fire the event.
   uint64_t newId = aInstallingDescriptor.isSome()
                  ? aInstallingDescriptor.ref().Id()
                  : kInvalidUpdateFoundId;
 
   if (mScheduledUpdateFoundId != kInvalidUpdateFoundId) {
     if (mScheduledUpdateFoundId == newId) {
       return;
     }
@@ -397,18 +396,22 @@ ServiceWorkerRegistration::MaybeSchedule
   bool updateFound = newId != kInvalidUpdateFoundId &&
                      mDispatchedUpdateFoundId != newId;
 
   if (!updateFound) {
     return;
   }
 
   mScheduledUpdateFoundId = newId;
+}
 
-  if (mPendingUpdatePromises > 0) {
+void
+ServiceWorkerRegistration::MaybeDispatchUpdateFoundRunnable()
+{
+  if (mScheduledUpdateFoundId == kInvalidUpdateFoundId) {
     return;
   }
 
   nsIGlobalObject* global = GetParentObject();
   NS_ENSURE_TRUE_VOID(global);
 
   nsCOMPtr<nsIRunnable> r = NewCancelableRunnableMethod(
     "ServiceWorkerRegistration::MaybeDispatchUpdateFound",
@@ -430,38 +433,16 @@ ServiceWorkerRegistration::MaybeDispatch
     return;
   }
 
   mDispatchedUpdateFoundId = scheduledId;
   DispatchTrustedEvent(NS_LITERAL_STRING("updatefound"));
 }
 
 void
-ServiceWorkerRegistration::UpdatePromiseSettled()
-{
-  MOZ_DIAGNOSTIC_ASSERT(mPendingUpdatePromises > 0);
-  mPendingUpdatePromises -= 1;
-  if (mPendingUpdatePromises > 0 ||
-      mScheduledUpdateFoundId == kInvalidUpdateFoundId) {
-    return;
-  }
-
-  nsIGlobalObject* global = GetParentObject();
-  NS_ENSURE_TRUE_VOID(global);
-
-  nsCOMPtr<nsIRunnable> r = NewCancelableRunnableMethod(
-    "ServiceWorkerRegistration::MaybeDispatchUpdateFound",
-    this,
-    &ServiceWorkerRegistration::MaybeDispatchUpdateFound);
-
-  Unused << global->EventTargetFor(TaskCategory::Other)->Dispatch(
-    r.forget(), NS_DISPATCH_NORMAL);
-}
-
-void
 ServiceWorkerRegistration::UpdateStateInternal(const Maybe<ServiceWorkerDescriptor>& aInstalling,
                                                const Maybe<ServiceWorkerDescriptor>& aWaiting,
                                                const Maybe<ServiceWorkerDescriptor>& aActive)
 {
   // Do this immediately as it may flush an already pending updatefound
   // event.  In that case we want to fire the pending event before
   // modifying any of the registration properties.
   MaybeScheduleUpdateFound(aInstalling);
--- a/dom/serviceworkers/ServiceWorkerRegistration.h
+++ b/dom/serviceworkers/ServiceWorkerRegistration.h
@@ -117,16 +117,19 @@ public:
                    ErrorResult& aRv);
 
   const ServiceWorkerRegistrationDescriptor&
   Descriptor() const;
 
   void
   WhenVersionReached(uint64_t aVersion, ServiceWorkerBoolCallback&& aCallback);
 
+  void
+  MaybeDispatchUpdateFoundRunnable();
+
 private:
   ServiceWorkerRegistration(nsIGlobalObject* aGlobal,
                             const ServiceWorkerRegistrationDescriptor& aDescriptor,
                             Inner* aInner);
 
   ~ServiceWorkerRegistration();
 
   void
@@ -135,19 +138,16 @@ private:
                       const Maybe<ServiceWorkerDescriptor>& aActive);
 
   void
   MaybeScheduleUpdateFound(const Maybe<ServiceWorkerDescriptor>& aInstallingDescriptor);
 
   void
   MaybeDispatchUpdateFound();
 
-  void
-  UpdatePromiseSettled();
-
   ServiceWorkerRegistrationDescriptor mDescriptor;
   RefPtr<Inner> mInner;
 
   RefPtr<ServiceWorker> mInstallingWorker;
   RefPtr<ServiceWorker> mWaitingWorker;
   RefPtr<ServiceWorker> mActiveWorker;
   RefPtr<PushManager> mPushManager;
 
@@ -162,17 +162,16 @@ private:
     {
       MOZ_DIAGNOSTIC_ASSERT(mFunc);
     }
   };
   nsTArray<UniquePtr<VersionCallback>> mVersionCallbackList;
 
   uint64_t mScheduledUpdateFoundId;
   uint64_t mDispatchedUpdateFoundId;
-  uint32_t mPendingUpdatePromises;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(ServiceWorkerRegistration, NS_DOM_SERVICEWORKERREGISTRATION_IID)
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* mozilla_dom_ServiceWorkerRegistration_h */
--- a/dom/serviceworkers/ServiceWorkerRegistrationChild.cpp
+++ b/dom/serviceworkers/ServiceWorkerRegistrationChild.cpp
@@ -31,16 +31,25 @@ IPCResult
 ServiceWorkerRegistrationChild::RecvUpdateState(const IPCServiceWorkerRegistrationDescriptor& aDescriptor)
 {
   if (mOwner) {
     mOwner->UpdateState(ServiceWorkerRegistrationDescriptor(aDescriptor));
   }
   return IPC_OK();
 }
 
+IPCResult
+ServiceWorkerRegistrationChild::RecvFireUpdateFound()
+{
+  if (mOwner) {
+    mOwner->FireUpdateFound();
+  }
+  return IPC_OK();
+}
+
 void
 ServiceWorkerRegistrationChild::WorkerShuttingDown()
 {
   MaybeStartTeardown();
 }
 
 ServiceWorkerRegistrationChild::ServiceWorkerRegistrationChild(WorkerHolderToken* aWorkerHolderToken)
   : mWorkerHolderToken(aWorkerHolderToken)
--- a/dom/serviceworkers/ServiceWorkerRegistrationChild.h
+++ b/dom/serviceworkers/ServiceWorkerRegistrationChild.h
@@ -24,16 +24,19 @@ class ServiceWorkerRegistrationChild fin
 
   // PServiceWorkerRegistrationChild
   void
   ActorDestroy(ActorDestroyReason aReason) override;
 
   mozilla::ipc::IPCResult
   RecvUpdateState(const IPCServiceWorkerRegistrationDescriptor& aDescriptor) override;
 
+  mozilla::ipc::IPCResult
+  RecvFireUpdateFound() override;
+
   // WorkerHolderToken::Listener
   void
   WorkerShuttingDown() override;
 
 public:
   explicit ServiceWorkerRegistrationChild(WorkerHolderToken* aWorkerHolderToken);
   ~ServiceWorkerRegistrationChild() = default;
 
--- a/dom/serviceworkers/ServiceWorkerRegistrationImpl.cpp
+++ b/dom/serviceworkers/ServiceWorkerRegistrationImpl.cpp
@@ -98,16 +98,18 @@ ServiceWorkerRegistrationMainThread::Reg
   // runnable to call this method is in the event queue.  Double check
   // whether there is still anything to do here.
   if (mOuter) {
     mOuter->RegistrationRemoved();
   }
   StopListeningForEvents();
 }
 
+// NB: These functions use NS_ENSURE_TRUE_VOID to be noisy about preconditions
+// that would otherwise cause things to silently not happen if they were false.
 void
 ServiceWorkerRegistrationMainThread::UpdateState(const ServiceWorkerRegistrationDescriptor& aDescriptor)
 {
   NS_ENSURE_TRUE_VOID(mOuter);
 
   nsIGlobalObject* global = mOuter->GetParentObject();
   NS_ENSURE_TRUE_VOID(global);
 
@@ -121,16 +123,38 @@ ServiceWorkerRegistrationMainThread::Upd
     });
 
   Unused <<
     global->EventTargetFor(TaskCategory::Other)->Dispatch(r.forget(),
                                                           NS_DISPATCH_NORMAL);
 }
 
 void
+ServiceWorkerRegistrationMainThread::FireUpdateFound()
+{
+  NS_ENSURE_TRUE_VOID(mOuter);
+
+  nsIGlobalObject* global = mOuter->GetParentObject();
+  NS_ENSURE_TRUE_VOID(global);
+
+  RefPtr<ServiceWorkerRegistrationMainThread> self = this;
+  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+    "ServiceWorkerRegistrationMainThread::FireUpdateFound",
+    [self] () mutable {
+      NS_ENSURE_TRUE_VOID(self->mOuter);
+      self->mOuter->MaybeDispatchUpdateFoundRunnable();
+    });
+
+  Unused <<
+    global->EventTargetFor(TaskCategory::Other)->Dispatch(r.forget(),
+                                                          NS_DISPATCH_NORMAL);
+}
+
+
+void
 ServiceWorkerRegistrationMainThread::RegistrationRemoved()
 {
   NS_ENSURE_TRUE_VOID(mOuter);
 
   nsIGlobalObject* global = mOuter->GetParentObject();
   NS_ENSURE_TRUE_VOID(global);
 
   // Queue a runnable to clean up the registration.  This is necessary
@@ -674,25 +698,48 @@ public:
         this,
         &WorkerListener::UpdateStateOnWorkerThread,
         aDescriptor);
 
     Unused << mEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
   }
 
   void
+  FireUpdateFound() override
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    nsCOMPtr<nsIRunnable> r =
+      NewCancelableRunnableMethod(
+        "WorkerListener::FireUpdateFound",
+        this,
+        &WorkerListener::FireUpdateFoundOnWorkerThread);
+
+    Unused << mEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+  }
+
+  void
   UpdateStateOnWorkerThread(const ServiceWorkerRegistrationDescriptor& aDescriptor)
   {
     MOZ_ASSERT(IsCurrentThreadRunningWorker());
     if (mRegistration) {
       mRegistration->UpdateState(aDescriptor);
     }
   }
 
   void
+  FireUpdateFoundOnWorkerThread()
+  {
+    MOZ_ASSERT(IsCurrentThreadRunningWorker());
+    if (mRegistration) {
+      mRegistration->FireUpdateFound();
+    }
+  }
+
+  void
   RegistrationRemoved() override;
 
   void
   GetScope(nsAString& aScope) const override
   {
     CopyUTF8toUTF16(mDescriptor.Scope(), aScope);
   }
 
@@ -934,16 +981,24 @@ ServiceWorkerRegistrationWorkerThread::R
 void
 ServiceWorkerRegistrationWorkerThread::UpdateState(const ServiceWorkerRegistrationDescriptor& aDescriptor)
 {
   if (mOuter) {
     mOuter->UpdateState(aDescriptor);
   }
 }
 
+void
+ServiceWorkerRegistrationWorkerThread::FireUpdateFound()
+{
+  if (mOuter) {
+    mOuter->MaybeDispatchUpdateFoundRunnable();
+  }
+}
+
 class RegistrationRemovedWorkerRunnable final : public WorkerRunnable
 {
   RefPtr<WorkerListener> mListener;
 public:
   RegistrationRemovedWorkerRunnable(WorkerPrivate* aWorkerPrivate,
                                     WorkerListener* aListener)
     : WorkerRunnable(aWorkerPrivate)
     , mListener(aListener)
--- a/dom/serviceworkers/ServiceWorkerRegistrationImpl.h
+++ b/dom/serviceworkers/ServiceWorkerRegistrationImpl.h
@@ -50,16 +50,19 @@ public:
   Unregister(ServiceWorkerBoolCallback&& aSuccessCB,
              ServiceWorkerFailureCallback&& aFailureCB) override;
 
   // ServiceWorkerRegistrationListener
   void
   UpdateState(const ServiceWorkerRegistrationDescriptor& aDescriptor) override;
 
   void
+  FireUpdateFound() override;
+
+  void
   RegistrationRemoved() override;
 
   void
   GetScope(nsAString& aScope) const override
   {
     aScope = mScope;
   }
 
@@ -124,16 +127,19 @@ private:
   InitListener();
 
   void
   ReleaseListener();
 
   void
   UpdateState(const ServiceWorkerRegistrationDescriptor& aDescriptor);
 
+  void
+  FireUpdateFound();
+
   // This can be called only by WorkerListener.
   WorkerPrivate*
   GetWorkerPrivate(const MutexAutoLock& aProofOfLock);
 
   ServiceWorkerRegistration* mOuter;
   const ServiceWorkerRegistrationDescriptor mDescriptor;
   const nsString mScope;
   RefPtr<WorkerListener> mListener;
--- a/dom/serviceworkers/ServiceWorkerRegistrationInfo.cpp
+++ b/dom/serviceworkers/ServiceWorkerRegistrationInfo.cpp
@@ -805,16 +805,26 @@ ServiceWorkerRegistrationInfo::GetUpdate
   if (!mControlledClientsCounter && mDelayMultiplier < (INT_MAX / 30)) {
     mDelayMultiplier = (mDelayMultiplier ? mDelayMultiplier : 1) * 30;
   }
 
   return delay;
 }
 
 void
+ServiceWorkerRegistrationInfo::FireUpdateFound()
+{
+  nsTObserverArray<ServiceWorkerRegistrationListener*>::ForwardIterator it(mInstanceList);
+  while (it.HasMore()) {
+    RefPtr<ServiceWorkerRegistrationListener> target = it.GetNext();
+    target->FireUpdateFound();
+  }
+}
+
+void
 ServiceWorkerRegistrationInfo::NotifyRemoved()
 {
   nsTObserverArray<ServiceWorkerRegistrationListener*>::ForwardIterator it(mInstanceList);
   while (it.HasMore()) {
     RefPtr<ServiceWorkerRegistrationListener> target = it.GetNext();
     target->RegistrationRemoved();
   }
 }
--- a/dom/serviceworkers/ServiceWorkerRegistrationListener.h
+++ b/dom/serviceworkers/ServiceWorkerRegistrationListener.h
@@ -18,16 +18,19 @@ class ServiceWorkerRegistrationListener
 {
 public:
   NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
 
   virtual void
   UpdateState(const ServiceWorkerRegistrationDescriptor& aDescriptor) = 0;
 
   virtual void
+  FireUpdateFound() = 0;
+
+  virtual void
   RegistrationRemoved() = 0;
 
   virtual void
   GetScope(nsAString& aScope) const = 0;
 
   virtual bool
   MatchesDescriptor(const ServiceWorkerRegistrationDescriptor& aDescriptor) = 0;
 };
--- a/dom/serviceworkers/ServiceWorkerRegistrationProxy.cpp
+++ b/dom/serviceworkers/ServiceWorkerRegistrationProxy.cpp
@@ -39,16 +39,26 @@ ServiceWorkerRegistrationProxy::UpdateSt
   AssertIsOnBackgroundThread();
   if (!mActor) {
     return;
   }
   Unused << mActor->SendUpdateState(aDescriptor.ToIPC());
 }
 
 void
+ServiceWorkerRegistrationProxy::FireUpdateFoundOnBGThread()
+{
+  AssertIsOnBackgroundThread();
+  if (!mActor) {
+    return;
+  }
+  Unused << mActor->SendFireUpdateFound();
+}
+
+void
 ServiceWorkerRegistrationProxy::InitOnMainThread()
 {
   AssertIsOnMainThread();
 
   auto scopeExit = MakeScopeExit([&] {
     MaybeShutdownOnMainThread();
   });
 
@@ -105,16 +115,28 @@ ServiceWorkerRegistrationProxy::UpdateSt
   nsCOMPtr<nsIRunnable> r = NewRunnableMethod<ServiceWorkerRegistrationDescriptor>(
     __func__, this, &ServiceWorkerRegistrationProxy::UpdateStateOnBGThread,
     aDescriptor);
 
   MOZ_ALWAYS_SUCCEEDS(mEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
 }
 
 void
+ServiceWorkerRegistrationProxy::FireUpdateFound()
+{
+  AssertIsOnMainThread();
+
+  nsCOMPtr<nsIRunnable> r =
+    NewRunnableMethod(__func__, this,
+                      &ServiceWorkerRegistrationProxy::FireUpdateFoundOnBGThread);
+
+  MOZ_ALWAYS_SUCCEEDS(mEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
+}
+
+void
 ServiceWorkerRegistrationProxy::RegistrationRemoved()
 {
   MaybeShutdownOnMainThread();
 }
 
 void
 ServiceWorkerRegistrationProxy::GetScope(nsAString& aScope) const
 {
--- a/dom/serviceworkers/ServiceWorkerRegistrationProxy.h
+++ b/dom/serviceworkers/ServiceWorkerRegistrationProxy.h
@@ -35,31 +35,37 @@ class ServiceWorkerRegistrationProxy fin
 
   // Background thread methods
   void
   MaybeShutdownOnBGThread();
 
   void
   UpdateStateOnBGThread(const ServiceWorkerRegistrationDescriptor& aDescriptor);
 
+  void
+  FireUpdateFoundOnBGThread();
+
   // Main thread methods
   void
   InitOnMainThread();
 
   void
   MaybeShutdownOnMainThread();
 
   void
   StopListeningOnMainThread();
 
   // ServiceWorkerRegistrationListener interface
   void
   UpdateState(const ServiceWorkerRegistrationDescriptor& aDescriptor) override;
 
   void
+  FireUpdateFound() override;
+
+  void
   RegistrationRemoved() override;
 
   void
   GetScope(nsAString& aScope) const override;
 
   bool
   MatchesDescriptor(const ServiceWorkerRegistrationDescriptor& aDescriptor) override;
 
--- a/dom/serviceworkers/ServiceWorkerUpdateJob.cpp
+++ b/dom/serviceworkers/ServiceWorkerUpdateJob.cpp
@@ -537,22 +537,18 @@ ServiceWorkerUpdateJob::Install()
   //
   //  https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#installation-algorithm
 
   mRegistration->TransitionEvaluatingToInstalling();
 
   // Step 6 of the Install algorithm resolving the job promise.
   InvokeResultCallbacks(NS_OK);
 
-  // The job promise cannot be rejected after this point, but the job can
-  // still fail; e.g. if the install event handler throws, etc.
-
-  // Note, the updatefound event is fired automatically when the installing
-  // property is set on the ServiceWorkerRegistration binding object.  This
-  // happens via the TransitionEvaluatingToInstalling() call above.
+  // Queue a task to fire an event named updatefound at all the ServiceWorkerRegistration.
+  mRegistration->FireUpdateFound();
 
   nsMainThreadPtrHandle<ServiceWorkerUpdateJob> handle(
     new nsMainThreadPtrHolder<ServiceWorkerUpdateJob>(
       "ServiceWorkerUpdateJob", this));
   RefPtr<LifeCycleEventCallback> callback = new ContinueInstallRunnable(handle);
 
   // Send the install event to the worker thread
   ServiceWorkerPrivate* workerPrivate =
--- a/dom/tests/mochitest/fetch/common_readableStreams.js
+++ b/dom/tests/mochitest/fetch/common_readableStreams.js
@@ -12,37 +12,35 @@ function makeBuffer(size) {
     buffer.set([++value % 255], i);
   }
 
   return buffer;
 }
 
 function apply_compartment(compartment, data) {
   if (compartment == SAME_COMPARTMENT) {
-    self[data.func](data.args, self);
-    return;
+    return self[data.func](data.args, self);
   }
 
   if (compartment == IFRAME_COMPARTMENT) {
     const iframe = document.querySelector("#iframe").contentWindow;
-    iframe.runTest.call(iframe, data);
-    return;
+    return iframe[data.func](data.args, self);
   }
 
   ok(false, "Invalid compartment value");
 }
 
 async function test_nativeStream(compartment) {
   info("test_nativeStream");
 
   let r = await fetch('/');
 
-  apply_compartment(compartment,
-                    { func: "test_nativeStream_continue",
-                      args: r });
+  return apply_compartment(compartment,
+                           { func: "test_nativeStream_continue",
+                             args: r });
 }
 
 async function test_nativeStream_continue(r, that) {
   that.ok(r.body instanceof that.ReadableStream, "We have a ReadableStream");
 
   let a = r.clone();
   that.ok(a instanceof that.Response, "We have a cloned Response");
   that.ok(a.body instanceof that.ReadableStream, "We have a ReadableStream");
@@ -55,59 +53,56 @@ async function test_nativeStream_continu
 
   that.ok(blob instanceof Blob, "We have a blob");
   let d = await a.body.getReader().read();
 
   that.ok(!d.done, "We have read something!");
   blob = await b.blob();
 
   that.ok(blob instanceof Blob, "We have a blob");
-
-  that.next();
 }
 
 async function test_timeout(compartment) {
   info("test_timeout");
 
   let blob = new Blob([""]);
   let r = await fetch(URL.createObjectURL(blob));
 
-  apply_compartment(compartment,
-                    { func: "test_timeout_continue",
-                      args: r });
+  return apply_compartment(compartment,
+                           { func: "test_timeout_continue",
+                             args: r });
 }
 
 async function test_timeout_continue(r, that) {
   await r.body.getReader().read();
 
-  setTimeout(() => {
-    r.blob().then(b => {
-      that.ok(false, "We cannot have a blob here!");
-    }, () => {
-      that.ok(true, "We cannot have a blob here!");
-    }).then(() => {
-      that.next();
-    });
-  }, 0);
+  await new Promise(resolve => setTimeout(resolve, 0));
+
+  try {
+    await r.blob();
+    that.ok(false, "We cannot have a blob here!");
+  } catch (exc) {
+    that.ok(true, "We cannot have a blob here!");
+  }
 }
 
 async function test_nonNativeStream(compartment) {
   info("test_nonNativeStream");
 
   let buffer = makeBuffer(BIG_BUFFER_SIZE);
   info("Buffer size: " + buffer.byteLength);
 
   let r = new Response(new ReadableStream({start : controller => {
     controller.enqueue(buffer);
     controller.close();
   }}));
 
-  apply_compartment(compartment,
-                    { func: "test_nonNativeStream_continue",
-                      args: { r, buffer } });
+  return apply_compartment(compartment,
+                           { func: "test_nonNativeStream_continue",
+                             args: { r, buffer } });
 }
 
 async function test_nonNativeStream_continue(data, that) {
   that.ok(data.r.body instanceof that.ReadableStream, "We have a ReadableStream");
 
   let a = data.r.clone();
   that.ok(a instanceof that.Response, "We have a cloned Response");
   that.ok(a.body instanceof that.ReadableStream, "We have a ReadableStream");
@@ -121,95 +116,90 @@ async function test_nonNativeStream_cont
   that.ok(blob instanceof Blob, "We have a blob");
   let d = await a.body.getReader().read();
 
   that.ok(!d.done, "We have read something!");
   blob = await b.blob();
 
   that.ok(blob instanceof Blob, "We have a blob");
   that.is(blob.size, data.buffer.byteLength, "Blob size matches");
-
-  that.next();
 }
 
 async function test_noUint8Array(compartment) {
   info("test_noUint8Array");
 
   let r = new Response(new ReadableStream({start : controller => {
     controller.enqueue('hello world!');
     controller.close();
   }}));
 
-  apply_compartment(compartment,
-                    { func: "test_noUint8Array_continue",
-                      args: r });
+  return apply_compartment(compartment,
+                           { func: "test_noUint8Array_continue",
+                             args: r });
 }
 
 async function test_noUint8Array_continue(r, that) {
   that.ok(r.body instanceof that.ReadableStream, "We have a ReadableStream");
 
-  r.blob().then(b => {
+  try {
+    await r.blob();
     that.ok(false, "We cannot have a blob here!");
-  }, () => {
+  } catch {
     that.ok(true, "We cannot have a blob here!");
-  }).then(that.next);
+  }
 }
 
 async function test_pendingStream(compartment) {
   let r = new Response(new ReadableStream({start : controller => {
     controller.enqueue(makeBuffer(BIG_BUFFER_SIZE));
     // Let's keep this controler open.
     self.ccc = controller;
   }}));
 
-  apply_compartment(compartment,
-                    { func: "test_pendingStream_continue",
-                      args: r });
+  return apply_compartment(compartment,
+                           { func: "test_pendingStream_continue",
+                             args: r });
 }
 
 async function test_pendingStream_continue(r, that) {
   let d = await r.body.getReader().read();
 
   that.ok(!d.done, "We have read something!");
 
   if ("close" in that) {
     that.close();
   }
-
-  that.next();
 }
 
 async function test_nativeStream_cache(compartment) {
   info("test_nativeStream_cache");
 
   let origBody = '123456789abcdef';
   let url = '/nativeStream';
 
   let cache = await caches.open('nativeStream');
 
   info("Storing a body as a string");
   await cache.put(url, new Response(origBody));
 
-  apply_compartment(compartment,
-                    { func: "test_nativeStream_cache_continue",
-                      args: { caches, cache, url, origBody } });
+  return apply_compartment(compartment,
+                           { func: "test_nativeStream_cache_continue",
+                             args: { caches, cache, url, origBody } });
 }
 
 async function test_nativeStream_cache_continue(data, that) {
   that.info("Retrieving the stored value");
   let cacheResponse = await data.cache.match(data.url);
 
   that.info("Converting the response to text");
   let cacheBody = await cacheResponse.text();
 
   that.is(data.origBody, cacheBody, "Bodies match");
 
   await data.caches.delete('nativeStream');
-
-  that.next();
 };
 
 async function test_nonNativeStream_cache(compartment) {
   info("test_nonNativeStream_cache");
 
   let url = '/nonNativeStream';
 
   let cache = await caches.open('nonNativeStream');
@@ -217,19 +207,19 @@ async function test_nonNativeStream_cach
   info("Buffer size: " + buffer.byteLength);
 
   info("Storing a body as a string");
   let r = new Response(new ReadableStream({start : controller => {
     controller.enqueue(buffer);
     controller.close();
   }}));
 
-  apply_compartment(compartment,
-                    { func: "test_nonNativeStream_cache_continue",
-                      args: { caches, cache, buffer, r } });
+  return apply_compartment(compartment,
+                           { func: "test_nonNativeStream_cache_continue",
+                             args: { caches, cache, buffer, r } });
 }
 
 async function test_nonNativeStream_cache_continue(data, that) {
   await data.cache.put(data.url, data.r);
 
   that.info("Retrieving the stored value");
   let cacheResponse = await data.cache.match(data.url);
 
@@ -240,35 +230,33 @@ async function test_nonNativeStream_cach
   that.is(cacheBody.byteLength, BIG_BUFFER_SIZE, "Body length is correct");
 
   let value = 0;
   for (let i = 0; i < 1000000; i+= 1000) {
     that.is(new Uint8Array(cacheBody)[i], ++value % 255, "byte in position " + i + " is correct");
   }
 
   await data.caches.delete('nonNativeStream');
-
-  that.next();
 };
 
 async function test_codeExecution(compartment) {
   info("test_codeExecution");
 
   let r = new Response(new ReadableStream({
     start(c) {
       controller = c
     },
     pull() {
       console.log("pull called");
     }
   }));
 
-  apply_compartment(compartment,
-                    { func: "test_codeExecution_continue",
-                      args: r });
+  return apply_compartment(compartment,
+                           { func: "test_codeExecution_continue",
+                             args: r });
 }
 
 async function test_codeExecution_continue(r, that) {
   function consoleListener() {
     that.SpecialPowers.addObserver(this, "console-api-log-event");
   }
 
   var promise = new Promise(resolve => {
@@ -286,18 +274,16 @@ async function test_codeExecution_contin
       }
     }
   });
 
   var cl = new consoleListener();
 
   r.body.getReader().read();
   await promise;
-
-  that.next();
 };
 
 async function test_global(compartment) {
   info("test_global: " + compartment);
 
   self.foo = 42;
   self.iter = ITER_MAX;
 
@@ -315,50 +301,53 @@ async function test_global(compartment) 
       self.controller.enqueue(buffer);
 
       if (--self.iter == 0) {
         controller.close();
       }
     }
   }));
 
-  apply_compartment(compartment,
-                    { func: "test_global_continue",
-                    args: r });
+  return apply_compartment(compartment,
+                           { func: "test_global_continue",
+                             args: r });
 }
 
 async function test_global_continue(r, that) {
   let a = await r.arrayBuffer();
 
   that.is(Object.getPrototypeOf(a), that.ArrayBuffer.prototype, "Body is an array buffer");
   that.is(a.byteLength, ITER_MAX, "Body length is correct");
 
   for (let i = 0; i < ITER_MAX; ++i) {
     that.is(new Uint8Array(a)[i], 42, "Byte " + i + " is correct");
   }
-
-  that.next();
 };
 
 function workify(func) {
-  info("Workifing " + func);
+  info("Workifying " + func);
 
-  let worker = new Worker('worker_readableStreams.js');
-  worker.postMessage(func);
-  worker.onmessage = function(e) {
-    if (e.data.type == 'done') {
-      next();
-      return;
-    }
+  return new Promise((resolve, reject) => {
+    let worker = new Worker('worker_readableStreams.js');
+    worker.postMessage(func);
+    worker.onmessage = function(e) {
+      if (e.data.type == 'done') {
+        resolve();
+        return;
+      }
 
-    if (e.data.type == 'test') {
-      ok(e.data.test, e.data.message);
-      return;
-    }
+      if (e.data.type == 'error') {
+        reject(e.data.message);
+        return;
+      }
 
-    if (e.data.type == 'info') {
-      info(e.data.message);
-      return;
+      if (e.data.type == 'test') {
+        ok(e.data.test, e.data.message);
+        return;
+      }
+
+      if (e.data.type == 'info') {
+        info(e.data.message);
+        return;
+      }
     }
-  }
-
-  return worker;
+  });
 }
--- a/dom/tests/mochitest/fetch/iframe_readableStreams.html
+++ b/dom/tests/mochitest/fetch/iframe_readableStreams.html
@@ -1,8 +1,4 @@
-<iframe src="iframe_readableStreams.html" id="iframe"></iframe>
 <script type="application/javascript" src="common_readableStreams.js"></script>
 <script>
-function runTest(data) {
-  const iframe = document.querySelector("#iframe").contentWindow;
-  self[data.func].call(iframe, data.args, parent);
-}
+parent.runTests();
 </script>
--- a/dom/tests/mochitest/fetch/test_readableStreams.html
+++ b/dom/tests/mochitest/fetch/test_readableStreams.html
@@ -3,73 +3,70 @@
 <head>
   <meta charset="utf-8">
   <title>Test for ReadableStreams and Fetch</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="common_readableStreams.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
-  <iframe src="iframe_readableStreams.html" id="iframe"></iframe>
   <script type="application/javascript">
 
-let tests = [
-  function() {
-    SpecialPowers.pushPrefEnv({
-      "set": [["dom.caches.enabled", true],
-              ["dom.caches.testing.enabled", true],
-              ["dom.quotaManager.testing", true]]
-    }, next);
-  },
+async function tests() {
+  await SpecialPowers.pushPrefEnv({
+    "set": [["dom.caches.enabled", true],
+            ["dom.caches.testing.enabled", true],
+            ["dom.quotaManager.testing", true]]
+  });
+
+  await test_nativeStream(SAME_COMPARTMENT);
+  await test_nativeStream(IFRAME_COMPARTMENT);
+  await workify('test_nativeStream');
 
-  function() { test_nativeStream(SAME_COMPARTMENT); },
-  function() { test_nativeStream(IFRAME_COMPARTMENT); },
-  function() { workify('test_nativeStream'); },
-
-  function() { test_timeout(SAME_COMPARTMENT); },
-  function() { test_timeout(IFRAME_COMPARTMENT); },
-  function() { workify('test_timeout'); },
+  await test_timeout(SAME_COMPARTMENT);
+  await test_timeout(IFRAME_COMPARTMENT);
+  await workify('test_timeout');
 
-  function() { test_nonNativeStream(SAME_COMPARTMENT); },
-  function() { test_nonNativeStream(IFRAME_COMPARTMENT); },
-  function() { workify('test_nonNativeStream'); },
+  await test_nonNativeStream(SAME_COMPARTMENT);
+  await test_nonNativeStream(IFRAME_COMPARTMENT);
+  await workify('test_nonNativeStream');
 
-  function() { test_pendingStream(SAME_COMPARTMENT); },
-  function() { test_pendingStream(IFRAME_COMPARTMENT); },
-  function() { workify('test_pendingStream'); },
+  await test_pendingStream(SAME_COMPARTMENT);
+  await test_pendingStream(IFRAME_COMPARTMENT);
+  await workify('test_pendingStream');
 
-  function() { test_noUint8Array(SAME_COMPARTMENT); },
-  function() { test_noUint8Array(IFRAME_COMPARTMENT); },
-  function() { workify('test_noUint8Array'); },
+  await test_noUint8Array(SAME_COMPARTMENT);
+  await test_noUint8Array(IFRAME_COMPARTMENT);
+  await workify('test_noUint8Array');
 
-  function() { test_nativeStream_cache(SAME_COMPARTMENT); },
-  function() { test_nativeStream_cache(IFRAME_COMPARTMENT); },
-  function() { workify('test_nativeStream_cache'); },
+  await test_nativeStream_cache(SAME_COMPARTMENT);
+  await test_nativeStream_cache(IFRAME_COMPARTMENT);
+  await workify('test_nativeStream_cache');
 
-  function() { test_nonNativeStream_cache(SAME_COMPARTMENT); },
-  function() { test_nonNativeStream_cache(IFRAME_COMPARTMENT); },
-  function() { workify('test_nonNativeStream_cache'); },
+  await test_nonNativeStream_cache(SAME_COMPARTMENT);
+  await test_nonNativeStream_cache(IFRAME_COMPARTMENT);
+  await workify('test_nonNativeStream_cache');
 
-  function() { test_codeExecution(SAME_COMPARTMENT); },
-  function() { test_codeExecution(IFRAME_COMPARTMENT); },
+  await test_codeExecution(SAME_COMPARTMENT);
+  await test_codeExecution(IFRAME_COMPARTMENT);
 
-  function() { test_global(SAME_COMPARTMENT); },
-  function() { test_global(IFRAME_COMPARTMENT); },
-  function() { workify('test_global'); },
-];
+  await test_global(SAME_COMPARTMENT);
+  await test_global(IFRAME_COMPARTMENT);
+  await workify('test_global');
+}
 
-function next() {
-  if (!tests.length) {
+async function runTests() {
+  try {
+    await tests();
+  } catch (exc) {
+    ok(false, exc.toString());
+  } finally {
     SimpleTest.finish();
-    return;
   }
-
-  let test = tests.shift();
-  test();
 }
 
 SimpleTest.waitForExplicitFinish();
-next();
-
+// The iframe starts the tests by calling parent.next() when it loads.
   </script>
+  <iframe src="iframe_readableStreams.html" id="iframe"></iframe>
 </body>
 </html>
 
--- a/dom/tests/mochitest/fetch/worker_readableStreams.js
+++ b/dom/tests/mochitest/fetch/worker_readableStreams.js
@@ -7,15 +7,17 @@ function info(message) {
 function ok(a, message) {
   postMessage({type: 'test', test: !!a, message });
 }
 
 function is(a, b, message) {
   ok(a === b, message);
 }
 
-function next() {
-  postMessage({type: 'done'});
+onmessage = function(e) {
+  self[e.data](SAME_COMPARTMENT).then(ok => {
+    postMessage({type: 'done'});
+  }, exc => {
+    dump(exc);
+    dump(exc.stack);
+    postMessage({type: 'error', message: exc.toString()});
+  });
 }
-
-onmessage = function(e) {
-  self[e.data](SAME_COMPARTMENT);
-}
--- a/dom/webidl/Element.webidl
+++ b/dom/webidl/Element.webidl
@@ -230,17 +230,17 @@ partial interface Element {
 // https://dom.spec.whatwg.org/#dictdef-shadowrootinit
 dictionary ShadowRootInit {
   required ShadowRootMode mode;
 };
 
 // https://dom.spec.whatwg.org/#element
 partial interface Element {
   // Shadow DOM v1
-  [Throws]
+  [Throws, UseCounter]
   ShadowRoot attachShadow(ShadowRootInit shadowRootInitDict);
   [BinaryName="shadowRootByMode"]
   readonly attribute ShadowRoot? shadowRoot;
 
   [Func="nsDocument::IsCallerChromeOrAddon", BinaryName="shadowRoot"]
   readonly attribute ShadowRoot? openOrClosedShadowRoot;
 
   [BinaryName="assignedSlotByMode"]
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -371,16 +371,27 @@ public:
                                           bool* aFirst,
                                           bool* aAny,
                                           bool* aAll,
                                           nsAString& outValue);
 
   /**
    * RemoveInlinePropertyAsAction() removes a property which changes inline
    * style of text.  E.g., bold, italic, super and sub.
+   *
+   * @param aProperty   Tag name whcih represents the inline style you want to
+   *                    remove.  E.g., nsGkAtoms::strong, nsGkAtoms::b, etc.
+   *                    If nsGkAtoms::href, <a> element which has href
+   *                    attribute will be removed.
+   *                    If nsGkAtoms::name, <a> element which has non-empty
+   *                    name attribute will be removed.
+   * @param aAttribute  If aProperty is nsGkAtoms::font, aAttribute should be
+   *                    nsGkAtoms::fase, nsGkAtoms::size, nsGkAtoms::color or
+   *                    nsGkAtoms::bgcolor.  Otherwise, set nullptr.
+   *                    Must not use nsGkAtoms::_empty here.
    */
   nsresult RemoveInlinePropertyAsAction(nsAtom& aProperty,
                                         nsAtom* aAttribute);
 
   /**
    * GetFontColorState() returns foreground color information in first
    * range of Selection.
    * If first range of Selection is collapsed and there is a cache of style for
--- a/editor/libeditor/HTMLStyleEditor.cpp
+++ b/editor/libeditor/HTMLStyleEditor.cpp
@@ -40,16 +40,25 @@
 #include "nscore.h"
 
 class nsISupports;
 
 namespace mozilla {
 
 using namespace dom;
 
+static already_AddRefed<nsAtom>
+AtomizeAttribute(const nsAString& aAttribute)
+{
+  if (aAttribute.IsEmpty()) {
+    return nullptr; // Don't use nsGkAtoms::_empty for attribute.
+   }
+   return NS_Atomize(aAttribute);
+}
+
 bool
 HTMLEditor::IsEmptyTextNode(nsINode& aNode)
 {
   bool isEmptyTextNode = false;
   return EditorBase::IsTextNode(&aNode) &&
          NS_SUCCEEDED(IsEmptyNode(&aNode, &isEmptyTextNode)) &&
          isEmptyTextNode;
 }
@@ -92,17 +101,17 @@ NS_IMETHODIMP
 HTMLEditor::SetInlineProperty(const nsAString& aProperty,
                               const nsAString& aAttribute,
                               const nsAString& aValue)
 {
   RefPtr<nsAtom> property = NS_Atomize(aProperty);
   if (NS_WARN_IF(!property)) {
     return NS_ERROR_INVALID_ARG;
   }
-  RefPtr<nsAtom> attribute = NS_Atomize(aAttribute);
+  RefPtr<nsAtom> attribute = AtomizeAttribute(aAttribute);
   AutoEditActionDataSetter editActionData(
     *this,
     HTMLEditUtils::GetEditActionForFormatText(*property, attribute, true));
   if (NS_WARN_IF(!editActionData.CanHandle())) {
     return NS_ERROR_NOT_INITIALIZED;
   }
   return SetInlinePropertyInternal(*property, attribute, aValue);
 }
@@ -1205,17 +1214,17 @@ NS_IMETHODIMP
 HTMLEditor::GetInlineProperty(const nsAString& aProperty,
                               const nsAString& aAttribute,
                               const nsAString& aValue,
                               bool* aFirst,
                               bool* aAny,
                               bool* aAll)
 {
   RefPtr<nsAtom> property = NS_Atomize(aProperty);
-  RefPtr<nsAtom> attribute = NS_Atomize(aAttribute);
+  RefPtr<nsAtom> attribute = AtomizeAttribute(aAttribute);
   return GetInlineProperty(property, attribute, aValue, aFirst, aAny, aAll);
 }
 
 nsresult
 HTMLEditor::GetInlineProperty(nsAtom* aProperty,
                               nsAtom* aAttribute,
                               const nsAString& aValue,
                               bool* aFirst,
@@ -1246,17 +1255,17 @@ HTMLEditor::GetInlinePropertyWithAttrVal
                                            const nsAString& aAttribute,
                                            const nsAString& aValue,
                                            bool* aFirst,
                                            bool* aAny,
                                            bool* aAll,
                                            nsAString& outValue)
 {
   RefPtr<nsAtom> property = NS_Atomize(aProperty);
-  RefPtr<nsAtom> attribute = NS_Atomize(aAttribute);
+  RefPtr<nsAtom> attribute = AtomizeAttribute(aAttribute);
   return GetInlinePropertyWithAttrValue(property, attribute, aValue, aFirst,
                                         aAny, aAll, outValue);
 }
 
 nsresult
 HTMLEditor::GetInlinePropertyWithAttrValue(nsAtom* aProperty,
                                            nsAtom* aAttribute,
                                            const nsAString& aValue,
@@ -1321,33 +1330,34 @@ HTMLEditor::RemoveInlinePropertyAsAction
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HTMLEditor::RemoveInlineProperty(const nsAString& aProperty,
                                  const nsAString& aAttribute)
 {
   RefPtr<nsAtom> property = NS_Atomize(aProperty);
-  RefPtr<nsAtom> attribute = NS_Atomize(aAttribute);
+  RefPtr<nsAtom> attribute = AtomizeAttribute(aAttribute);
 
   AutoEditActionDataSetter editActionData(
     *this,
     HTMLEditUtils::GetEditActionForFormatText(*property, attribute, false));
   if (NS_WARN_IF(!editActionData.CanHandle())) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   return RemoveInlinePropertyInternal(property, attribute);
 }
 
 nsresult
 HTMLEditor::RemoveInlinePropertyInternal(nsAtom* aProperty,
                                          nsAtom* aAttribute)
 {
   MOZ_ASSERT(IsEditActionDataAvailable());
+  MOZ_ASSERT(aAttribute != nsGkAtoms::_empty);
 
   if (NS_WARN_IF(!mRules)) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   CommitComposition();
 
   if (SelectionRefPtr()->IsCollapsed()) {
--- a/editor/libeditor/TypeInState.cpp
+++ b/editor/libeditor/TypeInState.cpp
@@ -294,16 +294,19 @@ TypeInState::IsPropSet(nsAtom* aProp,
 }
 
 bool
 TypeInState::IsPropSet(nsAtom* aProp,
                        nsAtom* aAttr,
                        nsAString* outValue,
                        int32_t& outIndex)
 {
+  if (aAttr == nsGkAtoms::_empty) {
+    aAttr = nullptr;
+  }
   // linear search.  list should be short.
   size_t count = mSetArray.Length();
   for (size_t i = 0; i < count; i++) {
     PropItem *item = mSetArray[i];
     if (item->tag == aProp && item->attr == aAttr) {
       if (outValue) {
         *outValue = item->value;
       }
@@ -342,16 +345,19 @@ TypeInState::IsPropCleared(nsAtom* aProp
 
 bool
 TypeInState::FindPropInList(nsAtom* aProp,
                             nsAtom* aAttr,
                             nsAString* outValue,
                             nsTArray<PropItem*>& aList,
                             int32_t& outIndex)
 {
+  if (aAttr == nsGkAtoms::_empty) {
+    aAttr = nullptr;
+  }
   // linear search.  list should be short.
   size_t count = aList.Length();
   for (size_t i = 0; i < count; i++) {
     PropItem *item = aList[i];
     if (item->tag == aProp && item->attr == aAttr) {
       if (outValue) {
         *outValue = item->value;
       }
@@ -372,17 +378,17 @@ PropItem::PropItem()
 {
   MOZ_COUNT_CTOR(PropItem);
 }
 
 PropItem::PropItem(nsAtom* aTag,
                    nsAtom* aAttr,
                    const nsAString &aValue)
   : tag(aTag)
-  , attr(aAttr)
+  , attr(aAttr != nsGkAtoms::_empty ? aAttr : nullptr)
   , value(aValue)
 {
   MOZ_COUNT_CTOR(PropItem);
 }
 
 PropItem::~PropItem()
 {
   MOZ_COUNT_DTOR(PropItem);
--- a/editor/libeditor/tests/mochitest.ini
+++ b/editor/libeditor/tests/mochitest.ini
@@ -281,16 +281,17 @@ skip-if = android_version == '24'
 [test_inlineTableEditing.html]
 [test_insertParagraph_in_inline_editing_host.html]
 [test_keypress_untrusted_event.html]
 [test_middle_click_paste.html]
 subsuite = clipboard
 skip-if = android_version == '24'
 [test_nsIEditorMailSupport_insertAsCitedQuotation.html]
 [test_nsIHTMLEditor_getSelectedElement.html]
+[test_nsIHTMLEditor_removeInlineProperty.html]
 [test_nsIHTMLEditor_selectElement.html]
 [test_nsIHTMLEditor_setCaretAfterElement.html]
 [test_nsIHTMLObjectResizer_hideResizers.html]
 [test_nsIPlaintextEditor_insertLineBreak.html]
 [test_nsITableEditor_deleteTableCell.html]
 [test_nsITableEditor_deleteTableCellContents.html]
 [test_nsITableEditor_deleteTableColumn.html]
 [test_nsITableEditor_deleteTableRow.html]
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/tests/test_nsIHTMLEditor_removeInlineProperty.html
@@ -0,0 +1,334 @@
+<!DOCTYPE>
+<html>
+<head>
+  <title>Test for nsIHTMLEditor.removeInlineProperty()</title>
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<div id="display">
+</div>
+<div id="content" contenteditable></div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+  let editor = document.getElementById("content");
+  let selection = window.getSelection();
+  let description, condition;
+  let inputEvents = [];
+  function onInput(aEvent) {
+    inputEvents.push(aEvent);
+  }
+  editor.addEventListener("input", onInput);
+
+  function checkInputEvent(aEvent, aDescription) {
+    if (aEvent.type != "input") {
+      return;
+    }
+    ok(aEvent instanceof InputEvent, `${aDescription}"input" event should be dispatched with InputEvent interface`);
+    is(aEvent.cancelable, false, `${aDescription}"input" event should be never cancelable`);
+    is(aEvent.bubbles, true, `${aDescription}"input" event should always bubble`);
+  }
+
+  function selectFromTextSiblings(aNode) {
+    condition = "selecting the node from end of previous text to start of next text node";
+    selection.setBaseAndExtent(aNode.previousSibling, aNode.previousSibling.length,
+                               aNode.nextSibling, 0);
+  }
+  function selectNode(aNode) {
+    condition = "selecting the node";
+    let range = document.createRange();
+    range.selectNode(aNode);
+    selection.removeAllRanges();
+    selection.addRange(range);
+  }
+  function selectAllChildren(aNode) {
+    condition = "selecting all children of the node";
+    selection.selectAllChildren(aNode);
+  }
+  function selectChildContents(aNode) {
+    condition = "selecting all contents of its child";
+    let range = document.createRange();
+    range.selectNodeContents(aNode.firstChild);
+    selection.removeAllRanges();
+    selection.addRange(range);
+  }
+
+  description = "When there is a <b> element and ";
+  for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
+    editor.innerHTML = "<p>test: <b>here</b> is bolden text</p>";
+    editor.focus();
+    inputEvents = [];
+    prepare(editor.firstChild.firstChild.nextSibling);
+    getHTMLEditor().removeInlineProperty("b", "");
+    is(editor.innerHTML, "<p>test: here is bolden text</p>",
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should remove the <b> element');
+    is(inputEvents.length, 1,
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should cause an "input" event');
+    if (inputEvents.length > 0) {
+      checkInputEvent(inputEvents[0], description);
+    }
+  }
+
+  description = "When there is a <b> element which has style attribute and ";
+  for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
+    editor.innerHTML = '<p>test: <b style="font-style: italic">here</b> is bolden text</p>';
+    editor.focus();
+    inputEvents = [];
+    prepare(editor.firstChild.firstChild.nextSibling);
+    getHTMLEditor().removeInlineProperty("b", "");
+    is(editor.innerHTML, '<p>test: <span style="font-style: italic">here</span> is bolden text</p>',
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should replace the <b> element with <span> element to keep the style');
+    is(inputEvents.length, 1,
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should cause an "input" event');
+    if (inputEvents.length > 0) {
+      checkInputEvent(inputEvents[0], description);
+    }
+  }
+
+  description = "When there is a <b> element which has class attribute and ";
+  for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
+    editor.innerHTML = '<p>test: <b class="foo">here</b> is bolden text</p>';
+    editor.focus();
+    inputEvents = [];
+    prepare(editor.firstChild.firstChild.nextSibling);
+    getHTMLEditor().removeInlineProperty("b", "");
+    is(editor.innerHTML, '<p>test: <span class="foo">here</span> is bolden text</p>',
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should replace the <b> element with <span> element to keep the class');
+    is(inputEvents.length, 1,
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should cause an "input" event');
+    if (inputEvents.length > 0) {
+      checkInputEvent(inputEvents[0], description);
+    }
+  }
+
+  description = "When there is a <b> element which has an <i> element and ";
+  for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
+    editor.innerHTML = "<p>test: <b><i>here</i></b> is bolden and italic text</p>";
+    editor.focus();
+    inputEvents = [];
+    prepare(editor.firstChild.firstChild.nextSibling);
+    getHTMLEditor().removeInlineProperty("b", "");
+    is(editor.innerHTML, "<p>test: <i>here</i> is bolden and italic text</p>",
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should remove only the <b> element');
+    is(inputEvents.length, 1,
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should cause an "input" event');
+    if (inputEvents.length > 0) {
+      checkInputEvent(inputEvents[0], description);
+    }
+  }
+
+  description = "When there is a <b> element which has an <i> element and ";
+  for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
+    editor.innerHTML = "<p>test: <b><i>here</i></b> is bolden and italic text</p>";
+    editor.focus();
+    inputEvents = [];
+    prepare(editor.firstChild.firstChild.nextSibling);
+    getHTMLEditor().removeInlineProperty("i", "");
+    is(editor.innerHTML, "<p>test: <b>here</b> is bolden and italic text</p>",
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("i", "") should remove only the <i> element');
+    is(inputEvents.length, 1,
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("i", "") should cause an "input" event');
+    if (inputEvents.length > 0) {
+      checkInputEvent(inputEvents[0], description);
+    }
+  }
+
+  description = "When there is an <i> element in a <b> element and ";
+  for (let prepare of [selectNode, selectAllChildren, selectChildContents]) {
+    editor.innerHTML = "<p>test: <b><i>here</i></b> is bolden and italic text</p>";
+    editor.focus();
+    inputEvents = [];
+    prepare(editor.firstChild.firstChild.nextSibling.firstChild);
+    getHTMLEditor().removeInlineProperty("b", "");
+    is(editor.innerHTML, "<p>test: <i>here</i> is bolden and italic text</p>",
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should remove only the <b> element');
+    is(inputEvents.length, 1,
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should cause an "input" event');
+    if (inputEvents.length > 0) {
+      checkInputEvent(inputEvents[0], description);
+    }
+  }
+
+  description = "When there is an <i> element in a <b> element and ";
+  for (let prepare of [selectNode, selectAllChildren, selectChildContents]) {
+    editor.innerHTML = "<p>test: <b><i>here</i></b> is bolden and italic text</p>";
+    editor.focus();
+    inputEvents = [];
+    prepare(editor.firstChild.firstChild.nextSibling.firstChild);
+    getHTMLEditor().removeInlineProperty("i", "");
+    is(editor.innerHTML, "<p>test: <b>here</b> is bolden and italic text</p>",
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("i", "") should remove only the <i> element');
+    is(inputEvents.length, 1,
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("i", "") should cause an "input" event');
+    if (inputEvents.length > 0) {
+      checkInputEvent(inputEvents[0], description);
+    }
+  }
+
+  description = "When there is an <i> element between text nodes in a <b> element and ";
+  for (let prepare of [selectNode, selectAllChildren, selectChildContents]) {
+    editor.innerHTML = "<p>test: <b>h<i>e</i>re</b> is bolden and italic text</p>";
+    editor.focus();
+    inputEvents = [];
+    prepare(editor.firstChild.firstChild.nextSibling.firstChild.nextSibling);
+    getHTMLEditor().removeInlineProperty("i", "");
+    is(editor.innerHTML, "<p>test: <b>here</b> is bolden and italic text</p>",
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("i", "") should remove only the <i> element');
+    is(inputEvents.length, 1,
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("i", "") should cause an "input" event');
+    if (inputEvents.length > 0) {
+      checkInputEvent(inputEvents[0], description);
+    }
+  }
+
+  description = "When there is an <i> element between text nodes in a <b> element and ";
+  for (let prepare of [selectNode, selectAllChildren, selectChildContents]) {
+    editor.innerHTML = "<p>test: <b>h<i>e</i>re</b> is bolden and italic text</p>";
+    editor.focus();
+    inputEvents = [];
+    prepare(editor.firstChild.firstChild.nextSibling.firstChild.nextSibling);
+    getHTMLEditor().removeInlineProperty("b", "");
+    is(editor.innerHTML, "<p>test: <b>h</b><i>e</i><b>re</b> is bolden and italic text</p>",
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should split the <b> element');
+    is(inputEvents.length, 1,
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should cause an "input" event');
+    if (inputEvents.length > 0) {
+      checkInputEvent(inputEvents[0], description);
+    }
+  }
+
+  description = "When there is an <a> element whose href attribute is not empty and ";
+  for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
+    editor.innerHTML = '<p>test: <a href="about:blank">here</a> is a link</p>';
+    editor.focus();
+    inputEvents = [];
+    prepare(editor.firstChild.firstChild.nextSibling);
+    getHTMLEditor().removeInlineProperty("href", "");
+    is(editor.innerHTML, "<p>test: here is a link</p>",
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("href", "") should remove the <a> element');
+    is(inputEvents.length, 1,
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("href", "") should cause an "input" event');
+    if (inputEvents.length > 0) {
+      checkInputEvent(inputEvents[0], description);
+    }
+  }
+
+  // XXX In the case of "name", removeInlineProperty() does not the <a> element when name attribute is empty.
+  description = "When there is an <a> element whose href attribute is empty and ";
+  for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
+    editor.innerHTML = '<p>test: <a href="">here</a> is a link</p>';
+    editor.focus();
+    inputEvents = [];
+    prepare(editor.firstChild.firstChild.nextSibling);
+    getHTMLEditor().removeInlineProperty("href", "");
+    is(editor.innerHTML, "<p>test: here is a link</p>",
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("href", "") should remove the <a> element');
+    is(inputEvents.length, 1,
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("href", "") should cause an "input" event');
+    if (inputEvents.length > 0) {
+      checkInputEvent(inputEvents[0], description);
+    }
+  }
+
+  description = "When there is an <a> element which does not have href attribute and ";
+  for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
+    editor.innerHTML = "<p>test: <a>here</a> is an anchor</p>";
+    editor.focus();
+    inputEvents = [];
+    prepare(editor.firstChild.firstChild.nextSibling);
+    getHTMLEditor().removeInlineProperty("href", "");
+    is(editor.innerHTML, "<p>test: <a>here</a> is an anchor</p>",
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("href", "") should NOT remove the <a> element');
+    is(inputEvents.length, 0,
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("href", "") should NOT cause an "input" event');
+  }
+
+  description = "When there is an <a> element whose name attribute is not empty and ";
+  for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
+    editor.innerHTML = '<p>test: <a name="foo">here</a> is a named anchor</p>';
+    editor.focus();
+    inputEvents = [];
+    prepare(editor.firstChild.firstChild.nextSibling);
+    getHTMLEditor().removeInlineProperty("href", "");
+    is(editor.innerHTML, '<p>test: <a name="foo">here</a> is a named anchor</p>',
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("href", "") should NOT remove the <a> element');
+    is(inputEvents.length, 0,
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("href", "") should NOT cause an "input" event');
+  }
+
+  description = "When there is an <a> element whose name attribute is not empty and ";
+  for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
+    editor.innerHTML = '<p>test: <a name="foo">here</a> is a named anchor</p>';
+    editor.focus();
+    inputEvents = [];
+    prepare(editor.firstChild.firstChild.nextSibling);
+    getHTMLEditor().removeInlineProperty("name", "");
+    is(editor.innerHTML, "<p>test: here is a named anchor</p>",
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("name", "") should remove the <a> element');
+    is(inputEvents.length, 1,
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("name", "") should cause an "input" event');
+    if (inputEvents.length > 0) {
+      checkInputEvent(inputEvents[0], description);
+    }
+  }
+
+  // XXX In the case of "href", removeInlineProperty() removes the <a> element when href attribute is empty.
+  description = "When there is an <a> element whose name attribute is empty and ";
+  for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
+    editor.innerHTML = '<p>test: <a name="">here</a> is a named anchor</p>';
+    editor.focus();
+    inputEvents = [];
+    prepare(editor.firstChild.firstChild.nextSibling);
+    getHTMLEditor().removeInlineProperty("name", "");
+    is(editor.innerHTML, '<p>test: <a name="">here</a> is a named anchor</p>',
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("name", "") should NOT remove the <a> element');
+    is(inputEvents.length, 0,
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("name", "") should NOT cause an "input" event');
+  }
+
+  description = "When there is an <a> element which does not have name attribute and ";
+  for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
+    editor.innerHTML = "<p>test: <a>here</a> is an anchor</p>";
+    editor.focus();
+    inputEvents = [];
+    prepare(editor.firstChild.firstChild.nextSibling);
+    getHTMLEditor().removeInlineProperty("name", "");
+    is(editor.innerHTML, "<p>test: <a>here</a> is an anchor</p>",
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("name", "") should NOT remove the <a> element');
+    is(inputEvents.length, 0,
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("name", "") should NOT cause an "input" event');
+  }
+
+  description = "When there is an <a> element whose href attribute is not empty and ";
+  for (let prepare of [selectFromTextSiblings, selectNode, selectAllChildren, selectChildContents]) {
+    editor.innerHTML = '<p>test: <a href="about:blank">here</a> is a link</p>';
+    editor.focus();
+    inputEvents = [];
+    prepare(editor.firstChild.firstChild.nextSibling);
+    getHTMLEditor().removeInlineProperty("name", "");
+    is(editor.innerHTML, '<p>test: <a href="about:blank">here</a> is a link</p>',
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("name", "") should NOT remove the <a> element');
+    is(inputEvents.length, 0,
+      description + condition + ': nsIHTMLEditor.removeInlineProperty("name", "") should NOT cause an "input" event');
+  }
+
+  editor.removeEventListener("input", onInput);
+
+  SimpleTest.finish();
+});
+
+function getHTMLEditor() {
+  var Ci = SpecialPowers.Ci;
+  var editingSession = SpecialPowers.wrap(window).docShell.editingSession;
+  return editingSession.getEditorForWindow(window).QueryInterface(Ci.nsIHTMLEditor);
+}
+
+</script>
+</body>
+
+</html>
--- a/editor/nsIHTMLEditor.idl
+++ b/editor/nsIHTMLEditor.idl
@@ -83,31 +83,27 @@ interface nsIHTMLEditor : nsISupports
   /**
    * removeAllInlineProperties() deletes all the inline properties from all
    * text in the current selection.
    */
   void removeAllInlineProperties();
 
 
   /**
-   * removeInlineProperty() deletes the properties from all text in the current
-   * selection.  If aProperty is not set on the selection, nothing is done.
+   * removeInlineProperty() removes a property which changes inline style of
+   * text.  E.g., bold, italic, super and sub.
    *
-   * @param aProperty   the property to remove from the selection
-   *                    All atoms are for normal HTML tags (e.g.:
-   *                    nsIEditorProperty::font) except when you want to
-   *                    remove just links and not named anchors.
-   *                    For that, use nsIEditorProperty::href
-   * @param aAttribute  the attribute of the property, if applicable.
-   *                    May be null.
-   *                    Example: aProperty=nsIEditorProptery::font,
-   *                    aAttribute="color"
-   *                    nsIEditProperty::allAttributes is special.
-   *                    It indicates that all content-based text properties
-   *                    are to be removed from the selection.
+   * @param aProperty   Tag name whcih represents the inline style you want to
+   *                    remove.  E.g., "strong", "b", etc.
+   *                    If "href", <a> element which has href attribute will be
+   *                    removed.
+   *                    If "name", <a> element which has non-empty name
+   *                    attribute will be removed.
+   * @param aAttribute  If aProperty is "font", aAttribute should be "face",
+   *                    "size", "color" or "bgcolor".
    */
   void removeInlineProperty(in AString aProperty, in AString aAttribute);
 
   /**
    *  Increase font size for text in selection by 1 HTML unit
    *  All existing text is scanned for existing <FONT SIZE> attributes
    *  so they will be incremented instead of inserting new <FONT> tag
    */
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -328,16 +328,17 @@ WebRenderBridgeParent::WebRenderBridgePa
   , mChildLayersObserverEpoch{0}
   , mParentLayersObserverEpoch{0}
   , mWrEpoch{0}
   , mIdNamespace(aApi->GetNamespace())
   , mPaused(false)
   , mDestroyed(false)
   , mReceivedDisplayList(false)
   , mIsFirstPaint(true)
+  , mSkippedComposite(false)
 {
   MOZ_ASSERT(mAsyncImageManager);
   MOZ_ASSERT(mAnimStorage);
   mAsyncImageManager->AddPipeline(mPipelineId, this);
   if (IsRootWebRenderBridgeParent()) {
     MOZ_ASSERT(!mCompositorScheduler);
     mCompositorScheduler = new CompositorVsyncScheduler(this, mWidget);
   }
@@ -349,16 +350,17 @@ WebRenderBridgeParent::WebRenderBridgePa
   , mChildLayersObserverEpoch{0}
   , mParentLayersObserverEpoch{0}
   , mWrEpoch{0}
   , mIdNamespace{0}
   , mPaused(false)
   , mDestroyed(true)
   , mReceivedDisplayList(false)
   , mIsFirstPaint(false)
+  , mSkippedComposite(false)
 {
 }
 
 /* static */ WebRenderBridgeParent*
 WebRenderBridgeParent::CreateDestroyed(const wr::PipelineId& aPipelineId)
 {
   return new WebRenderBridgeParent(aPipelineId);
 }
@@ -1772,35 +1774,44 @@ WebRenderBridgeParent::SampleAnimations(
       }
     }
   }
 
   return isAnimating;
 }
 
 void
+WebRenderBridgeParent::CompositeIfNeeded()
+{
+  if (mSkippedComposite) {
+    mSkippedComposite = false;
+    CompositeToTarget(nullptr, nullptr);
+  }
+}
+
+void
 WebRenderBridgeParent::CompositeToTarget(gfx::DrawTarget* aTarget, const gfx::IntRect* aRect)
 {
   // This function should only get called in the root WRBP
   MOZ_ASSERT(IsRootWebRenderBridgeParent());
 
   // The two arguments are part of the CompositorVsyncSchedulerOwner API but in
   // this implementation they should never be non-null.
   MOZ_ASSERT(aTarget == nullptr);
   MOZ_ASSERT(aRect == nullptr);
 
   AUTO_PROFILER_TRACING("Paint", "CompositeToTarget");
   if (mPaused || !mReceivedDisplayList) {
     mPreviousFrameTimeStamp = TimeStamp();
     return;
   }
 
-  if (wr::RenderThread::Get()->TooManyPendingFrames(mApi->GetId())) {
+  if (mSkippedComposite || wr::RenderThread::Get()->TooManyPendingFrames(mApi->GetId())) {
     // Render thread is busy, try next time.
-    mCompositorScheduler->ScheduleComposition();
+    mSkippedComposite = true;
     mPreviousFrameTimeStamp = TimeStamp();
 
     // Record that we skipped presenting a frame for
     // all pending transactions that have finished scene building.
     for (auto& id : mPendingTransactionIds) {
       if (id.mSceneBuiltTime) {
         id.mSkippedComposites++;
       }
--- a/gfx/layers/wr/WebRenderBridgeParent.h
+++ b/gfx/layers/wr/WebRenderBridgeParent.h
@@ -175,16 +175,18 @@ public:
                                             const TimeStamp& aCompositeStartTime,
                                             const TimeStamp& aRenderStartTime,
                                             const TimeStamp& aEndTime,
                                             UiCompositorControllerParent* aUiController,
                                             wr::RendererStats* aStats = nullptr,
                                             nsTArray<FrameStats>* aOutputStats = nullptr);
   void NotifySceneBuiltForEpoch(const wr::Epoch& aEpoch, const TimeStamp& aEndTime);
 
+  void CompositeIfNeeded();
+
   TextureFactoryIdentifier GetTextureFactoryIdentifier();
 
   void ExtractImageCompositeNotifications(nsTArray<ImageCompositeNotificationInfo>* aNotifications);
 
   wr::Epoch GetCurrentEpoch() const { return mWrEpoch; }
   wr::IdNamespace GetIdNamespace()
   {
     return mIdNamespace;
@@ -380,14 +382,15 @@ private:
   std::queue<CompositorAnimationIdsForEpoch> mCompositorAnimationsToDelete;
   wr::Epoch mWrEpoch;
   wr::IdNamespace mIdNamespace;
 
   bool mPaused;
   bool mDestroyed;
   bool mReceivedDisplayList;
   bool mIsFirstPaint;
+  bool mSkippedComposite;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // mozilla_layers_WebRenderBridgeParent_h
--- a/gfx/webrender_bindings/RenderThread.cpp
+++ b/gfx/webrender_bindings/RenderThread.cpp
@@ -361,16 +361,20 @@ NotifyDidRender(layers::CompositorBridge
     aBridge->NotifyPipelineRendered(
         info.epochs.data[i].pipeline_id,
         info.epochs.data[i].epoch,
         aCompositeStart,
         aRenderStart,
         aEnd,
         &aStats);
   }
+
+  if (aBridge->GetWrBridge()) {
+    aBridge->GetWrBridge()->CompositeIfNeeded();
+  }
 }
 
 void
 RenderThread::UpdateAndRender(wr::WindowId aWindowId,
                               const TimeStamp& aStartTime,
                               bool aRender,
                               const Maybe<gfx::IntSize>& aReadbackSize,
                               const Maybe<Range<uint8_t>>& aReadbackBuffer,
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-7f5c575c965520adffc488c76d6b0f6b4cefe9fc
+a2b4202242d937d328eda21c2d9fcfece609283e
--- a/gfx/wr/webrender/src/resource_cache.rs
+++ b/gfx/wr/webrender/src/resource_cache.rs
@@ -31,16 +31,17 @@ use image::{compute_tile_range, for_each
 use internal_types::{FastHashMap, FastHashSet, TextureSource, TextureUpdateList};
 use profiler::{ResourceProfileCounters, TextureCacheProfileCounters};
 use render_backend::{FrameId, FrameStamp};
 use render_task::{RenderTaskCache, RenderTaskCacheKey, RenderTaskId};
 use render_task::{RenderTaskCacheEntry, RenderTaskCacheEntryHandle, RenderTaskTree};
 use smallvec::SmallVec;
 use std::collections::hash_map::Entry::{self, Occupied, Vacant};
 use std::collections::hash_map::IterMut;
+use std::collections::VecDeque;
 use std::{cmp, mem};
 use std::fmt::Debug;
 use std::hash::Hash;
 use std::os::raw::c_void;
 #[cfg(any(feature = "capture", feature = "replay"))]
 use std::path::PathBuf;
 use std::sync::{Arc, RwLock};
 use texture_cache::{TextureCache, TextureCacheHandle, Eviction};
@@ -431,16 +432,19 @@ pub struct ResourceCache {
     rasterized_blob_images: FastHashMap<BlobImageKey, RasterizedBlob>,
     blob_image_templates: FastHashMap<BlobImageKey, BlobImageTemplate>,
 
     // If while building a frame we encounter blobs that we didn't already
     // rasterize, add them to this list and rasterize them synchronously.
     missing_blob_images: Vec<BlobImageParams>,
     // The rasterizer associated with the current scene.
     blob_image_rasterizer: Option<Box<AsyncBlobImageRasterizer>>,
+    // A log of the last three frames worth of deleted image keys kept
+    // for debugging purposes.
+    deleted_blob_keys: VecDeque<Vec<BlobImageKey>>
 }
 
 impl ResourceCache {
     pub fn new(
         texture_cache: TextureCache,
         glyph_rasterizer: GlyphRasterizer,
         blob_image_handler: Option<Box<BlobImageHandler>>,
     ) -> Self {
@@ -455,16 +459,18 @@ impl ResourceCache {
             current_frame_id: FrameId::INVALID,
             pending_image_requests: FastHashSet::default(),
             glyph_rasterizer,
             blob_image_handler,
             rasterized_blob_images: FastHashMap::default(),
             blob_image_templates: FastHashMap::default(),
             missing_blob_images: Vec::new(),
             blob_image_rasterizer: None,
+            // We want to keep three frames worth of delete blob keys
+            deleted_blob_keys: vec![Vec::new(), Vec::new(), Vec::new()].into(),
         }
     }
 
     pub fn max_texture_size(&self) -> i32 {
         self.texture_cache.max_texture_size()
     }
 
     fn should_tile(limit: i32, descriptor: &ImageDescriptor, data: &CachedImageData) -> bool {
@@ -883,16 +889,17 @@ impl ResourceCache {
         if let Some(mut cached) = self.cached_images.remove(&image_key) {
             cached.drop_from_cache(&mut self.texture_cache);
         }
 
         match value {
             Some(image) => if image.data.is_blob() {
                 let blob_key = BlobImageKey(image_key);
                 self.blob_image_handler.as_mut().unwrap().delete(blob_key);
+                self.deleted_blob_keys.back_mut().unwrap().push(blob_key);
                 self.blob_image_templates.remove(&blob_key);
                 self.rasterized_blob_images.remove(&blob_key);
             },
             None => {
                 warn!("Delete the non-exist key");
                 debug!("key={:?}", image_key);
             }
         }
@@ -1452,16 +1459,20 @@ impl ResourceCache {
 
     pub fn begin_frame(&mut self, stamp: FrameStamp) {
         debug_assert_eq!(self.state, State::Idle);
         self.state = State::AddResources;
         self.texture_cache.begin_frame(stamp);
         self.cached_glyphs.begin_frame(&self.texture_cache, &self.cached_render_tasks, &mut self.glyph_rasterizer);
         self.cached_render_tasks.begin_frame(&mut self.texture_cache);
         self.current_frame_id = stamp.frame_id();
+
+        // pop the old frame and push a new one
+        self.deleted_blob_keys.pop_front();
+        self.deleted_blob_keys.push_back(Vec::new());
     }
 
     pub fn block_until_all_resources_added(
         &mut self,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         texture_cache_profile: &mut TextureCacheProfileCounters,
     ) {
@@ -1497,16 +1508,22 @@ impl ResourceCache {
             return;
         }
 
         self.blob_image_handler
             .as_mut()
             .unwrap()
             .prepare_resources(&self.resources, &self.missing_blob_images);
 
+
+        for blob_image in &self.missing_blob_images {
+            if !self.blob_image_templates.contains_key(&blob_image.request.key) {
+                panic!("missing blob image key {:?} deleted: {:?}", blob_image, self.deleted_blob_keys);
+            }
+        }
         let is_low_priority = false;
         let rasterized_blobs = self.blob_image_rasterizer
             .as_mut()
             .unwrap()
             .rasterize(&self.missing_blob_images, is_low_priority);
 
         self.add_rasterized_blob_images(rasterized_blobs);
 
--- a/gfx/wr/webrender/src/spatial_node.rs
+++ b/gfx/wr/webrender/src/spatial_node.rs
@@ -151,30 +151,30 @@ impl SpatialNode {
             _ if old_scroll_info.offset != LayoutVector2D::zero() => {
                 warn!("Tried to scroll a non-scroll node.")
             }
             _ => {}
         }
     }
 
     pub fn set_scroll_origin(&mut self, origin: &LayoutPoint, clamp: ScrollClamping) -> bool {
-        let scrollable_size = self.scrollable_size();
-        let scrollable_width = scrollable_size.width;
-        let scrollable_height = scrollable_size.height;
-
         let scrolling = match self.node_type {
             SpatialNodeType::ScrollFrame(ref mut scrolling) => scrolling,
             _ => {
                 warn!("Tried to scroll a non-scroll node.");
                 return false;
             }
         };
 
         let new_offset = match clamp {
             ScrollClamping::ToContentBounds => {
+                let scrollable_size = scrolling.scrollable_size;
+                let scrollable_width = scrollable_size.width;
+                let scrollable_height = scrollable_size.height;
+
                 if scrollable_height <= 0. && scrollable_width <= 0. {
                     return false;
                 }
 
                 let origin = LayoutPoint::new(origin.x.max(0.0), origin.y.max(0.0));
                 LayoutVector2D::new(
                     (-origin.x).max(-scrollable_width).min(0.0).round(),
                     (-origin.y).max(-scrollable_height).min(0.0).round(),
@@ -469,44 +469,35 @@ impl SpatialNode {
         // reference frame and the offset is the accumulated offset of all the nodes
         // between us and the parent reference frame. If we are a reference frame,
         // we need to reset both these values.
         match self.node_type {
             SpatialNodeType::StickyFrame(ref info) => {
                 // We don't translate the combined rect by the sticky offset, because sticky
                 // offsets actually adjust the node position itself, whereas scroll offsets
                 // only apply to contents inside the node.
-                state.parent_accumulated_scroll_offset =
-                    info.current_offset + state.parent_accumulated_scroll_offset;
+                state.parent_accumulated_scroll_offset += info.current_offset;
             }
             SpatialNodeType::ScrollFrame(ref scrolling) => {
-                state.parent_accumulated_scroll_offset =
-                    scrolling.offset + state.parent_accumulated_scroll_offset;
+                state.parent_accumulated_scroll_offset += scrolling.offset;
                 state.nearest_scrolling_ancestor_offset = scrolling.offset;
                 state.nearest_scrolling_ancestor_viewport = scrolling.viewport_rect;
             }
             SpatialNodeType::ReferenceFrame(ref info) => {
                 state.parent_reference_frame_transform = self.world_viewport_transform;
                 state.parent_accumulated_scroll_offset = LayoutVector2D::zero();
                 state.coordinate_system_relative_scale_offset = self.coordinate_system_relative_scale_offset;
                 let translation = -info.origin_in_parent_reference_frame;
                 state.nearest_scrolling_ancestor_viewport =
                     state.nearest_scrolling_ancestor_viewport
                        .translate(&translation);
             }
         }
     }
 
-    pub fn scrollable_size(&self) -> LayoutSize {
-        match self.node_type {
-           SpatialNodeType::ScrollFrame(state) => state.scrollable_size,
-            _ => LayoutSize::zero(),
-        }
-    }
-
     pub fn scroll(&mut self, scroll_location: ScrollLocation) -> bool {
         let scrolling = match self.node_type {
             SpatialNodeType::ScrollFrame(ref mut scrolling) => scrolling,
             _ => return false,
         };
 
         let delta = match scroll_location {
             ScrollLocation::Delta(delta) => delta,
--- a/gfx/wr/webrender_api/src/display_item.rs
+++ b/gfx/wr/webrender_api/src/display_item.rs
@@ -296,17 +296,17 @@ impl NormalBorder {
     }
 
     /// Normalizes a border so that we don't render disallowed stuff, like inset
     /// borders that are less than two pixels wide.
     #[inline]
     pub fn normalize(&mut self, widths: &LayoutSideOffsets) {
         debug_assert!(
             self.do_aa || self.can_disable_antialiasing(),
-            "Unexpected disabled-antialising in a border, likely won't work or will be ignored"
+            "Unexpected disabled-antialiasing in a border, likely won't work or will be ignored"
         );
 
         #[inline]
         fn renders_small_border_solid(style: BorderStyle) -> bool {
             match style {
                 BorderStyle::Groove |
                 BorderStyle::Ridge => true,
                 _ => false,
--- a/image/SurfaceCache.cpp
+++ b/image/SurfaceCache.cpp
@@ -1223,16 +1223,17 @@ public:
 
   NS_IMETHOD
   CollectReports(nsIHandleReportCallback* aHandleReport,
                  nsISupports*             aData,
                  bool                     aAnonymize) override
   {
     StaticMutexAutoLock lock(sInstanceMutex);
 
+    // clang-format off
     // We have explicit memory reporting for the surface cache which is more
     // accurate than the cost metrics we report here, but these metrics are
     // still useful to report, since they control the cache's behavior.
     MOZ_COLLECT_REPORT(
       "imagelib-surface-cache-estimated-total",
       KIND_OTHER, UNITS_BYTES, (mMaxCost - mAvailableCost),
 "Estimated total memory used by the imagelib surface cache.");
 
@@ -1241,16 +1242,17 @@ public:
       KIND_OTHER, UNITS_BYTES, mLockedCost,
 "Estimated memory used by locked surfaces in the imagelib surface cache.");
 
     MOZ_COLLECT_REPORT(
       "imagelib-surface-cache-overflow-count",
       KIND_OTHER, UNITS_COUNT, mOverflowCount,
 "Count of how many times the surface cache has hit its capacity and been "
 "unable to insert a new surface.");
+    // clang-format on
 
     return NS_OK;
   }
 
   void CollectSizeOfSurfaces(const ImageKey                  aImageKey,
                              nsTArray<SurfaceMemoryCounter>& aCounters,
                              MallocSizeOf                    aMallocSizeOf,
                              const StaticMutexAutoLock&      aAutoLock)
--- a/ipc/chromium/src/chrome/common/mach_ipc_mac.h
+++ b/ipc/chromium/src/chrome/common/mach_ipc_mac.h
@@ -62,18 +62,20 @@
 //    message.AddDescriptor(mach_thread_self());   // this thread
 //
 //    char messageString[] = "Hello server!\n";
 //    message.SetData(messageString, strlen(messageString)+1);
 //    // timeout 1000ms
 //    kern_return_t result = sender.SendMessage(message, 1000);
 //
 
+#ifndef PRINT_MACH_RESULT
 #define PRINT_MACH_RESULT(result_, message_) \
   printf(message_" %s (%d)\n", mach_error_string(result_), result_ );
+#endif
 
 //==============================================================================
 // A wrapper class for mach_msg_port_descriptor_t (with same memory layout)
 // with convenient constructors and accessors
 class MachMsgPortDescriptor : public mach_msg_port_descriptor_t {
  public:
   // General-purpose constructor
   MachMsgPortDescriptor(mach_port_t in_name,
--- a/ipc/testshell/XPCShellEnvironment.cpp
+++ b/ipc/testshell/XPCShellEnvironment.cpp
@@ -337,27 +337,25 @@ XPCShellEnvironment::ProcessFile(JSConte
         /* Clear any pending exception from previous failed compiles.  */
         JS_ClearPendingException(cx);
 
         JS::CompileOptions options(cx);
         options.setFileAndLine("typein", startline);
 
         JS::Rooted<JSScript*> script(cx);
         if (JS::CompileUtf8(cx, options, buffer, strlen(buffer), &script)) {
-            JS::WarningReporter older;
 
             ok = JS_ExecuteScript(cx, script, &result);
             if (ok && !result.isUndefined()) {
                 /* Suppress warnings from JS::ToString(). */
-                older = JS::SetWarningReporter(cx, nullptr);
+                JS::AutoSuppressWarningReporter suppressWarnings(cx);
                 str = JS::ToString(cx, result);
                 JS::UniqueChars bytes;
                 if (str)
                     bytes = JS_EncodeStringToLatin1(cx, str);
-                JS::SetWarningReporter(cx, older);
 
                 if (!!bytes)
                     fprintf(stdout, "%s\n", bytes.get());
                 else
                     ok = false;
             }
         }
     } while (!hitEOF && !env->IsQuitting());
@@ -507,22 +505,22 @@ XPCShellEnvironment::EvaluateString(cons
 
   if (aResult) {
       aResult->Truncate();
   }
 
   JS::Rooted<JS::Value> result(cx);
   bool ok = JS_ExecuteScript(cx, script, &result);
   if (ok && !result.isUndefined()) {
-      JS::WarningReporter old = JS::SetWarningReporter(cx, nullptr);
+      /* Suppress warnings from JS::ToString(). */
+      JS::AutoSuppressWarningReporter suppressWarnings(cx);
       JSString* str = JS::ToString(cx, result);
       nsAutoJSString autoStr;
       if (str)
           autoStr.init(cx, str);
-      JS::SetWarningReporter(cx, old);
 
       if (!autoStr.IsEmpty() && aResult) {
           aResult->Assign(autoStr);
       }
   }
 
   return true;
 }
--- a/js/public/Stream.h
+++ b/js/public/Stream.h
@@ -6,200 +6,183 @@
 
 /*
  * JSAPI functions and callbacks related to WHATWG Stream objects.
  *
  * Much of the API here mirrors the standard algorithms and standard JS methods
  * of the objects defined in the Streams standard. One difference is that the
  * functionality of the JS controller object is exposed to C++ as functions
  * taking ReadableStream instances instead, for convenience.
- *
- * ## External streams
- *
- * Embeddings can create ReadableStreams that read from custom C++ data
- * sources. Such streams are always byte streams: the chunks they produce are
- * typed arrays (and they will support ReadableStreamBYOBReader once we have
- * it).
- *
- * When creating an "external readable stream" using
- * JS::NewReadableExternalSourceStreamObject, an underlying source can be
- * passed to be stored on the stream. The underlying source is treated as an
- * opaque void* pointer by the JS engine: it's purely meant as a reference to
- * be used by the embedding to identify whatever actual source it uses to
- * supply data for the stream.
- *
- * External readable streams are optimized to allow the embedding to interact
- * with them with a minimum of overhead: chunks aren't enqueued as individual
- * typed arrays; instead, the embedding only updates the amount of data
- * available using ReadableStreamUpdateDataAvailableFromSource. When JS
- * requests data from a reader, WriteIntoReadRequestBufferCallback is invoked,
- * asking the embedding to write data directly into the buffer we're about to
- * hand to JS.
- *
- * Additionally, ReadableStreamGetExternalUnderlyingSource can be used to get
- * the void* pointer to the underlying source. This locks the stream until it
- * is released again using JS::ReadableStreamReleaseExternalUnderlyingSource.
- *
- * Embeddings can use this to optimize away the JS `ReadableStream` overhead
- * when an embedding-defined C++ stream is passed to an embedding-defined C++
- * consumer. For example, consider a ServiceWorker piping a `fetch` Response
- * body to a TextDecoder. Instead of copying chunks of data into JS typed array
- * buffers and creating a Promise per chunk, only to immediately resolve the
- * Promises and read the data out again, the embedding can directly feed the
- * incoming data to the TextDecoder.
  */
 
 #ifndef js_Stream_h
 #define js_Stream_h
 
 #include <stddef.h>
 
 #include "jstypes.h"
 
 #include "js/RootingAPI.h"
 #include "js/TypeDecls.h"
 
 namespace JS {
 
 /**
- * ## Readable stream callbacks
+ * Abstract base class for external underlying sources.
+ *
+ * The term "underlying source" is defined in the Streams spec:
+ *   https://streams.spec.whatwg.org/#underlying-source
+ *
+ * A `ReadableStreamUnderlyingSource` is an underlying source that is
+ * implemented in C++ rather than JS. It can be passed to
+ * `JS::NewReadableExternalSourceStreamObject` to create a custom,
+ * embedding-defined ReadableStream.
+ *
+ * There are several API difference between this class and the standard API for
+ * underlying sources implemented in JS:
+ *
+ * -   JS underlying sources can be either byte sources or non-byte sources.
+ *     External underlying source are always byte sources.
+ *
+ * -   The C++ API does not bother with controller objects. Instead of using
+ *     controller methods, the underlying source directly calls API functions
+ *     like JS::ReadableStream{UpdateDataAvailableFromSource,Close,Error}.
  *
- * Compartment safety: All callbacks (except Finalize) receive `cx` and
+ * -   External readable streams are optimized to allow the embedding to
+ *     interact with them with a minimum of overhead: chunks aren't enqueued as
+ *     individual typed arrays; instead, the embedding only updates the amount
+ *     of data available using
+ *     JS::ReadableStreamUpdateDataAvailableFromSource. When JS requests data
+ *     from a reader, writeIntoReadRequestBuffer is invoked, asking the
+ *     embedding to write data directly into the buffer we're about to hand to
+ *     JS.
+ *
+ * -   The C++ API provides extra callbacks onClosed() and onErrored().
+ *
+ * -   This class has a `finalize()` method, because C++ cares about lifetimes.
+ *
+ * Additionally, ReadableStreamGetExternalUnderlyingSource can be used to get
+ * the pointer to the underlying source. This locks the stream until it is
+ * released again using JS::ReadableStreamReleaseExternalUnderlyingSource.
+ *
+ * Embeddings can use this to optimize away the JS `ReadableStream` overhead
+ * when an embedding-defined C++ stream is passed to an embedding-defined C++
+ * consumer. For example, consider a ServiceWorker piping a `fetch` Response
+ * body to a TextDecoder. Instead of copying chunks of data into JS typed array
+ * buffers and creating a Promise per chunk, only to immediately resolve the
+ * Promises and read the data out again, the embedding can directly feed the
+ * incoming data to the TextDecoder.
+ *
+ * Compartment safety: All methods (except `finalize`) receive `cx` and
  * `stream` arguments. SpiderMonkey enters the realm of the stream object
- * before invoking these callbacks, so `stream` is never a wrapper. Other
+ * before invoking these methods, so `stream` is never a wrapper. Other
  * arguments may be wrappers.
  */
+class JS_PUBLIC_API ReadableStreamUnderlyingSource
+{
+  public:
+    virtual ~ReadableStreamUnderlyingSource() {}
 
-/**
- * Invoked whenever a reader desires more data from a ReadableStream's
- * embedding-provided underlying source.
- *
- * The given |desiredSize| is the absolute size, not a delta from the previous
- * desired size.
- */
-typedef void
-(* RequestReadableStreamDataCallback)(JSContext* cx, HandleObject stream,
-                                      void* underlyingSource, size_t desiredSize);
+    /**
+     * Invoked whenever a reader desires more data from this source.
+     *
+     * The given `desiredSize` is the absolute size, not a delta from the
+     * previous desired size.
+     */
+    virtual void requestData(JSContext* cx, HandleObject stream, size_t desiredSize) = 0;
 
-/**
- * Invoked to cause the embedding to fill the given |buffer| with data from
- * the given embedding-provided underlying source.
- *
- * This can only happen after the embedding has updated the amount of data
- * available using JS::ReadableStreamUpdateDataAvailableFromSource. If at
- * least one read request is pending when
- * JS::ReadableStreamUpdateDataAvailableFromSource is called,
- * the WriteIntoReadRequestBufferCallback is invoked immediately from under
- * the call to JS::WriteIntoReadRequestBufferCallback. If not, it is invoked
- * if and when a new read request is made.
- *
- * Note: This callback *must not cause GC*, because that could potentially
- * invalidate the |buffer| pointer.
- */
-typedef void
-(* WriteIntoReadRequestBufferCallback)(JSContext* cx, HandleObject stream,
-                                       void* underlyingSource, void* buffer, size_t length,
-                                       size_t* bytesWritten);
+    /**
+     * Invoked to cause the embedding to fill the given `buffer` with data from
+     * this underlying source.
+     *
+     * This is called only after the embedding has updated the amount of data
+     * available using JS::ReadableStreamUpdateDataAvailableFromSource. If at
+     * least one read request is pending when
+     * JS::ReadableStreamUpdateDataAvailableFromSource is called, this method
+     * is invoked immediately from under the call to
+     * JS::ReadableStreamUpdateDataAvailableFromSource. If not, it is invoked
+     * if and when a new read request is made.
+     *
+     * Note: This method *must not cause GC*, because that could potentially
+     * invalidate the `buffer` pointer.
+     */
+    virtual void writeIntoReadRequestBuffer(JSContext* cx, HandleObject stream,
+                                            void* buffer, size_t length,
+                                            size_t* bytesWritten) = 0;
 
-/**
- * Invoked in reaction to the ReadableStream being canceled to allow the
- * embedding to free the underlying source.
- *
- * This is equivalent to calling |cancel| on non-external underlying sources
- * provided to the ReadableStream constructor in JavaScript.
- *
- * The given |reason| is the JS::Value that was passed as an argument to
- * ReadableStream#cancel().
- *
- * The returned JS::Value will be used to resolve the Promise returned by
- * ReadableStream#cancel().
- */
-typedef Value
-(* CancelReadableStreamCallback)(JSContext* cx, HandleObject stream,
-                                 void* underlyingSource, HandleValue reason);
-
-/**
- * Invoked in reaction to a ReadableStream with an embedding-provided
- * underlying source being closed.
- */
-typedef void
-(* ReadableStreamClosedCallback)(JSContext* cx, HandleObject stream, void* underlyingSource);
+    /**
+     * Invoked in reaction to the ReadableStream being canceled. This is
+     * equivalent to the `cancel` method on non-external underlying sources
+     * provided to the ReadableStream constructor in JavaScript.
+     *
+     * The underlying source may free up some resources in this method, but
+     * `*this` must not be destroyed until `finalize()` is called.
+     *
+     * The given `reason` is the JS::Value that was passed as an argument to
+     * ReadableStream#cancel().
+     *
+     * The returned JS::Value will be used to resolve the Promise returned by
+     * ReadableStream#cancel().
+     */
+    virtual Value cancel(JSContext* cx, HandleObject stream, HandleValue reason) = 0;
 
-/**
- * Invoked in reaction to a ReadableStream with an embedding-provided
- * underlying source being errored with the
- * given reason.
- */
-typedef void
-(* ReadableStreamErroredCallback)(JSContext* cx, HandleObject stream, void* underlyingSource,
-                                  HandleValue reason);
+    /**
+     * Invoked when the associated ReadableStream becomes closed.
+     *
+     * The underlying source may free up some resources in this method, but
+     * `*this` must not be destroyed until `finalize()` is called.
+     */
+    virtual void onClosed(JSContext* cx, HandleObject stream) = 0;
 
-/**
- * Invoked in reaction to a ReadableStream with an embedding-provided
- * underlying source being finalized. Only the underlying source is passed
- * as an argument, while the ReadableStream itself is not to prevent the
- * embedding from operating on a JSObject that might not be in a valid state
- * anymore.
- *
- * Note: the ReadableStream might be finalized on a background thread. That
- * means this callback might be invoked from an arbitrary thread, which the
- * embedding must be able to handle.
- */
-typedef void
-(* ReadableStreamFinalizeCallback)(void* underlyingSource);
+    /**
+     * Invoked when the associated ReadableStream becomes errored.
+     *
+     * The underlying source may free up some resources in this method, but
+     * `*this` must not be destroyed until `finalize()` is called.
+     */
+    virtual void onErrored(JSContext* cx, HandleObject stream, HandleValue reason) = 0;
 
-/**
- * Sets runtime-wide callbacks to use for interacting with embedding-provided
- * hooks for operating on ReadableStream instances.
- *
- * See the documentation for the individual callback types for details.
- */
-extern JS_PUBLIC_API void
-SetReadableStreamCallbacks(JSContext* cx,
-                           RequestReadableStreamDataCallback dataRequestCallback,
-                           WriteIntoReadRequestBufferCallback writeIntoReadRequestCallback,
-                           CancelReadableStreamCallback cancelCallback,
-                           ReadableStreamClosedCallback closedCallback,
-                           ReadableStreamErroredCallback erroredCallback,
-                           ReadableStreamFinalizeCallback finalizeCallback);
-
-extern JS_PUBLIC_API bool
-HasReadableStreamCallbacks(JSContext* cx);
+    /**
+     * Invoked when the associated ReadableStream object is finalized. The
+     * stream object is not passed as an argument, as it might not be in a
+     * valid state anymore.
+     *
+     * Note: Finalization can happen on a background thread, so the embedding
+     * must be prepared for `finalize()` to be invoked from any thread.
+     */
+    virtual void finalize() = 0;
+};
 
 /**
  * Returns a new instance of the ReadableStream builtin class in the current
  * compartment, configured as a default stream.
  * If a |proto| is passed, that gets set as the instance's [[Prototype]]
  * instead of the original value of |ReadableStream.prototype|.
  */
 extern JS_PUBLIC_API JSObject*
 NewReadableDefaultStreamObject(JSContext* cx, HandleObject underlyingSource = nullptr,
                                HandleFunction size = nullptr, double highWaterMark = 1,
                                HandleObject proto = nullptr);
 
 /**
  * Returns a new instance of the ReadableStream builtin class in the current
- * compartment, with the right slot layout. If a |proto| is passed, that gets
- * set as the instance's [[Prototype]] instead of the original value of
- * |ReadableStream.prototype|.
+ * compartment. If a |proto| is passed, that gets set as the instance's
+ * [[Prototype]] instead of the original value of |ReadableStream.prototype|.
  *
  * The instance is optimized for operating as a byte stream backed by an
- * embedding-provided underlying source, using the callbacks set via
- * |JS::SetReadableStreamCallbacks|.
+ * embedding-provided underlying source, using the virtual methods of
+ * |underlyingSource| as callbacks.
  *
- * Note: the embedding is responsible for ensuring that the pointer to the
- * underlying source stays valid as long as the stream can be read from.
- * The underlying source can be freed if the tree is canceled or errored.
- * It can also be freed if the stream is destroyed. The embedding is notified
- * of that using ReadableStreamFinalizeCallback.
- *
- * Note: |underlyingSource| must have an even address.
+ * Note: The embedding must ensure that |*underlyingSource| lives as long as
+ * the new stream object. The JS engine will call the finalize() method when
+ * the stream object is destroyed.
  */
 extern JS_PUBLIC_API JSObject*
-NewReadableExternalSourceStreamObject(JSContext* cx, void* underlyingSource,
+NewReadableExternalSourceStreamObject(JSContext* cx,
+                                      ReadableStreamUnderlyingSource* underlyingSource,
                                       HandleObject proto = nullptr);
 
 /**
  * Returns the embedding-provided underlying source of the given |stream|.
  *
  * Can be used to optimize operations if both the underlying source and the
  * intended sink are embedding-provided. In that case it might be
  * preferrable to pipe data directly from source to sink without interacting
@@ -217,17 +200,18 @@ NewReadableExternalSourceStreamObject(JS
  * have a Promise to resolve/reject, which a reader provides.
  *
  * Asserts that |stream| is a ReadableStream object or an unwrappable wrapper
  * for one.
  *
  * Asserts that the stream has an embedding-provided underlying source.
  */
 extern JS_PUBLIC_API bool
-ReadableStreamGetExternalUnderlyingSource(JSContext* cx, HandleObject stream, void** source);
+ReadableStreamGetExternalUnderlyingSource(JSContext* cx, HandleObject stream,
+                                          ReadableStreamUnderlyingSource** source);
 
 /**
  * Releases the embedding-provided underlying source of the given |stream|,
  * returning the stream into an unlocked state.
  *
  * Asserts that the stream was locked through
  * ReadableStreamGetExternalUnderlyingSource.
  *
@@ -240,17 +224,17 @@ extern JS_PUBLIC_API bool
 ReadableStreamReleaseExternalUnderlyingSource(JSContext* cx, HandleObject stream);
 
 /**
  * Update the amount of data available at the underlying source of the given
  * |stream|.
  *
  * Can only be used for streams with an embedding-provided underlying source.
  * The JS engine will use the given value to satisfy read requests for the
- * stream by invoking the JS::WriteIntoReadRequestBuffer callback.
+ * stream by invoking the writeIntoReadRequestBuffer method.
  *
  * Asserts that |stream| is a ReadableStream object or an unwrappable wrapper
  * for one.
  */
 extern JS_PUBLIC_API bool
 ReadableStreamUpdateDataAvailableFromSource(JSContext* cx, HandleObject stream,
                                             uint32_t availableData);
 
@@ -371,23 +355,22 @@ ReadableStreamTee(JSContext* cx, HandleO
  *
  * Asserts that |stream| is a ReadableStream object or an unwrappable wrapper
  * for one.
  */
 extern JS_PUBLIC_API bool
 ReadableStreamGetDesiredSize(JSContext* cx, JSObject* stream, bool* hasValue, double* value);
 
 /**
- * Closes the given ReadableStream.
- *
- * Throws a TypeError and returns false if the closing operation fails.
+ * Close the given ReadableStream. This is equivalent to `controller.close()`
+ * in JS.
  *
- * Note: This is semantically equivalent to the |close| method on
- * the stream controller's prototype in JS. We expose it with the stream
- * itself as a target for simplicity.
+ * This can fail with or without an exception pending under a variety of
+ * circumstances. On failure, the stream may or may not be closed, and
+ * downstream consumers may or may not have been notified.
  *
  * Asserts that |stream| is a ReadableStream object or an unwrappable wrapper
  * for one.
  */
 extern JS_PUBLIC_API bool
 ReadableStreamClose(JSContext* cx, HandleObject stream);
 
 /**
--- a/js/src/builtin/Stream.cpp
+++ b/js/src/builtin/Stream.cpp
@@ -484,29 +484,30 @@ static MOZ_MUST_USE bool
 SetUpReadableStreamDefaultController(JSContext* cx,
                                      Handle<ReadableStream*> stream,
                                      HandleValue underlyingSource,
                                      double highWaterMarkVal,
                                      HandleValue size);
 
 static MOZ_MUST_USE ReadableByteStreamController*
 CreateExternalReadableByteStreamController(JSContext* cx, Handle<ReadableStream*> stream,
-                                           void* underlyingSource);
+                                           JS::ReadableStreamUnderlyingSource* source);
 
 ReadableStream*
-ReadableStream::createExternalSourceStream(JSContext* cx, void* underlyingSource,
+ReadableStream::createExternalSourceStream(JSContext* cx,
+                                           JS::ReadableStreamUnderlyingSource* source,
                                            HandleObject proto /* = nullptr */)
 {
     Rooted<ReadableStream*> stream(cx, create(cx, proto));
     if (!stream) {
         return nullptr;
     }
 
     Rooted<ReadableStreamController*> controller(cx);
-    controller = CreateExternalReadableByteStreamController(cx, stream, underlyingSource);
+    controller = CreateExternalReadableByteStreamController(cx, stream, source);
     if (!controller) {
         return nullptr;
     }
 
     stream->setController(controller);
     return stream;
 }
 
@@ -1512,24 +1513,21 @@ ReadableStreamCloseInternal(JSContext* c
     RootedObject closedPromise(cx, unwrappedReader->closedPromise());
     if (!cx->compartment()->wrap(cx, &closedPromise)) {
         return false;
     }
     if (!ResolvePromise(cx, closedPromise, UndefinedHandleValue)) {
         return false;
     }
 
-    if (unwrappedStream->mode() == JS::ReadableStreamMode::ExternalSource &&
-        cx->runtime()->readableStreamClosedCallback)
-    {
+    if (unwrappedStream->mode() == JS::ReadableStreamMode::ExternalSource) {
         // Make sure we're in the stream's compartment.
         AutoRealm ar(cx, unwrappedStream);
-        ReadableStreamController* controller = unwrappedStream->controller();
-        void* source = controller->underlyingSource().toPrivate();
-        cx->runtime()->readableStreamClosedCallback(cx, unwrappedStream, source);
+        JS::ReadableStreamUnderlyingSource* source = unwrappedStream->controller()->externalSource();
+        source->onClosed(cx, unwrappedStream);
     }
 
     return true;
 }
 
 /**
  * Streams spec, 3.4.5. ReadableStreamCreateReadResult ( value, done, forAuthorCode )
  */
@@ -1637,32 +1635,28 @@ ReadableStreamErrorInternal(JSContext* c
     RootedObject closedPromise(cx, unwrappedReader->closedPromise());
     if (!cx->compartment()->wrap(cx, &closedPromise)) {
         return false;
     }
     if (!RejectPromise(cx, closedPromise, e)) {
         return false;
     }
 
-    if (unwrappedStream->mode() == JS::ReadableStreamMode::ExternalSource &&
-        cx->runtime()->readableStreamErroredCallback)
-    {
+    if (unwrappedStream->mode() == JS::ReadableStreamMode::ExternalSource) {
         // Make sure we're in the stream's compartment.
         AutoRealm ar(cx, unwrappedStream);
-        ReadableStreamController* controller = unwrappedStream->controller();
-        void* source = controller->underlyingSource().toPrivate();
+        JS::ReadableStreamUnderlyingSource* source = unwrappedStream->controller()->externalSource();
 
         // Ensure that the embedding doesn't have to deal with
         // mixed-compartment arguments to the callback.
         RootedValue error(cx, e);
         if (!cx->compartment()->wrap(cx, &error)) {
             return false;
         }
-
-        cx->runtime()->readableStreamErroredCallback(cx, unwrappedStream, source, error);
+        source->onErrored(cx, unwrappedStream, error);
     }
 
     return true;
 }
 
 /**
  * Streams spec, 3.4.7.
  *      ReadableStreamFulfillReadIntoRequest( stream, chunk, done )
@@ -2563,25 +2557,25 @@ ReadableStreamControllerCancelSteps(JSCo
         return ReadableStreamTee_Cancel(cx, unwrappedteeState, unwrappedDefaultController,
                                         reason);
     }
 
     if (unwrappedController->hasExternalSource()) {
         RootedValue rval(cx);
         {
             AutoRealm ar(cx, unwrappedController);
+            JS::ReadableStreamUnderlyingSource* source = unwrappedController->externalSource();
             Rooted<ReadableStream*> stream(cx, unwrappedController->stream());
-            void* source = unwrappedUnderlyingSource.toPrivate();
             RootedValue wrappedReason(cx, reason);
             if (!cx->compartment()->wrap(cx, &wrappedReason)) {
                 return nullptr;
             }
 
             cx->check(stream, wrappedReason);
-            rval = cx->runtime()->readableStreamCancelCallback(cx, stream, source, wrappedReason);
+            rval = source->cancel(cx, stream, wrappedReason);
         }
 
         if (!cx->compartment()->wrap(cx, &rval)) {
             return nullptr;
         }
         return PromiseObject::unforgeableResolve(cx, rval);
     }
 
@@ -2795,20 +2789,20 @@ ReadableStreamControllerCallPullIfNeeded
         MOZ_ASSERT(unwrappedUnderlyingSource.toObject().is<TeeState>(),
                    "tee streams and controllers are always same-compartment with the TeeState object");
         Rooted<TeeState*> unwrappedTeeState(cx,
             &unwrappedUnderlyingSource.toObject().as<TeeState>());
         pullPromise = ReadableStreamTee_Pull(cx, unwrappedTeeState);
     } else if (unwrappedController->hasExternalSource()) {
         {
             AutoRealm ar(cx, unwrappedController);
+            JS::ReadableStreamUnderlyingSource* source = unwrappedController->externalSource();
             Rooted<ReadableStream*> stream(cx, unwrappedController->stream());
-            void* source = unwrappedUnderlyingSource.toPrivate();
             double desiredSize = ReadableStreamControllerGetDesiredSizeUnchecked(unwrappedController);
-            cx->runtime()->readableStreamDataRequestCallback(cx, stream, source, desiredSize);
+            source->requestData(cx, stream, desiredSize);
         }
         pullPromise = PromiseObject::unforgeableResolve(cx, UndefinedHandleValue);
     } else {
         RootedValue underlyingSource(cx, unwrappedUnderlyingSource);
         if (!cx->compartment()->wrap(cx, &underlyingSource)) {
             return false;
         }
         pullPromise = PromiseInvokeOrNoop(cx, underlyingSource, cx->names().pull, controllerVal);
@@ -3315,32 +3309,33 @@ ReadableByteStreamController::constructo
 
 /**
  * Version of the ReadableByteStreamConstructor that's specialized for
  * handling external, embedding-provided, underlying sources.
  */
 static MOZ_MUST_USE ReadableByteStreamController*
 CreateExternalReadableByteStreamController(JSContext* cx,
                                            Handle<ReadableStream*> stream,
-                                           void* underlyingSource)
+                                           JS::ReadableStreamUnderlyingSource* source)
 {
     Rooted<ReadableByteStreamController*> controller(cx,
         NewBuiltinClassInstance<ReadableByteStreamController>(cx));
     if (!controller) {
         return nullptr;
     }
 
     // Step 3: Set this.[[controlledReadableStream]] to stream.
     controller->setStream(stream);
 
     // Step 4: Set this.[[underlyingByteSource]] to underlyingByteSource.
-    controller->setUnderlyingSource(PrivateValue(underlyingSource));
-
-    // Step 5: Set this.[[pullAgain]], and this.[[pulling]] to false.
-    controller->setFlags(ReadableStreamController::Flag_ExternalSource);
+    controller->setExternalSource(source);
+
+    // Step 5: Set this.[[pullAgain]] and this.[[pulling]] to false (implicit).
+    MOZ_ASSERT(!controller->pullAgain());
+    MOZ_ASSERT(!controller->pulling());
 
     // Step 6: Perform ! ReadableByteStreamControllerClearPendingPullIntos(this).
     // Omitted.
 
     // Step 7: Perform ! ResetQueue(this).
     controller->setQueueTotalSize(0);
 
     // Step 8: Set this.[[started]] and this.[[closeRequested]] to false.
@@ -3403,18 +3398,17 @@ ReadableByteStreamControllerFinalize(Fre
     if (controller.getFixedSlot(ReadableStreamController::Slot_Flags).isUndefined()) {
         return;
     }
 
     if (!controller.hasExternalSource()) {
         return;
     }
 
-    void* underlyingSource = controller.underlyingSource().toPrivate();
-    obj->runtimeFromAnyThread()->readableStreamFinalizeCallback(underlyingSource);
+    controller.externalSource()->finalize();
 }
 
 static const ClassOps ReadableByteStreamControllerClassOps = {
     nullptr,        /* addProperty */
     nullptr,        /* delProperty */
     nullptr,        /* enumerate */
     nullptr,        /* newEnumerate */
     nullptr,        /* resolve */
@@ -3460,34 +3454,33 @@ ReadableByteStreamControllerPullSteps(JS
     double queueTotalSize = unwrappedController->queueTotalSize();
     if (queueTotalSize > 0) {
         // Step 3.a: Assert: ! ReadableStreamGetNumReadRequests(_stream_) is 0.
         MOZ_ASSERT(ReadableStreamGetNumReadRequests(unwrappedStream) == 0);
 
         RootedObject view(cx);
 
         if (unwrappedStream->mode() == JS::ReadableStreamMode::ExternalSource) {
-            void* underlyingSource = unwrappedController->underlyingSource().toPrivate();
+            JS::ReadableStreamUnderlyingSource* source = unwrappedController->externalSource();
 
             view = JS_NewUint8Array(cx, queueTotalSize);
             if (!view) {
                 return nullptr;
             }
 
             size_t bytesWritten;
             {
                 AutoRealm ar(cx, unwrappedStream);
                 JS::AutoSuppressGCAnalysis suppressGC(cx);
                 JS::AutoCheckCannotGC noGC;
                 bool dummy;
                 void* buffer = JS_GetArrayBufferViewData(view, &dummy, noGC);
 
-                auto cb = cx->runtime()->readableStreamWriteIntoReadRequestCallback;
-                MOZ_ASSERT(cb);
-                cb(cx, unwrappedStream, underlyingSource, buffer, queueTotalSize, &bytesWritten);
+                source->writeIntoReadRequestBuffer(cx, unwrappedStream, buffer, queueTotalSize,
+                                                   &bytesWritten);
             }
 
             queueTotalSize = queueTotalSize - bytesWritten;
         } else {
             // Step 3.b: Let entry be the first element of this.[[queue]].
             // Step 3.c: Remove entry from this.[[queue]], shifting all other
             //           elements downward (so that the second becomes the
             //           first, and so on).
@@ -4209,55 +4202,16 @@ JS_FRIEND_API JSObject*
 js::UnwrapReadableStream(JSObject* obj)
 {
     if (JSObject* unwrapped = CheckedUnwrap(obj)) {
         return unwrapped->is<ReadableStream>() ? unwrapped : nullptr;
     }
     return nullptr;
 }
 
-extern JS_PUBLIC_API void
-JS::SetReadableStreamCallbacks(JSContext* cx,
-                               JS::RequestReadableStreamDataCallback dataRequestCallback,
-                               JS::WriteIntoReadRequestBufferCallback writeIntoReadRequestCallback,
-                               JS::CancelReadableStreamCallback cancelCallback,
-                               JS::ReadableStreamClosedCallback closedCallback,
-                               JS::ReadableStreamErroredCallback erroredCallback,
-                               JS::ReadableStreamFinalizeCallback finalizeCallback)
-{
-    MOZ_ASSERT(dataRequestCallback);
-    MOZ_ASSERT(writeIntoReadRequestCallback);
-    MOZ_ASSERT(cancelCallback);
-    MOZ_ASSERT(closedCallback);
-    MOZ_ASSERT(erroredCallback);
-    MOZ_ASSERT(finalizeCallback);
-
-    JSRuntime* rt = cx->runtime();
-
-    MOZ_ASSERT(!rt->readableStreamDataRequestCallback);
-    MOZ_ASSERT(!rt->readableStreamWriteIntoReadRequestCallback);
-    MOZ_ASSERT(!rt->readableStreamCancelCallback);
-    MOZ_ASSERT(!rt->readableStreamClosedCallback);
-    MOZ_ASSERT(!rt->readableStreamErroredCallback);
-    MOZ_ASSERT(!rt->readableStreamFinalizeCallback);
-
-    rt->readableStreamDataRequestCallback = dataRequestCallback;
-    rt->readableStreamWriteIntoReadRequestCallback = writeIntoReadRequestCallback;
-    rt->readableStreamCancelCallback = cancelCallback;
-    rt->readableStreamClosedCallback = closedCallback;
-    rt->readableStreamErroredCallback = erroredCallback;
-    rt->readableStreamFinalizeCallback = finalizeCallback;
-}
-
-JS_PUBLIC_API bool
-JS::HasReadableStreamCallbacks(JSContext* cx)
-{
-    return cx->runtime()->readableStreamDataRequestCallback;
-}
-
 JS_PUBLIC_API JSObject*
 JS::NewReadableDefaultStreamObject(JSContext* cx,
                                    JS::HandleObject underlyingSource /* = nullptr */,
                                    JS::HandleFunction size /* = nullptr */,
                                    double highWaterMark /* = 1 */,
                                    JS::HandleObject proto /* = nullptr */)
 {
     MOZ_ASSERT(!cx->zone()->isAtomsZone());
@@ -4275,34 +4229,26 @@ JS::NewReadableDefaultStreamObject(JSCon
     }
     RootedValue sourceVal(cx, ObjectValue(*source));
     RootedValue sizeVal(cx, size ? ObjectValue(*size) : UndefinedValue());
     return CreateReadableStream(cx, sourceVal, highWaterMark, sizeVal);
 }
 
 JS_PUBLIC_API JSObject*
 JS::NewReadableExternalSourceStreamObject(JSContext* cx,
-                                          void* underlyingSource,
+                                          JS::ReadableStreamUnderlyingSource* underlyingSource,
                                           HandleObject proto /* = nullptr */)
 {
     MOZ_ASSERT(!cx->zone()->isAtomsZone());
     AssertHeapIsIdle();
     CHECK_THREAD(cx);
+    MOZ_ASSERT(underlyingSource);
     MOZ_ASSERT((uintptr_t(underlyingSource) & 1) == 0,
                "external underlying source pointers must be aligned");
     cx->check(proto);
-#ifdef DEBUG
-    JSRuntime* rt = cx->runtime();
-    MOZ_ASSERT(rt->readableStreamDataRequestCallback);
-    MOZ_ASSERT(rt->readableStreamWriteIntoReadRequestCallback);
-    MOZ_ASSERT(rt->readableStreamCancelCallback);
-    MOZ_ASSERT(rt->readableStreamClosedCallback);
-    MOZ_ASSERT(rt->readableStreamErroredCallback);
-    MOZ_ASSERT(rt->readableStreamFinalizeCallback);
-#endif // DEBUG
 
     return ReadableStream::createExternalSourceStream(cx, underlyingSource, proto);
 }
 
 JS_PUBLIC_API bool
 JS::IsReadableStream(JSObject* obj)
 {
     return obj->canUnwrapAs<ReadableStream>();
@@ -4405,17 +4351,18 @@ JS::ReadableStreamGetReader(JSContext* c
     }
 
     JSObject* result = CreateReadableStreamDefaultReader(cx, unwrappedStream);
     MOZ_ASSERT_IF(result, IsObjectInContextCompartment(result, cx));
     return result;
 }
 
 JS_PUBLIC_API bool
-JS::ReadableStreamGetExternalUnderlyingSource(JSContext* cx, HandleObject streamObj, void** source)
+JS::ReadableStreamGetExternalUnderlyingSource(JSContext* cx, HandleObject streamObj,
+                                              JS::ReadableStreamUnderlyingSource** source)
 {
     AssertHeapIsIdle();
     CHECK_THREAD(cx);
 
     Rooted<ReadableStream*> unwrappedStream(cx,
         APIUnwrapAndDowncast<ReadableStream>(cx, streamObj));
     if (!unwrappedStream) {
         return false;
@@ -4430,17 +4377,17 @@ JS::ReadableStreamGetExternalUnderlyingS
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                   JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE,
                                   "ReadableStreamGetExternalUnderlyingSource");
         return false;
     }
 
     auto unwrappedController = &unwrappedStream->controller()->as<ReadableByteStreamController>();
     unwrappedController->setSourceLocked();
-    *source = unwrappedController->underlyingSource().toPrivate();
+    *source = unwrappedController->externalSource();
     return true;
 }
 
 JS_PUBLIC_API bool
 JS::ReadableStreamReleaseExternalUnderlyingSource(JSContext* cx, HandleObject streamObj)
 {
     ReadableStream* unwrappedStream = APIUnwrapAndDowncast<ReadableStream>(cx, streamObj);
     if (!unwrappedStream) {
@@ -4528,28 +4475,27 @@ JS::ReadableStreamUpdateDataAvailableFro
         if (!viewObj) {
             return false;
         }
         Rooted<ArrayBufferViewObject*> transferredView(cx, &viewObj->as<ArrayBufferViewObject>());
         if (!transferredView) {
             return false;
         }
 
-        void* underlyingSource = unwrappedController->underlyingSource().toPrivate();
+        JS::ReadableStreamUnderlyingSource* source = unwrappedController->externalSource();
 
         size_t bytesWritten;
         {
             AutoRealm ar(cx, unwrappedStream);
             JS::AutoSuppressGCAnalysis suppressGC(cx);
             JS::AutoCheckCannotGC noGC;
             bool dummy;
             void* buffer = JS_GetArrayBufferViewData(transferredView, &dummy, noGC);
-            auto cb = cx->runtime()->readableStreamWriteIntoReadRequestCallback;
-            MOZ_ASSERT(cb);
-            cb(cx, unwrappedStream, underlyingSource, buffer, availableData, &bytesWritten);
+            source->writeIntoReadRequestBuffer(cx, unwrappedStream, buffer, availableData,
+                                               &bytesWritten);
         }
 
         // Step iii: Perform ! ReadableStreamFulfillReadRequest(stream,
         //                                                      transferredView,
         //                                                      false).
         RootedValue chunk(cx, ObjectValue(*transferredView));
         if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, unwrappedStream, chunk, false)) {
             return false;
--- a/js/src/builtin/Stream.h
+++ b/js/src/builtin/Stream.h
@@ -93,17 +93,18 @@ class ReadableStream : public NativeObje
     Value storedError() const { return getFixedSlot(Slot_StoredError); }
     void setStoredError(HandleValue value) { setFixedSlot(Slot_StoredError, value); }
 
     JS::ReadableStreamMode mode() const;
 
     bool locked() const;
 
     static MOZ_MUST_USE ReadableStream* create(JSContext* cx, HandleObject proto = nullptr);
-    static ReadableStream* createExternalSourceStream(JSContext* cx, void* underlyingSource,
+    static ReadableStream* createExternalSourceStream(JSContext* cx,
+                                                      JS::ReadableStreamUnderlyingSource* source,
                                                       HandleObject proto = nullptr);
 
     static bool constructor(JSContext* cx, unsigned argc, Value* vp);
     static const ClassSpec classSpec_;
     static const Class class_;
     static const ClassSpec protoClassSpec_;
     static const Class protoClass_;
 };
@@ -263,16 +264,28 @@ class ReadableStreamController : public 
     ReadableStream* stream() const {
         return &getFixedSlot(Slot_Stream).toObject().as<ReadableStream>();
     }
     void setStream(ReadableStream* stream) { setFixedSlot(Slot_Stream, ObjectValue(*stream)); }
     Value underlyingSource() const { return getFixedSlot(Slot_UnderlyingSource); }
     void setUnderlyingSource(const Value& underlyingSource) {
         setFixedSlot(Slot_UnderlyingSource, underlyingSource);
     }
+    JS::ReadableStreamUnderlyingSource* externalSource() const {
+        static_assert(alignof(JS::ReadableStreamUnderlyingSource) >= 2,
+                      "External underling sources are stored as PrivateValues, "
+                      "so they must have even addresses");
+        MOZ_ASSERT(hasExternalSource());
+        return static_cast<JS::ReadableStreamUnderlyingSource*>(underlyingSource().toPrivate());
+    }
+    void setExternalSource(JS::ReadableStreamUnderlyingSource* underlyingSource) {
+        MOZ_ASSERT(getFixedSlot(Slot_Flags).isUndefined());
+        setUnderlyingSource(JS::PrivateValue(underlyingSource));
+        setFlags(Flag_ExternalSource);
+    }
     double strategyHWM() const { return getFixedSlot(Slot_StrategyHWM).toNumber(); }
     void setStrategyHWM(double highWaterMark) {
         setFixedSlot(Slot_StrategyHWM, NumberValue(highWaterMark));
     }
     uint32_t flags() const { return getFixedSlot(Slot_Flags).toInt32(); }
     void setFlags(uint32_t flags) { setFixedSlot(Slot_Flags, Int32Value(flags)); }
     void addFlags(uint32_t flags) { setFlags(this->flags() | flags); }
     void removeFlags(uint32_t flags) { setFlags(this->flags() & ~flags); }
@@ -325,18 +338,19 @@ class ReadableStreamDefaultController : 
 
 class ReadableByteStreamController : public ReadableStreamController
 {
   public:
     /**
      * Memory layout for ReadableByteStreamControllers, starting after the
      * slots shared among all types of controllers.
      *
-     * PendingPullIntos is guaranteed to be in the  same compartment as the
-     * controller, but might contain wrappers for objects from other compartments.
+     * PendingPullIntos is guaranteed to be in the same compartment as the
+     * controller, but might contain wrappers for objects from other
+     * compartments.
      *
      * AutoAllocateSize is a primitive (numeric) value.
      */
     enum Slots {
         Slot_BYOBRequest = ReadableStreamController::SlotCount,
         Slot_PendingPullIntos,
         Slot_AutoAllocateSize,
         SlotCount
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/cacheir/bug1509293.js
@@ -0,0 +1,2 @@
+var summary = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
+Array.prototype.push([...summary]);
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -35,59 +35,95 @@
 #include "vm/JSScript-inl.h"
 #include "vm/NativeObject-inl.h"
 #include "vm/TypeInference-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
 using mozilla::AssertedCast;
-
-BaselineCompiler::BaselineCompiler(JSContext* cx, TempAllocator& alloc, JSScript* script)
-  : cx(cx),
+using mozilla::Maybe;
+
+namespace js {
+namespace jit {
+
+BaselineCompilerHandler::BaselineCompilerHandler(TempAllocator& alloc, JSScript* script)
+  : alloc_(alloc),
+    script_(script),
+    compileDebugInstrumentation_(script->isDebuggee())
+{
+}
+
+BaselineInterpreterHandler::BaselineInterpreterHandler()
+{
+}
+
+template <typename Handler>
+template <typename... HandlerArgs>
+BaselineCodeGen<Handler>::BaselineCodeGen(JSContext* cx, TempAllocator& alloc, JSScript* script,
+                                          HandlerArgs&&... args)
+  : handler(std::forward<HandlerArgs>(args)...),
+    cx(cx),
     script(script),
     pc(script->code()),
     ionCompileable_(jit::IsIonEnabled(cx) && CanIonCompileScript(cx, script)),
-    compileDebugInstrumentation_(script->isDebuggee()),
     alloc_(alloc),
     analysis_(alloc, script),
     frame(script, masm),
-    pcMappingEntries_(),
+    traceLoggerToggleOffsets_(cx),
     icEntryIndex_(0),
     pushedBeforeCall_(0),
 #ifdef DEBUG
     inCall_(false),
 #endif
+    modifiesArguments_(false)
+{
+}
+
+BaselineCompiler::BaselineCompiler(JSContext* cx, TempAllocator& alloc, JSScript* script)
+  : BaselineCodeGen(cx, alloc, script,
+                    /* HandlerArgs = */ alloc, script),
+    pcMappingEntries_(),
     profilerPushToggleOffset_(),
     profilerEnterFrameToggleOffset_(),
     profilerExitFrameToggleOffset_(),
-    traceLoggerToggleOffsets_(cx),
-    traceLoggerScriptTextIdOffset_(),
-    modifiesArguments_(false)
+    traceLoggerScriptTextIdOffset_()
 {
 #ifdef JS_CODEGEN_NONE
     MOZ_CRASH();
 #endif
 }
 
 bool
+BaselineCompilerHandler::init()
+{
+    uint32_t len = script_->length();
+
+    if (!labels_.init(alloc_, len)) {
+        return false;
+    }
+
+    for (size_t i = 0; i < len; i++) {
+        new (&labels_[i]) Label();
+    }
+
+    return true;
+}
+
+bool
 BaselineCompiler::init()
 {
     if (!analysis_.init(alloc_, cx->caches().gsnCache)) {
         return false;
     }
 
-    if (!labels_.init(alloc_, script->length())) {
+    if (!handler.init()) {
         return false;
     }
 
-    for (size_t i = 0; i < script->length(); i++) {
-        new (&labels_[i]) Label();
-    }
-
     if (!frame.init(alloc_)) {
         return false;
     }
 
     return true;
 }
 
 bool
@@ -296,17 +332,17 @@ BaselineCompiler::compile()
 
     // The last entry in the last index found, and is used to avoid binary
     // searches for the sought entry when queries are in linear order.
     bytecodeMap[script->nTypeSets()] = 0;
 
     // Compute yield/await native resume addresses.
     baselineScript->computeResumeNativeOffsets(script);
 
-    if (compileDebugInstrumentation_) {
+    if (compileDebugInstrumentation()) {
         baselineScript->setHasDebugInstrumentation();
     }
 
     // Always register a native => bytecode mapping entry, since profiler can be
     // turned on with baseline jitcode on stack, and baseline jitcode cannot be invalidated.
     {
         JitSpew(JitSpew_Profiling, "Added JitcodeGlobalEntry for baseline script %s:%u:%u (%p)",
                     script->filename(), script->lineno(), script->column(), baselineScript.get());
@@ -560,18 +596,19 @@ BaselineCompiler::emitOutOfLinePostBarri
     masm.passABIArg(objReg);
     masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, PostWriteBarrier));
 
     masm.popValue(R0);
     masm.ret();
     return true;
 }
 
-bool
-BaselineCompiler::emitNextIC()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emitNextIC()
 {
     // Emit a call to an IC stored in ICScript. Calls to this must match the
     // ICEntry order in ICScript: first the non-op IC entries for |this| and
     // formal arguments, then the for-op IC entries for JOF_IC ops.
 
     uint32_t pcOffset = script->pcToOffset(pc);
 
     // We don't use every ICEntry and we can skip unreachable ops, so we have
@@ -594,33 +631,35 @@ BaselineCompiler::emitNextIC()
     if (!retAddrEntries_.emplaceBack(script->pcToOffset(pc), kind, callOffset)) {
         ReportOutOfMemory(cx);
         return false;
     }
 
     return true;
 }
 
+template <typename Handler>
 void
-BaselineCompiler::prepareVMCall()
+BaselineCodeGen<Handler>::prepareVMCall()
 {
     pushedBeforeCall_ = masm.framePushed();
 #ifdef DEBUG
     inCall_ = true;
 #endif
 
     // Ensure everything is synced.
     frame.syncStack(0);
 
     // Save the frame pointer.
     masm.Push(BaselineFrameReg);
 }
 
-bool
-BaselineCompiler::callVM(const VMFunction& fun, CallVMPhase phase)
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::callVM(const VMFunction& fun, CallVMPhase phase)
 {
     TrampolinePtr code = cx->runtime()->jitRuntime()->getVMWrapper(fun);
 
 #ifdef DEBUG
     // Assert prepareVMCall() has been called.
     MOZ_ASSERT(inCall_);
     inCall_ = false;
 
@@ -741,34 +780,34 @@ BaselineCompiler::emitStackCheck()
 
     masm.bind(&skipCall);
     return true;
 }
 
 void
 BaselineCompiler::emitIsDebuggeeCheck()
 {
-    if (compileDebugInstrumentation_) {
+    if (compileDebugInstrumentation()) {
         masm.Push(BaselineFrameReg);
         masm.setupUnalignedABICall(R0.scratchReg());
         masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
         masm.passABIArg(R0.scratchReg());
         masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, jit::FrameIsDebuggeeCheck));
         masm.Pop(BaselineFrameReg);
     }
 }
 
 typedef bool (*DebugPrologueFn)(JSContext*, BaselineFrame*, jsbytecode*, bool*);
 static const VMFunction DebugPrologueInfo =
     FunctionInfo<DebugPrologueFn>(jit::DebugPrologue, "DebugPrologue");
 
 bool
 BaselineCompiler::emitDebugPrologue()
 {
-    if (compileDebugInstrumentation_) {
+    if (compileDebugInstrumentation()) {
         // Load pointer to BaselineFrame in R0.
         masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
 
         prepareVMCall();
         pushArg(ImmPtr(pc));
         pushArg(R0.scratchReg());
         if (!callVM(DebugPrologueInfo)) {
             return false;
@@ -853,18 +892,19 @@ BaselineCompiler::initEnvironmentChain()
 
     return true;
 }
 
 typedef bool (*InterruptCheckFn)(JSContext*);
 static const VMFunction InterruptCheckInfo =
     FunctionInfo<InterruptCheckFn>(InterruptCheck, "InterruptCheck");
 
-bool
-BaselineCompiler::emitInterruptCheck()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emitInterruptCheck()
 {
     frame.syncStack(0);
 
     Label done;
     masm.branch32(Assembler::Equal,
                   AbsoluteAddress(cx->addressOfInterruptBits()), Imm32(0),
                   &done);
 
@@ -877,18 +917,19 @@ BaselineCompiler::emitInterruptCheck()
     return true;
 }
 
 typedef bool (*IonCompileScriptForBaselineFn)(JSContext*, BaselineFrame*, jsbytecode*);
 static const VMFunction IonCompileScriptForBaselineInfo =
     FunctionInfo<IonCompileScriptForBaselineFn>(IonCompileScriptForBaseline,
                                                 "IonCompileScriptForBaseline");
 
-bool
-BaselineCompiler::emitWarmUpCounterIncrement(bool allowOsr)
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emitWarmUpCounterIncrement(bool allowOsr)
 {
     // Emit no warm-up counter increments or bailouts if Ion is not
     // enabled, or if the script will never be Ion-compileable
 
     if (!ionCompileable_) {
         return true;
     }
 
@@ -978,17 +1019,17 @@ BaselineCompiler::emitArgumentTypeChecks
     }
 
     return true;
 }
 
 bool
 BaselineCompiler::emitDebugTrap()
 {
-    MOZ_ASSERT(compileDebugInstrumentation_);
+    MOZ_ASSERT(compileDebugInstrumentation());
     MOZ_ASSERT(frame.numUnsyncedSlots() == 0);
 
     bool enabled = script->stepModeEnabled() || script->hasBreakpointsAt(pc);
 
 #if defined(JS_CODEGEN_ARM64)
     // Flush any pending constant pools to prevent incorrect
     // PCMappingEntry offsets. See Bug 1446819.
     masm.flush();
@@ -1068,18 +1109,20 @@ BaselineCompiler::emitTraceLoggerExit()
 
     masm.Pop(loggerReg);
 
     masm.bind(&noTraceLogger);
 
     return true;
 }
 
-bool
-BaselineCompiler::emitTraceLoggerResume(Register baselineScript, AllocatableGeneralRegisterSet& regs)
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emitTraceLoggerResume(Register baselineScript,
+                                                 AllocatableGeneralRegisterSet& regs)
 {
     Register scriptId = regs.takeAny();
     Register loggerReg = regs.takeAny();
 
     Label noTraceLogger;
     if (!traceLoggerToggleOffsets_.append(masm.toggledJump(&noTraceLogger))) {
         return false;
     }
@@ -1158,18 +1201,18 @@ BaselineCompiler::emitBody()
             prevpc = pc;
             continue;
         }
 
         if (info->jumpTarget) {
             // Fully sync the stack if there are incoming jumps.
             frame.syncStack(0);
             frame.setStackDepth(info->stackDepth);
-            masm.bind(labelOf(pc));
-        } else if (MOZ_UNLIKELY(compileDebugInstrumentation_)) {
+            masm.bind(handler.labelOf(pc));
+        } else if (MOZ_UNLIKELY(compileDebugInstrumentation())) {
             // Also fully sync the stack if the debugger is enabled.
             frame.syncStack(0);
         } else {
             // At the beginning of any op, at most the top 2 stack-values are unsynced.
             if (frame.stackDepth() > 2) {
                 frame.syncStack(2);
             }
         }
@@ -1185,17 +1228,17 @@ BaselineCompiler::emitBody()
             emittedOps = 0;
         }
         if (MOZ_UNLIKELY(!addPCMappingEntry(addIndexEntry))) {
             ReportOutOfMemory(cx);
             return Method_Error;
         }
 
         // Emit traps for breakpoints and step mode.
-        if (MOZ_UNLIKELY(compileDebugInstrumentation_) && !emitDebugTrap()) {
+        if (MOZ_UNLIKELY(compileDebugInstrumentation()) && !emitDebugTrap()) {
             return Method_Error;
         }
 
         switch (op) {
           // ===== NOT Yet Implemented =====
           case JSOP_FORCEINTERPRETER:
             // Intentionally not implemented.
           case JSOP_SETINTRINSIC:
@@ -1238,116 +1281,128 @@ OPCODE_LIST(EMIT_OP)
         prevpc = pc;
 #endif
     }
 
     MOZ_ASSERT(JSOp(*prevpc) == JSOP_RETRVAL);
     return Method_Compiled;
 }
 
-bool
-BaselineCompiler::emit_JSOP_NOP()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_NOP()
 {
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_ITERNEXT()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_ITERNEXT()
 {
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_NOP_DESTRUCTURING()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_NOP_DESTRUCTURING()
 {
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_TRY_DESTRUCTURING_ITERCLOSE()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_TRY_DESTRUCTURING_ITERCLOSE()
 {
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_LABEL()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_LABEL()
 {
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_POP()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_POP()
 {
     frame.pop();
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_POPN()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_POPN()
 {
     frame.popn(GET_UINT16(pc));
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_DUPAT()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_DUPAT()
 {
     frame.syncStack(0);
 
     // DUPAT takes a value on the stack and re-pushes it on top.  It's like
     // GETLOCAL but it addresses from the top of the stack instead of from the
     // stack frame.
 
     int depth = -(GET_UINT24(pc) + 1);
     masm.loadValue(frame.addressOfStackValue(frame.peek(depth)), R0);
     frame.push(R0);
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_DUP()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_DUP()
 {
     // Keep top stack value in R0, sync the rest so that we can use R1. We use
     // separate registers because every register can be used by at most one
     // StackValue.
     frame.popRegsAndSync(1);
     masm.moveValue(R0, R1);
 
     // inc/dec ops use DUP followed by ONE, ADD. Push R0 last to avoid a move.
     frame.push(R1);
     frame.push(R0);
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_DUP2()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_DUP2()
 {
     frame.syncStack(0);
 
     masm.loadValue(frame.addressOfStackValue(frame.peek(-2)), R0);
     masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R1);
 
     frame.push(R0);
     frame.push(R1);
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_SWAP()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_SWAP()
 {
     // Keep top stack values in R0 and R1.
     frame.popRegsAndSync(2);
 
     frame.push(R1);
     frame.push(R0);
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_PICK()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_PICK()
 {
     frame.syncStack(0);
 
     // Pick takes a value on the stack and moves it to the top.
     // For instance, pick 2:
     //     before: A B C D E
     //     after : A B D E C
 
@@ -1365,18 +1420,19 @@ BaselineCompiler::emit_JSOP_PICK()
     }
 
     // Push R0.
     frame.pop();
     frame.push(R0);
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_UNPICK()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_UNPICK()
 {
     frame.syncStack(0);
 
     // Pick takes the top of the stack value and moves it under the nth value.
     // For instance, unpick 2:
     //     before: A B C D E
     //     after : A B E C D
 
@@ -1393,119 +1449,167 @@ BaselineCompiler::emit_JSOP_UNPICK()
     }
 
     // Store R0 under the nth value.
     Address dest = frame.addressOfStackValue(frame.peek(depth));
     masm.storeValue(R0, dest);
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_GOTO()
-{
-    frame.syncStack(0);
+template <>
+void
+BaselineCompilerCodeGen::emitJump()
+{
+    MOZ_ASSERT(IsJumpOpcode(JSOp(*pc)));
+    frame.assertSyncedStack();
 
     jsbytecode* target = pc + GET_JUMP_OFFSET(pc);
-    masm.jump(labelOf(target));
+    masm.jump(handler.labelOf(target));
+}
+
+template <>
+void
+BaselineInterpreterCodeGen::emitJump()
+{
+    // We have to add the current pc's jump offset to the frame's pc.
+    MOZ_CRASH("NYI: interpreter emitJump");
+}
+
+template <>
+void
+BaselineCompilerCodeGen::emitTestBooleanTruthy(bool branchIfTrue, ValueOperand val)
+{
+    MOZ_ASSERT(IsJumpOpcode(JSOp(*pc)));
+    frame.assertSyncedStack();
+
+    jsbytecode* target = pc + GET_JUMP_OFFSET(pc);
+    masm.branchTestBooleanTruthy(branchIfTrue, val, handler.labelOf(target));
+}
+
+template <>
+void
+BaselineInterpreterCodeGen::emitTestBooleanTruthy(bool branchIfTrue, ValueOperand val)
+{
+    Label done;
+    masm.branchTestBooleanTruthy(!branchIfTrue, val, &done);
+    emitJump();
+    masm.bind(&done);
+}
+
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_GOTO()
+{
+    frame.syncStack(0);
+    emitJump();
     return true;
 }
 
-bool
-BaselineCompiler::emitToBoolean()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emitToBoolean()
 {
     Label skipIC;
     masm.branchTestBoolean(Assembler::Equal, R0, &skipIC);
 
     // Call IC
     if (!emitNextIC()) {
         return false;
     }
 
     masm.bind(&skipIC);
     return true;
 }
 
-bool
-BaselineCompiler::emitTest(bool branchIfTrue)
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emitTest(bool branchIfTrue)
 {
     bool knownBoolean = frame.peek(-1)->isKnownBoolean();
 
     // Keep top stack value in R0.
     frame.popRegsAndSync(1);
 
     if (!knownBoolean && !emitToBoolean()) {
         return false;
     }
 
     // IC will leave a BooleanValue in R0, just need to branch on it.
-    masm.branchTestBooleanTruthy(branchIfTrue, R0, labelOf(pc + GET_JUMP_OFFSET(pc)));
+    emitTestBooleanTruthy(branchIfTrue, R0);
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_IFEQ()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_IFEQ()
 {
     return emitTest(false);
 }
 
-bool
-BaselineCompiler::emit_JSOP_IFNE()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_IFNE()
 {
     return emitTest(true);
 }
 
-bool
-BaselineCompiler::emitAndOr(bool branchIfTrue)
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emitAndOr(bool branchIfTrue)
 {
     bool knownBoolean = frame.peek(-1)->isKnownBoolean();
 
     // AND and OR leave the original value on the stack.
     frame.syncStack(0);
 
     masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0);
     if (!knownBoolean && !emitToBoolean()) {
         return false;
     }
 
-    masm.branchTestBooleanTruthy(branchIfTrue, R0, labelOf(pc + GET_JUMP_OFFSET(pc)));
+    emitTestBooleanTruthy(branchIfTrue, R0);
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_AND()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_AND()
 {
     return emitAndOr(false);
 }
 
-bool
-BaselineCompiler::emit_JSOP_OR()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_OR()
 {
     return emitAndOr(true);
 }
 
-bool
-BaselineCompiler::emit_JSOP_NOT()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_NOT()
 {
     bool knownBoolean = frame.peek(-1)->isKnownBoolean();
 
     // Keep top stack value in R0.
     frame.popRegsAndSync(1);
 
     if (!knownBoolean && !emitToBoolean()) {
         return false;
     }
 
     masm.notBoolean(R0);
 
     frame.push(R0, JSVAL_TYPE_BOOLEAN);
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_POS()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_POS()
 {
     // Keep top stack value in R0.
     frame.popRegsAndSync(1);
 
     // Inline path for int32 and double.
     Label done;
     masm.branchTestNumber(Assembler::Equal, R0, &done);
 
@@ -1514,77 +1618,84 @@ BaselineCompiler::emit_JSOP_POS()
         return false;
     }
 
     masm.bind(&done);
     frame.push(R0);
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_LOOPHEAD()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_LOOPHEAD()
 {
     if (!emit_JSOP_JUMPTARGET()) {
         return false;
     }
     return emitInterruptCheck();
 }
 
-bool
-BaselineCompiler::emit_JSOP_LOOPENTRY()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_LOOPENTRY()
 {
     if (!emit_JSOP_JUMPTARGET()) {
         return false;
     }
     frame.syncStack(0);
     if (!emitWarmUpCounterIncrement(LoopEntryCanIonOsr(pc))) {
         return false;
     }
     if (script->trackRecordReplayProgress()) {
         masm.inc64(AbsoluteAddress(mozilla::recordreplay::ExecutionProgressCounter()));
     }
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_VOID()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_VOID()
 {
     frame.pop();
     frame.push(UndefinedValue());
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_UNDEFINED()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_UNDEFINED()
 {
     // If this ever changes, change what JSOP_GIMPLICITTHIS does too.
     frame.push(UndefinedValue());
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_HOLE()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_HOLE()
 {
     frame.push(MagicValue(JS_ELEMENTS_HOLE));
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_NULL()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_NULL()
 {
     frame.push(NullValue());
     return true;
 }
 
 typedef bool (*ThrowCheckIsObjectFn)(JSContext*, CheckIsObjectKind);
 static const VMFunction ThrowCheckIsObjectInfo =
     FunctionInfo<ThrowCheckIsObjectFn>(ThrowCheckIsObject, "ThrowCheckIsObject");
 
-bool
-BaselineCompiler::emit_JSOP_CHECKISOBJ()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_CHECKISOBJ()
 {
     frame.syncStack(0);
     masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0);
 
     Label ok;
     masm.branchTestObject(Assembler::Equal, R0, &ok);
 
     prepareVMCall();
@@ -1597,18 +1708,19 @@ BaselineCompiler::emit_JSOP_CHECKISOBJ()
     masm.bind(&ok);
     return true;
 }
 
 typedef bool (*CheckIsCallableFn)(JSContext*, HandleValue, CheckIsCallableKind);
 static const VMFunction CheckIsCallableInfo =
     FunctionInfo<CheckIsCallableFn>(CheckIsCallable, "CheckIsCallable");
 
-bool
-BaselineCompiler::emit_JSOP_CHECKISCALLABLE()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_CHECKISCALLABLE()
 {
     frame.syncStack(0);
     masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0);
 
     prepareVMCall();
 
     pushArg(Imm32(GET_UINT8(pc)));
     pushArg(R0);
@@ -1624,36 +1736,39 @@ static const VMFunction ThrowUninitializ
     FunctionInfo<ThrowUninitializedThisFn>(BaselineThrowUninitializedThis,
                                            "BaselineThrowUninitializedThis");
 
 typedef bool (*ThrowInitializedThisFn)(JSContext*);
 static const VMFunction ThrowInitializedThisInfo =
     FunctionInfo<ThrowInitializedThisFn>(BaselineThrowInitializedThis,
                                          "BaselineThrowInitializedThis");
 
-bool
-BaselineCompiler::emit_JSOP_CHECKTHIS()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_CHECKTHIS()
 {
     frame.syncStack(0);
     masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0);
 
     return emitCheckThis(R0);
 }
 
-bool
-BaselineCompiler::emit_JSOP_CHECKTHISREINIT()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_CHECKTHISREINIT()
 {
     frame.syncStack(0);
     masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0);
 
     return emitCheckThis(R0, /* reinit = */true);
 }
 
-bool
-BaselineCompiler::emitCheckThis(ValueOperand val, bool reinit)
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emitCheckThis(ValueOperand val, bool reinit)
 {
     Label thisOK;
     if (reinit) {
         masm.branchTestMagic(Assembler::Equal, val, &thisOK);
     } else {
         masm.branchTestMagic(Assembler::NotEqual, val, &thisOK);
     }
 
@@ -1675,18 +1790,19 @@ BaselineCompiler::emitCheckThis(ValueOpe
     masm.bind(&thisOK);
     return true;
 }
 
 typedef bool (*ThrowBadDerivedReturnFn)(JSContext*, HandleValue);
 static const VMFunction ThrowBadDerivedReturnInfo =
     FunctionInfo<ThrowBadDerivedReturnFn>(jit::ThrowBadDerivedReturn, "ThrowBadDerivedReturn");
 
-bool
-BaselineCompiler::emit_JSOP_CHECKRETURN()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_CHECKRETURN()
 {
     MOZ_ASSERT(script->isDerivedClassConstructor());
 
     // Load |this| in R0, return value in R1.
     frame.popRegsAndSync(1);
     emitLoadReturnValue(R1);
 
     Label done, returnOK;
@@ -1713,18 +1829,19 @@ BaselineCompiler::emit_JSOP_CHECKRETURN(
     masm.bind(&done);
     return true;
 }
 
 typedef bool (*GetFunctionThisFn)(JSContext*, BaselineFrame*, MutableHandleValue);
 static const VMFunction GetFunctionThisInfo =
     FunctionInfo<GetFunctionThisFn>(jit::BaselineGetFunctionThis, "BaselineGetFunctionThis");
 
-bool
-BaselineCompiler::emit_JSOP_FUNCTIONTHIS()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_FUNCTIONTHIS()
 {
     MOZ_ASSERT(function());
     MOZ_ASSERT(!function()->isArrow());
 
     frame.pushThis();
 
     // In strict mode code or self-hosted functions, |this| is left alone.
     if (script->strict() || (function() && function()->isSelfHostedBuiltin())) {
@@ -1750,18 +1867,19 @@ BaselineCompiler::emit_JSOP_FUNCTIONTHIS
     return true;
 }
 
 typedef void (*GetNonSyntacticGlobalThisFn)(JSContext*, HandleObject, MutableHandleValue);
 static const VMFunction GetNonSyntacticGlobalThisInfo =
     FunctionInfo<GetNonSyntacticGlobalThisFn>(js::GetNonSyntacticGlobalThis,
                                               "GetNonSyntacticGlobalThis");
 
-bool
-BaselineCompiler::emit_JSOP_GLOBALTHIS()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_GLOBALTHIS()
 {
     frame.syncStack(0);
 
     if (!script->hasNonSyntacticScope()) {
         LexicalEnvironmentObject* globalLexical = &script->global().lexicalEnvironment();
         masm.moveValue(globalLexical->thisValue(), R0);
         frame.push(R0);
         return true;
@@ -1775,116 +1893,130 @@ BaselineCompiler::emit_JSOP_GLOBALTHIS()
     if (!callVM(GetNonSyntacticGlobalThisInfo)) {
         return false;
     }
 
     frame.push(R0);
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_TRUE()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_TRUE()
 {
     frame.push(BooleanValue(true));
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_FALSE()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_FALSE()
 {
     frame.push(BooleanValue(false));
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_ZERO()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_ZERO()
 {
     frame.push(Int32Value(0));
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_ONE()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_ONE()
 {
     frame.push(Int32Value(1));
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_INT8()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_INT8()
 {
     frame.push(Int32Value(GET_INT8(pc)));
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_INT32()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_INT32()
 {
     frame.push(Int32Value(GET_INT32(pc)));
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_UINT16()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_UINT16()
 {
     frame.push(Int32Value(GET_UINT16(pc)));
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_UINT24()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_UINT24()
 {
     frame.push(Int32Value(GET_UINT24(pc)));
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_RESUMEINDEX()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_RESUMEINDEX()
 {
     return emit_JSOP_UINT24();
 }
 
-bool
-BaselineCompiler::emit_JSOP_DOUBLE()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_DOUBLE()
 {
     frame.push(script->getConst(GET_UINT32_INDEX(pc)));
     return true;
 }
 
 #ifdef ENABLE_BIGINT
-bool
-BaselineCompiler::emit_JSOP_BIGINT()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_BIGINT()
 {
     frame.push(script->getConst(GET_UINT32_INDEX(pc)));
     return true;
 }
 #endif
 
-bool
-BaselineCompiler::emit_JSOP_STRING()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_STRING()
 {
     frame.push(StringValue(script->getAtom(pc)));
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_SYMBOL()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_SYMBOL()
 {
     unsigned which = GET_UINT8(pc);
     JS::Symbol* sym = cx->runtime()->wellKnownSymbols->get(which);
     frame.push(SymbolValue(sym));
     return true;
 }
 
 typedef JSObject* (*DeepCloneObjectLiteralFn)(JSContext*, HandleObject, NewObjectKind);
 static const VMFunction DeepCloneObjectLiteralInfo =
     FunctionInfo<DeepCloneObjectLiteralFn>(DeepCloneObjectLiteral, "DeepCloneObjectLiteral");
 
-bool
-BaselineCompiler::emit_JSOP_OBJECT()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_OBJECT()
 {
     if (cx->realm()->creationOptions().cloneSingletons()) {
         RootedObject obj(cx, script->getObject(GET_UINT32_INDEX(pc)));
         if (!obj) {
             return false;
         }
 
         prepareVMCall();
@@ -1902,18 +2034,19 @@ BaselineCompiler::emit_JSOP_OBJECT()
         return true;
     }
 
     cx->realm()->behaviors().setSingletonsAsValues();
     frame.push(ObjectValue(*script->getObject(pc)));
     return true;
 }
 
-bool
-BaselineCompiler::emit_JSOP_CALLSITEOBJ()
+template <typename Handler>
+bool
+BaselineCodeGen<Handler>::emit_JSOP_CALLSITEOBJ()
 {
     RootedObject cso(cx, script->getObject(pc));
     RootedObject raw(cx, script->getObject(GET_UINT32_INDEX(pc) + 1));
     if (!cso || !raw) {
         return false;
     }