Merge mozilla-central to inbound. a=merge CLOSED TREE
authorBrindusan Cristian <cbrindusan@mozilla.com>
Thu, 25 Apr 2019 01:46:24 +0300
changeset 530085 769c9790263d7cb6b1a350fd60967b88cff8f0f4
parent 530084 9d57a72036d3ce614762bdb48bdcd1311bc72f12 (current diff)
parent 529993 c7a9affeb6046ef5688e024360da6dd2dc5da358 (diff)
child 530086 b617fdd890779198b7e7ea269e1744c6de561904
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to inbound. a=merge CLOSED TREE
Cargo.lock
browser/config/tooltool-manifests/win32/vs2015.manifest
browser/config/tooltool-manifests/win64/vs2015.manifest
browser/extensions/fxmonitor/privileged/FirefoxMonitor.jsm
browser/extensions/fxmonitor/privileged/subscripts/Globals.jsm
dom/xbl/nsXBLPrototypeResources.cpp
dom/xbl/nsXBLPrototypeResources.h
dom/xbl/nsXBLResourceLoader.cpp
dom/xbl/nsXBLResourceLoader.h
js/src/vm/Stopwatch.h
layout/reftests/bugs/1375513-ref.html
layout/reftests/bugs/1375513.css
layout/reftests/bugs/1375513.html
layout/reftests/bugs/1375513.xml
layout/reftests/xul/root-binding-style-xbl.css
layout/reftests/xul/root-binding-style.xul
layout/reftests/xul/xbl_bindings.xml
layout/style/test/media_queries_dynamic_xbl_binding.xml
layout/style/test/media_queries_dynamic_xbl_iframe.html
layout/style/test/media_queries_dynamic_xbl_style.css
layout/style/test/test_media_queries_dynamic_xbl.html
testing/web-platform/meta/fetch/stale-while-revalidate/stale-image.tentative.html.ini
testing/web-platform/meta/fetch/stale-while-revalidate/stale-script.tentative.html.ini
testing/web-platform/meta/webaudio/the-audio-api/the-mediaelementaudiosourcenode-interface/mediaElementAudioSourceToScriptProcessorTest.html.ini
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -1078,83 +1078,86 @@ toolbarpaletteitem[place="palette"] > #d
   -moz-appearance: none;
   -moz-binding: url("chrome://browser/content/places/menu.xml#places-popup-arrow");
   background: transparent;
   border: none;
   /* The popup inherits -moz-image-region from the button, must reset it */
   -moz-image-region: auto;
 }
 
+@supports -moz-bool-pref("xul.panel-animations.enabled") {
 %ifdef MOZ_WIDGET_COCOA
+  /* On Mac, use the properties "-moz-window-transform" and "-moz-window-opacity"
+     instead of "transform" and "opacity" for these animations.
+     The -moz-window* properties apply to the whole window including the window's
+     shadow, and they don't affect the window's "shape", so the system doesn't
+     have to recompute the shadow shape during the animation. This makes them a
+     lot faster. In fact, Gecko no longer triggers shadow shape recomputations
+     for repaints.
+     These properties are not implemented on other platforms. */
+  #BMB_bookmarksPopup:not([animate="false"]) {
+    -moz-window-opacity: 0;
+    -moz-window-transform: translateY(-70px);
+    transition-property: -moz-window-transform, -moz-window-opacity;
+    transition-duration: 0.18s, 0.18s;
+    transition-timing-function:
+      var(--animation-easing-function), ease-out;
+  }
 
-/* On Mac, use the properties "-moz-window-transform" and "-moz-window-opacity"
-   instead of "transform" and "opacity" for these animations.
-   The -moz-window* properties apply to the whole window including the window's
-   shadow, and they don't affect the window's "shape", so the system doesn't
-   have to recompute the shadow shape during the animation. This makes them a
-   lot faster. In fact, Gecko no longer triggers shadow shape recomputations
-   for repaints.
-   These properties are not implemented on other platforms. */
-#BMB_bookmarksPopup:not([animate="false"]) {
-  -moz-window-opacity: 0;
-  -moz-window-transform: translateY(-70px);
-  transition-property: -moz-window-transform, -moz-window-opacity;
-  transition-duration: 0.18s, 0.18s;
-  transition-timing-function:
-    var(--animation-easing-function), ease-out;
-}
+  #BMB_bookmarksPopup[side="bottom"]:not([animate="false"]) {
+    -moz-window-transform: translateY(70px);
+  }
 
-#BMB_bookmarksPopup[side="bottom"]:not([animate="false"]) {
-  -moz-window-transform: translateY(70px);
-}
-
-#BMB_bookmarksPopup[side][animate="open"] {
-  -moz-window-opacity: 1.0;
-  transition-duration: 0.18s, 0.18s;
-  -moz-window-transform: none;
-  transition-timing-function:
-    var(--animation-easing-function), ease-in-out;
-}
+  /* [animate] is here only so that this rule has greater specificity than the
+   * rule right above */
+  #BMB_bookmarksPopup[animate][animate="open"] {
+    -moz-window-opacity: 1.0;
+    transition-duration: 0.18s, 0.18s;
+    -moz-window-transform: none;
+    transition-timing-function:
+      var(--animation-easing-function), ease-in-out;
+  }
 
-#BMB_bookmarksPopup[animate="cancel"] {
-  -moz-window-transform: none;
-}
+  #BMB_bookmarksPopup[animate][animate="cancel"] {
+    -moz-window-transform: none;
+  }
+%else
+  #BMB_bookmarksPopup {
+    will-change: transform, opacity; /* workaround for bug 1414033 */
+  }
 
-%elifndef MOZ_WIDGET_GTK
+  #BMB_bookmarksPopup:not([animate="false"]) {
+    opacity: 0;
+    transform: translateY(-70px);
+    transition-property: transform, opacity;
+    transition-duration: 0.18s, 0.18s;
+    transition-timing-function:
+      var(--animation-easing-function), ease-out;
+  }
 
-#BMB_bookmarksPopup {
-  will-change: transform, opacity; /* workaround for bug 1414033 */
-}
+  #BMB_bookmarksPopup[side="bottom"]:not([animate="false"]) {
+    transform: translateY(70px);
+  }
 
-#BMB_bookmarksPopup:not([animate="false"]) {
-  opacity: 0;
-  transform: translateY(-70px);
-  transition-property: transform, opacity;
-  transition-duration: 0.18s, 0.18s;
-  transition-timing-function:
-    var(--animation-easing-function), ease-out;
+  /* [animate] is here only so that this rule has greater specificity than the
+   * rule right above */
+  #BMB_bookmarksPopup[animate][animate="open"] {
+    opacity: 1.0;
+    transition-duration: 0.18s, 0.18s;
+    transform: none;
+    transition-timing-function:
+      var(--animation-easing-function), ease-in-out;
+  }
+
+  #BMB_bookmarksPopup[animate][animate="cancel"] {
+    transform: none;
+  }
+%endif
 }
 
-#BMB_bookmarksPopup[side="bottom"]:not([animate="false"]) {
-  transform: translateY(70px);
-}
-
-#BMB_bookmarksPopup[side][animate="open"] {
-  opacity: 1.0;
-  transition-duration: 0.18s, 0.18s;
-  transform: none;
-  transition-timing-function:
-    var(--animation-easing-function), ease-in-out;
-}
-
-#BMB_bookmarksPopup[animate="cancel"] {
-  transform: none;
-}
-%endif
-
 /* Apply crisp rendering for favicons at exactly 2dppx resolution */
 @media (resolution: 2dppx) {
   #PanelUI-remotetabs-tabslist > toolbarbutton > .toolbarbutton-icon,
   #PanelUI-recentlyClosedWindows > toolbarbutton > .toolbarbutton-icon,
   #PanelUI-recentlyClosedTabs > toolbarbutton > .toolbarbutton-icon,
   #PanelUI-historyItems > toolbarbutton > .toolbarbutton-icon {
     image-rendering: -moz-crisp-edges;
   }
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -5946,16 +5946,22 @@ function onViewToolbarsPopupShowing(aEve
     let multipleTabsSelected = !!gBrowser.multiSelectedTabsCount;
     document.getElementById("toolbar-context-bookmarkSelectedTabs").hidden = !multipleTabsSelected;
     document.getElementById("toolbar-context-bookmarkSelectedTab").hidden = multipleTabsSelected;
     document.getElementById("toolbar-context-reloadSelectedTabs").hidden = !multipleTabsSelected;
     document.getElementById("toolbar-context-reloadSelectedTab").hidden = multipleTabsSelected;
     document.getElementById("toolbar-context-selectAllTabs").disabled = gBrowser.allTabsSelected();
     document.getElementById("toolbar-context-undoCloseTab").disabled =
       SessionStore.getClosedTabCount(window) == 0;
+
+    MozXULElement.insertFTLIfNeeded("browser/toolbarContextMenu.ftl");
+    document.getElementById("toolbar-context-menu").querySelectorAll("[data-lazy-l10n-id]").forEach(el => {
+      el.setAttribute("data-l10n-id", el.getAttribute("data-lazy-l10n-id"));
+      el.removeAttribute("data-lazy-l10n-id");
+    });
     return;
   }
 
   let movable = toolbarItem &&
                 toolbarItem.id &&
                 CustomizableUI.isWidgetRemovable(toolbarItem);
   if (movable) {
     if (CustomizableUI.isSpecialWidget(toolbarItem.id)) {
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -411,42 +411,36 @@
       <menuitem oncommand="gCustomizeMode.removeFromArea(document.popupNode)"
                 accesskey="&customizeMenu.removeFromToolbar.accesskey;"
                 label="&customizeMenu.removeFromToolbar.label;"
                 contexttype="toolbaritem"
                 class="customize-context-removeFromToolbar"/>
       <menuitem id="toolbar-context-reloadSelectedTab"
                 contexttype="tabbar"
                 oncommand="gBrowser.reloadMultiSelectedTabs();"
-                label="&toolbarContextMenu.reloadSelectedTab.label;"
-                accesskey="&toolbarContextMenu.reloadSelectedTab.accesskey;"/>
+                data-lazy-l10n-id="toolbar-context-menu-reload-selected-tab"/>
       <menuitem id="toolbar-context-reloadSelectedTabs"
                 contexttype="tabbar"
                 oncommand="gBrowser.reloadMultiSelectedTabs();"
-                label="&toolbarContextMenu.reloadSelectedTabs.label;"
-                accesskey="&toolbarContextMenu.reloadSelectedTabs.accesskey;"/>
+                data-lazy-l10n-id="toolbar-context-menu-reload-selected-tabs"/>
       <menuitem id="toolbar-context-bookmarkSelectedTab"
                 contexttype="tabbar"
                 oncommand="PlacesCommandHook.bookmarkPages(PlacesCommandHook.uniqueSelectedPages);"
-                label="&toolbarContextMenu.bookmarkSelectedTab.label;"
-                accesskey="&toolbarContextMenu.bookmarkSelectedTab.accesskey;"/>
+                data-lazy-l10n-id="toolbar-context-menu-bookmark-selected-tab"/>
       <menuitem id="toolbar-context-bookmarkSelectedTabs"
                 contexttype="tabbar"
                 oncommand="PlacesCommandHook.bookmarkPages(PlacesCommandHook.uniqueSelectedPages);"
-                label="&toolbarContextMenu.bookmarkSelectedTabs.label;"
-                accesskey="&toolbarContextMenu.bookmarkSelectedTabs.accesskey;"/>
+                data-lazy-l10n-id="toolbar-context-menu-bookmark-selected-tabs"/>
       <menuitem id="toolbar-context-selectAllTabs"
                 contexttype="tabbar"
                 oncommand="gBrowser.selectAllTabs();"
-                label="&toolbarContextMenu.selectAllTabs.label;"
-                accesskey="&toolbarContextMenu.selectAllTabs.accesskey;"/>
+                data-lazy-l10n-id="toolbar-context-menu-select-all-tabs"/>
       <menuitem id="toolbar-context-undoCloseTab"
                 contexttype="tabbar"
-                label="&toolbarContextMenu.undoCloseTab.label;"
-                accesskey="&toolbarContextMenu.undoCloseTab.accesskey;"
+                data-lazy-l10n-id="toolbar-context-menu-undo-close-tab"
                 observes="History:UndoCloseTab"/>
       <menuseparator/>
       <menuseparator id="viewToolbarsMenuSeparator"/>
       <!-- XXXgijs: we're using oncommand handler here to avoid the event being
                     redirected to the command element, thus preventing
                     listeners on the menupopup or further up the tree from
                     seeing the command event pass by. The observes attribute is
                     here so that the menuitem is still disabled and re-enabled
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -121,20 +121,16 @@ var whitelist = [
   // browser/extensions/pdfjs/content/web/viewer.js#7450
   {file: "resource://pdf.js/web/debugger.js"},
 
   // resource://app/modules/translation/TranslationContentHandler.jsm
   {file: "resource://app/modules/translation/BingTranslator.jsm"},
   {file: "resource://app/modules/translation/GoogleTranslator.jsm"},
   {file: "resource://app/modules/translation/YandexTranslator.jsm"},
 
-  // Used in Firefox Monitor, which is an extension - we don't check
-  // files inside the XPI.
-  {file: "resource://app/modules/EveryWindow.jsm"},
-
   // Starting from here, files in the whitelist are bugs that need fixing.
   // Bug 1339424 (wontfix?)
   {file: "chrome://browser/locale/taskbar.properties",
    platforms: ["linux", "macosx"]},
   // Bug 1356031 (only used by devtools)
   {file: "chrome://global/skin/icons/error-16.png"},
   // Bug 1344267
   {file: "chrome://marionette/content/test_anonymous_content.xul"},
--- a/browser/components/customizableui/test/browser_940013_registerToolbarNode_calls_registerArea.js
+++ b/browser/components/customizableui/test/browser_940013_registerToolbarNode_calls_registerArea.js
@@ -28,23 +28,20 @@ add_task(async function() {
   assertAreaPlacements(kToolbarId, [kButtonId]);
   ok(!CustomizableUI.inDefaultState, "No longer in default state after toolbar is registered and visible.");
   CustomizableUI.unregisterArea(kToolbarId, true);
   toolbar.remove();
   ok(CustomizableUI.inDefaultState, "Everything should be in its default state.");
   btn.remove();
 });
 
-add_task(async function asyncCleanup() {
-  await resetCustomization();
-});
-
-function cleanup() {
+async function cleanup() {
   let toolbar = document.getElementById(kToolbarId);
   if (toolbar) {
     toolbar.remove();
   }
   let btn = document.getElementById(kButtonId) ||
             gNavToolbox.querySelector("#" + kButtonId);
   if (btn) {
     btn.remove();
   }
+  await resetCustomization();
 }
--- a/browser/components/customizableui/test/browser_978084_dragEnd_after_move.js
+++ b/browser/components/customizableui/test/browser_978084_dragEnd_after_move.js
@@ -35,12 +35,12 @@ add_task(async function() {
   CustomizableUI.getCustomizationTarget(navbar).appendChild(draggedItem);
   await startCustomizing();
   simulateItemDrag(draggedItem, CustomizableUI.getCustomizationTarget(dest));
   is(document.documentElement.hasAttribute("customizing-movingItem"), false,
      "Make sure customizing-movingItem is removed");
   await endCustomizing();
 });
 
-add_task(async function asyncCleanup() {
+registerCleanupFunction(async function asyncCleanup() {
   await endCustomizing();
-  await resetCustomization();
+  removeCustomToolbars();
 });
--- a/browser/components/enterprisepolicies/Policies.jsm
+++ b/browser/components/enterprisepolicies/Policies.jsm
@@ -674,16 +674,27 @@ var Policies = {
           setAndLockPref("browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons", false);
           setAndLockPref("browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features", false);
           manager.disallowFeature("xpinstall");
         }
       }
     },
   },
 
+  "LocalFileLinks": {
+    onBeforeAddons(manager, param) {
+      // If there are existing capabilities, lock them with the policy pref.
+      let policyNames = Services.prefs.getCharPref("capability.policy.policynames", "").split(" ");
+      policyNames.push("localfilelinks_policy");
+      setAndLockPref("capability.policy.policynames", policyNames.join(" "));
+      setAndLockPref("capability.policy.localfilelinks_policy.checkloaduri.enabled", "allAccess");
+      setAndLockPref("capability.policy.localfilelinks_policy.sites", param.join(" "));
+    },
+  },
+
   "NetworkPrediction": {
     onBeforeAddons(manager, param) {
       setAndLockPref("network.dns.disablePrefetch", !param);
       setAndLockPref("network.dns.disablePrefetchFromHTTPS", !param);
     },
   },
 
   "NewTabPage": {
--- a/browser/components/enterprisepolicies/schemas/policies-schema.json
+++ b/browser/components/enterprisepolicies/schemas/policies-schema.json
@@ -398,16 +398,23 @@
           }
         },
         "Default": {
           "type": "boolean"
         }
       }
     },
 
+    "LocalFileLinks": {
+      "type": "array",
+      "items": {
+        "type": "string"
+      }
+    },
+
     "NetworkPrediction": {
       "type": "boolean"
     },
 
     "NewTabPage": {
       "type": "boolean"
     },
 
--- a/browser/components/newtab/lib/ActivityStream.jsm
+++ b/browser/components/newtab/lib/ActivityStream.jsm
@@ -230,17 +230,17 @@ const PREFS_CONFIG = new Map([
         "CA": ["en-CA", "en-GB", "en-US", "en-ZA"],
       })[geo];
       const isEnabled = IS_NIGHTLY_OR_UNBRANDED_BUILD && locales && locales.includes(locale);
       return JSON.stringify({
         api_key_pref: "extensions.pocket.oAuthConsumerKey",
         collapsible: true,
         enabled: isEnabled,
         show_spocs: showSpocs({geo}),
-        hardcoded_layout: true,
+        hardcoded_layout: false,
         personalized: false,
         // This is currently an exmple layout used for dev purposes.
         layout_endpoint: "https://getpocket.cdn.mozilla.net/v3/newtab/layout?version=1&consumer_key=$apiKey&layout_variant=basic",
       });
     },
   }],
   ["discoverystream.endpoints", {
     title: "Endpoint prefixes (comma-separated) that are allowed to be requested",
--- a/browser/components/preferences/in-content/search.js
+++ b/browser/components/preferences/in-content/search.js
@@ -233,17 +233,17 @@ var gSearchPane = {
       case "select":
         if (aEvent.target.id == "engineList") {
           gSearchPane.onTreeSelect();
         }
         break;
       case "blur":
         if (aEvent.target.id == "engineList" &&
             aEvent.target.inputField == document.getBindingParent(aEvent.originalTarget)) {
-          gSearchPane.onInputBlur();
+          gSearchPane.onInputBlur(aEvent);
         }
         break;
     }
   },
 
   observe(aEngine, aTopic, aVerb) {
     if (aTopic == "browser-search-engine-modified") {
       aEngine.QueryInterface(Ci.nsISearchEngine);
--- a/browser/components/preferences/in-content/tests/browser_contentblocking.js
+++ b/browser/components/preferences/in-content/tests/browser_contentblocking.js
@@ -299,30 +299,22 @@ add_task(async function testContentBlock
   await TestUtils.waitForCondition(() => Services.prefs.getStringPref(CAT_PREF) == "custom");
   // The custom option does not force changes of any prefs, other than CAT_PREF, all other TP prefs should remain as they were for standard.
   for (let pref of prefs) {
     ok(!Services.prefs.prefHasUserValue(pref), `the pref ${pref} remains as default value`);
   }
   is(Services.prefs.getStringPref(CAT_PREF), "custom", `${CAT_PREF} has been set to custom`);
 
   strictRadioOption.click();
-  await TestUtils.waitForCondition(() => Services.prefs.prefHasUserValue(TP_PREF));
-
-  // Changing the TP_PREF should necessarily set CAT_PREF to "custom"
-  Services.prefs.setBoolPref(TP_PREF, false);
-  await TestUtils.waitForCondition(() => !Services.prefs.prefHasUserValue(TP_PREF));
-  is(Services.prefs.getStringPref(CAT_PREF), "custom", `${CAT_PREF} has been set to custom`);
-
-  strictRadioOption.click();
   await TestUtils.waitForCondition(() => Services.prefs.getStringPref(CAT_PREF) == "strict");
 
-  // Changing the FP_PREF and CM_PREF should necessarily set CAT_PREF to "custom"
-  for (let pref of [FP_PREF, CM_PREF]) {
+  // Changing the FP_PREF, CM_PREF, TP_PREF, or TP_PBM_PREF should necessarily set CAT_PREF to "custom"
+  for (let pref of [FP_PREF, CM_PREF, TP_PREF, TP_PBM_PREF]) {
     Services.prefs.setBoolPref(pref, !Services.prefs.getBoolPref(pref));
-    await TestUtils.waitForCondition(() => Services.prefs.prefHasUserValue(pref));
+    await TestUtils.waitForCondition(() => Services.prefs.getStringPref(CAT_PREF) == "custom");
     is(Services.prefs.getStringPref(CAT_PREF), "custom", `${CAT_PREF} has been set to custom`);
 
     strictRadioOption.click();
     await TestUtils.waitForCondition(() => Services.prefs.getStringPref(CAT_PREF) == "strict");
   }
 
   // Changing the NCB_PREF should necessarily set CAT_PREF to "custom"
   let defaultNCB = defaults.get(NCB_PREF);
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -550,18 +550,17 @@ class UrlbarInput {
     // next) or the cursor isn't at the end of the input.  But if there is a
     // selection and it's the autofill placeholder value, then do autofill.
     if (!isPlaceholderSelected &&
         (this.selectionStart != this.selectionEnd ||
          this.selectionEnd != this._lastSearchString.length)) {
       return;
     }
 
-    let { value, selectionStart, selectionEnd } = result.autofill;
-    this._autofillValue(value, selectionStart, selectionEnd);
+    this.setValueFromResult(result);
   }
 
   /**
    * Starts a query based on the current input value.
    *
    * @param {boolean} [options.allowAutofill]
    *   Whether or not to allow providers to include autofill results.
    * @param {string} [options.searchString]
deleted file mode 100644
--- a/browser/config/tooltool-manifests/win32/vs2015.manifest
+++ /dev/null
@@ -1,25 +0,0 @@
-[
-  {
-    "size": 266240,
-    "digest": "bb345b0e700ffab4d09436981f14b5de84da55a3f18a7f09ebc4364a4488acdeab8d46f447b12ac70f2da1444a68b8ce8b8675f0dae2ccf845e966d1df0f0869",
-    "algorithm": "sha512",
-    "filename": "mozmake.exe"
-  },
-  {
-    "version": "Visual Studio 2015 Update 3 14.0.25425.01 / SDK 10.0.14393.0",
-    "size": 326656969,
-    "digest": "babc414ffc0457d27f5a1ed24a8e4873afbe2f1c1a4075469a27c005e1babc3b2a788f643f825efedff95b79686664c67ec4340ed535487168a3482e68559bc7",
-    "algorithm": "sha512",
-    "filename": "vs2015u3.zip",
-    "unpack": true
-  },
-  {
-    "version": "makecab rev d2bc6797648b7a834782714a55d339d2fd4e58c8",
-    "algorithm": "sha512",
-    "visibility": "public",
-    "filename": "makecab.tar.bz2",
-    "unpack": true,
-    "digest": "196ac6a567c85559957dfe511c3d8654d23c94d5603259e19ccafe9d71e0e4ccee63ccc9a778f2699654b786cda54266108b7d4db543d01bb0b42545b4e6ec75",
-    "size": 297118
-  }
-]
deleted file mode 100644
--- a/browser/config/tooltool-manifests/win64/vs2015.manifest
+++ /dev/null
@@ -1,25 +0,0 @@
-[
-  {
-    "size": 266240,
-    "digest": "bb345b0e700ffab4d09436981f14b5de84da55a3f18a7f09ebc4364a4488acdeab8d46f447b12ac70f2da1444a68b8ce8b8675f0dae2ccf845e966d1df0f0869",
-    "algorithm": "sha512",
-    "filename": "mozmake.exe"
-  },
-  {
-    "version": "Visual Studio 2015 Update 3 14.0.25425.01 / SDK 10.0.14393.0",
-    "size": 326656969,
-    "digest": "babc414ffc0457d27f5a1ed24a8e4873afbe2f1c1a4075469a27c005e1babc3b2a788f643f825efedff95b79686664c67ec4340ed535487168a3482e68559bc7",
-    "algorithm": "sha512",
-    "filename": "vs2015u3.zip",
-    "unpack": true
-  },
-  {
-    "version": "makecab rev d2bc6797648b7a834782714a55d339d2fd4e58c8",
-    "algorithm": "sha512",
-    "visibility": "public",
-    "filename": "makecab.tar.bz2",
-    "unpack": true,
-    "digest": "196ac6a567c85559957dfe511c3d8654d23c94d5603259e19ccafe9d71e0e4ccee63ccc9a778f2699654b786cda54266108b7d4db543d01bb0b42545b4e6ec75",
-    "size": 297118
-  }
-]
--- a/browser/extensions/fxmonitor/moz.build
+++ b/browser/extensions/fxmonitor/moz.build
@@ -17,18 +17,13 @@ FINAL_TARGET_FILES.features['fxmonitor@m
 FINAL_TARGET_FILES.features['fxmonitor@mozilla.org']['assets'] += [
   'assets/alert.svg',
   'assets/monitor32.svg'
 ]
 
 FINAL_TARGET_FILES.features['fxmonitor@mozilla.org']['privileged'] += [
   'privileged/api.js',
   'privileged/FirefoxMonitor.css',
-  'privileged/FirefoxMonitor.jsm',
   'privileged/schema.json'
 ]
 
-FINAL_TARGET_FILES.features['fxmonitor@mozilla.org']['privileged']['subscripts'] += [
-  'privileged/subscripts/Globals.jsm'
-]
-
 with Files('**'):
   BUG_COMPONENT = ('Firefox', 'Firefox Monitor')
deleted file mode 100644
--- a/browser/extensions/fxmonitor/privileged/FirefoxMonitor.jsm
+++ /dev/null
@@ -1,521 +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/. */
-
-/* globals Services, XPCOMUtils */
-
-this.FirefoxMonitor = {
-  // Map of breached site host -> breach metadata.
-  domainMap: new Map(),
-
-  // Set of hosts for which the user has already been shown,
-  // and interacted with, the popup.
-  warnedHostsSet: new Set(),
-
-  // The above set is persisted as a JSON string in this pref.
-  kWarnedHostsPref: "extensions.fxmonitor.warnedHosts",
-
-  // Reference to the extension object from the WebExtension context.
-  // Used for getting URIs for resources packaged in the extension.
-  extension: null,
-
-  // Whether we've started observing for the user visiting a breached site.
-  observerAdded: false,
-
-  // This is here for documentation, will be redefined to a lazy getter
-  // that creates and returns a string bundle in loadStrings().
-  strings: null,
-
-  // This is here for documentation, will be redefined to a pref getter
-  // using XPCOMUtils.defineLazyPreferenceGetter in init().
-  enabled: null,
-
-  kEnabledPref: "extensions.fxmonitor.enabled",
-
-  // Telemetry event recording is enabled by default.
-  // If this pref exists and is true-y, it's disabled.
-  kTelemetryDisabledPref: "extensions.fxmonitor.telemetryDisabled",
-
-  kNotificationID: "fxmonitor",
-
-  // This is here for documentation, will be redefined to a pref getter
-  // using XPCOMUtils.defineLazyPreferenceGetter in delayedInit().
-  // The value of this property is used as the URL to which the user
-  // is directed when they click "Check Firefox Monitor".
-  FirefoxMonitorURL: null,
-  kFirefoxMonitorURLPref: "extensions.fxmonitor.FirefoxMonitorURL",
-  kDefaultFirefoxMonitorURL: "https://monitor.firefox.com",
-
-  // This is here for documentation, will be redefined to a pref getter
-  // using XPCOMUtils.defineLazyPreferenceGetter in delayedInit().
-  // The pref stores whether the user has seen a breach alert already.
-  // The value is used in warnIfNeeded.
-  firstAlertShown: null,
-  kFirstAlertShownPref: "extensions.fxmonitor.firstAlertShown",
-
-  disable() {
-    Preferences.set(this.kEnabledPref, false);
-  },
-
-  getURL(aPath) {
-    return this.extension.getURL(aPath);
-  },
-
-  getString(aKey) {
-    return this.strings.GetStringFromName(aKey);
-  },
-
-  getFormattedString(aKey, args) {
-    return this.strings.formatStringFromName(aKey, args, args.length);
-  },
-
-  init(aExtension) {
-    this.extension = aExtension;
-
-    XPCOMUtils.defineLazyPreferenceGetter(
-      this, "enabled", this.kEnabledPref, true,
-      (pref, oldVal, newVal) => {
-        if (newVal) {
-          this.startObserving();
-        } else {
-          this.stopObserving();
-        }
-      }
-    );
-
-    if (this.enabled) {
-      this.startObserving();
-    }
-  },
-
-  // Used to enforce idempotency of delayedInit. delayedInit is
-  // called in startObserving() to ensure we load our strings, etc.
-  _delayedInited: false,
-  async delayedInit() {
-    if (this._delayedInited) {
-      return;
-    }
-
-    this._delayedInited = true;
-
-    /* globals EveryWindow, Preferences, RemoteSettings, fetch, btoa, XUL_NS */
-    Services.scriptloader.loadSubScript(
-      this.getURL("privileged/subscripts/Globals.jsm"));
-
-
-    // Expire our telemetry on November 1, at which time
-    // we should redo data-review.
-    let telemetryExpiryDate = new Date(2019, 10, 1); // Month is zero-index
-    let today = new Date();
-    let expired = today.getTime() > telemetryExpiryDate.getTime();
-
-    Services.telemetry.registerEvents("fxmonitor", {
-      "interaction": {
-        methods: ["interaction"],
-        objects: [
-          "doorhanger_shown",
-          "doorhanger_removed",
-          "check_btn",
-          "dismiss_btn",
-          "never_show_btn",
-        ],
-        record_on_release: true,
-        expired,
-      },
-    });
-
-    let telemetryEnabled = !Preferences.get(this.kTelemetryDisabledPref);
-    Services.telemetry.setEventRecordingEnabled("fxmonitor", telemetryEnabled);
-
-    let warnedHostsJSON = Preferences.get(this.kWarnedHostsPref, "");
-    if (warnedHostsJSON) {
-      try {
-        let json = JSON.parse(warnedHostsJSON);
-        this.warnedHostsSet = new Set(json);
-      } catch (ex) {
-        // Invalid JSON, invalidate the pref.
-        Preferences.reset(this.kWarnedHostsPref);
-      }
-    }
-
-    XPCOMUtils.defineLazyPreferenceGetter(this, "FirefoxMonitorURL",
-      this.kFirefoxMonitorURLPref, this.kDefaultFirefoxMonitorURL);
-
-    XPCOMUtils.defineLazyPreferenceGetter(this, "firstAlertShown",
-      this.kFirstAlertShownPref, false);
-
-    await this.loadStrings();
-    await this.loadBreaches();
-  },
-
-  loadStrings() {
-    let l10nManifest;
-    if (this.extension.rootURI instanceof Ci.nsIJARURI) {
-      l10nManifest = this.extension.rootURI.JARFile
-                            .QueryInterface(Ci.nsIFileURL).file;
-    } else if (this.extension.rootURI instanceof Ci.nsIFileURL) {
-      l10nManifest = this.extension.rootURI.file;
-    }
-
-    if (l10nManifest) {
-      Components.manager.addBootstrappedManifestLocation(l10nManifest);
-
-      XPCOMUtils.defineLazyGetter(this, "strings", () => {
-        return Services.strings.createBundle(
-          "chrome://fxmonitor/locale/fxmonitor.properties");
-      });
-    } else {
-      // Something is very strange if we reach this line, so we throw
-      // in order to prevent init from completing and burst the stack.
-      throw new Error("Cannot find fxmonitor chrome.manifest for registering translated strings");
-    }
-  },
-
-  kRemoteSettingsKey: "fxmonitor-breaches",
-  async loadBreaches() {
-    let populateSites = (data) => {
-      this.domainMap.clear();
-      data.forEach(site => {
-        if (!site.Domain || !site.Name || !site.PwnCount || !site.BreachDate || !site.AddedDate) {
-          Cu.reportError(`Firefox Monitor: malformed breach entry.\nSite:\n${JSON.stringify(site)}`);
-          return;
-        }
-
-        try {
-          this.domainMap.set(site.Domain, {
-            Name: site.Name,
-            PwnCount: site.PwnCount,
-            Year: (new Date(site.BreachDate)).getFullYear(),
-            AddedDate: site.AddedDate.split("T")[0],
-          });
-        } catch (e) {
-          Cu.reportError(`Firefox Monitor: malformed breach entry.\nSite:\n${JSON.stringify(site)}\nError:\n${e}`);
-        }
-      });
-    };
-
-    RemoteSettings(this.kRemoteSettingsKey).on("sync", (event) => {
-      let { data: { current } } = event;
-      populateSites(current);
-    });
-
-    let data = await RemoteSettings(this.kRemoteSettingsKey).get();
-    if (data && data.length) {
-      populateSites(data);
-    }
-  },
-
-  // nsIWebProgressListener implementation.
-  onStateChange(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
-    if (!(aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) ||
-        (!aWebProgress.isTopLevel || aWebProgress.isLoadingDocument ||
-         !Components.isSuccessCode(aStatus))) {
-      return;
-    }
-
-    let host;
-    try {
-      host = Services.eTLD.getBaseDomain(aRequest.URI);
-    } catch (e) {
-      // If we can't get the host for the URL, it's not one we
-      // care about for breach alerts anyway.
-      return;
-    }
-
-    this.warnIfNeeded(aBrowser, host);
-  },
-
-  notificationsByWindow: new WeakMap(),
-  panelUIsByWindow: new WeakMap(),
-
-  async startObserving() {
-    if (this.observerAdded) {
-      return;
-    }
-
-    EveryWindow.registerCallback(
-      this.kNotificationID,
-      (win) => {
-        if (this.notificationsByWindow.has(win)) {
-          // We've already set up this window.
-          return;
-        }
-
-        this.notificationsByWindow.set(win, new Set());
-
-        // Start listening across all tabs! The UI will
-        // be set up lazily when we actually need to show
-        // a notification.
-        this.delayedInit().then(() => {
-          win.gBrowser.addTabsProgressListener(this);
-        });
-      },
-      (win, closing) => {
-        // If the window is going away, don't bother doing anything.
-        if (closing) {
-          return;
-        }
-
-        let DOMWindowUtils = win.windowUtils;
-        DOMWindowUtils.removeSheetUsingURIString(this.getURL("privileged/FirefoxMonitor.css"),
-                                                 DOMWindowUtils.AUTHOR_SHEET);
-
-        this.notificationsByWindow.get(win).forEach(n => {
-          n.remove();
-        });
-        this.notificationsByWindow.delete(win);
-
-        let doc = win.document;
-        doc.getElementById(`${this.kNotificationID}-notification-anchor`).remove();
-        doc.getElementById(`${this.kNotificationID}-notification`).remove();
-        this.panelUIsByWindow.delete(win);
-
-        win.gBrowser.removeTabsProgressListener(this);
-      },
-    );
-
-    this.observerAdded = true;
-  },
-
-  setupPanelUI(win) {
-    // Inject our stylesheet.
-    let DOMWindowUtils = win.windowUtils;
-    DOMWindowUtils.loadSheetUsingURIString(this.getURL("privileged/FirefoxMonitor.css"),
-                                           DOMWindowUtils.AUTHOR_SHEET);
-
-    // Setup the popup notification stuff. First, the URL bar icon:
-    let doc = win.document;
-    let notificationBox = doc.getElementById("notification-popup-box");
-    // We create a box to use as the anchor, and put an icon image
-    // inside it. This way, when we animate the icon, its scale change
-    // does not cause the popup notification to bounce due to the anchor
-    // point moving.
-    let anchorBox = doc.createElementNS(XUL_NS, "box");
-    anchorBox.setAttribute("id", `${this.kNotificationID}-notification-anchor`);
-    anchorBox.classList.add("notification-anchor-icon");
-    let img = doc.createElementNS(XUL_NS, "image");
-    img.setAttribute("role", "button");
-    img.classList.add(`${this.kNotificationID}-icon`);
-    img.style.listStyleImage = `url(${this.getURL("assets/monitor32.svg")})`;
-    anchorBox.appendChild(img);
-    notificationBox.appendChild(anchorBox);
-    img.setAttribute("tooltiptext",
-      this.getFormattedString("fxmonitor.anchorIcon.tooltiptext",
-                              [this.getString("fxmonitor.brandName")]));
-
-    // Now, the popupnotificationcontent:
-    let parentElt = doc.defaultView.PopupNotifications.panel.parentNode;
-    let pn = doc.createElementNS(XUL_NS, "popupnotification");
-    let pnContent = doc.createElementNS(XUL_NS, "popupnotificationcontent");
-    let panelUI = new PanelUI(doc);
-    pnContent.appendChild(panelUI.box);
-    pn.appendChild(pnContent);
-    pn.setAttribute("id", `${this.kNotificationID}-notification`);
-    pn.setAttribute("hidden", "true");
-    parentElt.appendChild(pn);
-    this.panelUIsByWindow.set(win, panelUI);
-    return panelUI;
-  },
-
-  stopObserving() {
-    if (!this.observerAdded) {
-      return;
-    }
-
-    EveryWindow.unregisterCallback(this.kNotificationID);
-
-    this.observerAdded = false;
-  },
-
-  warnIfNeeded(browser, host) {
-    if (!this.enabled || this.warnedHostsSet.has(host) || !this.domainMap.has(host)) {
-      return;
-    }
-
-    let site = this.domainMap.get(host);
-
-    // We only alert for breaches that were found up to 2 months ago,
-    // except for the very first alert we show the user - in which case,
-    // we include breaches found in the last three years.
-    let breachDateThreshold = new Date();
-    if (this.firstAlertShown) {
-      breachDateThreshold.setMonth(breachDateThreshold.getMonth() - 2);
-    } else {
-      breachDateThreshold.setFullYear(breachDateThreshold.getFullYear() - 1);
-    }
-
-    if (new Date(site.AddedDate).getTime() < breachDateThreshold.getTime()) {
-      return;
-    } else if (!this.firstAlertShown) {
-      Preferences.set(this.kFirstAlertShownPref, true);
-    }
-
-    this.warnedHostsSet.add(host);
-    Preferences.set(this.kWarnedHostsPref, JSON.stringify([...this.warnedHostsSet]));
-
-    let doc = browser.ownerDocument;
-    let win = doc.defaultView;
-    let panelUI = this.panelUIsByWindow.get(win);
-    if (!panelUI) {
-      panelUI = this.setupPanelUI(win);
-    }
-
-    let animatedOnce = false;
-    let populatePanel = (event) => {
-      switch (event) {
-        case "showing":
-          panelUI.refresh(site);
-          if (animatedOnce) {
-            // If we've already animated once for this site, don't animate again.
-            doc.getElementById("notification-popup")
-               .setAttribute("fxmonitoranimationdone", "true");
-            doc.getElementById(`${this.kNotificationID}-notification-anchor`)
-               .setAttribute("fxmonitoranimationdone", "true");
-            break;
-          }
-          // Make sure we animate if we're coming from another tab that has
-          // this attribute set.
-          doc.getElementById("notification-popup")
-             .removeAttribute("fxmonitoranimationdone");
-          doc.getElementById(`${this.kNotificationID}-notification-anchor`)
-             .removeAttribute("fxmonitoranimationdone");
-          break;
-        case "shown":
-          animatedOnce = true;
-          break;
-        case "removed":
-          this.notificationsByWindow.get(win).delete(
-            win.PopupNotifications.getNotification(this.kNotificationID, browser));
-          Services.telemetry.recordEvent("fxmonitor", "interaction", "doorhanger_removed");
-          break;
-      }
-    };
-
-    let n = win.PopupNotifications.show(
-      browser, this.kNotificationID, "",
-      `${this.kNotificationID}-notification-anchor`,
-      panelUI.primaryAction, panelUI.secondaryActions, {
-        persistent: true,
-        hideClose: true,
-        eventCallback: populatePanel,
-        popupIconURL: this.getURL("assets/monitor32.svg"),
-      }
-    );
-
-    Services.telemetry.recordEvent("fxmonitor", "interaction", "doorhanger_shown");
-
-    this.notificationsByWindow.get(win).add(n);
-  },
-};
-
-/* globals PluralForm */
-
-function PanelUI(doc) {
-  this.site = null;
-  this.doc = doc;
-
-  let box = doc.createElementNS(XUL_NS, "vbox");
-
-  let elt = doc.createElementNS(XUL_NS, "description");
-  elt.textContent = this.getString("fxmonitor.popupHeader");
-  elt.classList.add("headerText");
-  box.appendChild(elt);
-
-  elt = doc.createElementNS(XUL_NS, "description");
-  elt.classList.add("popupText");
-  box.appendChild(elt);
-
-  this.box = box;
-}
-
-PanelUI.prototype = {
-  getString(aKey) {
-    return FirefoxMonitor.getString(aKey);
-  },
-
-  getFormattedString(aKey, args) {
-    return FirefoxMonitor.getFormattedString(aKey, args);
-  },
-
-  get brandString() {
-    if (this._brandString) {
-      return this._brandString;
-    }
-    return this._brandString = this.getString("fxmonitor.brandName");
-  },
-
-  getFirefoxMonitorURL: (aSiteName) => {
-    return `${FirefoxMonitor.FirefoxMonitorURL}/?breach=${encodeURIComponent(aSiteName)}&utm_source=firefox&utm_medium=popup`;
-  },
-
-  get primaryAction() {
-    if (this._primaryAction) {
-      return this._primaryAction;
-    }
-    return this._primaryAction = {
-      label: this.getFormattedString("fxmonitor.checkButton.label", [this.brandString]),
-      accessKey: this.getString("fxmonitor.checkButton.accessKey"),
-      callback: () => {
-        let win = this.doc.defaultView;
-        win.openTrustedLinkIn(
-          this.getFirefoxMonitorURL(this.site.Name), "tab", { });
-
-        Services.telemetry.recordEvent("fxmonitor", "interaction", "check_btn");
-      },
-    };
-  },
-
-  get secondaryActions() {
-    if (this._secondaryActions) {
-      return this._secondaryActions;
-    }
-    return this._secondaryActions = [
-      {
-        label: this.getString("fxmonitor.dismissButton.label"),
-        accessKey: this.getString("fxmonitor.dismissButton.accessKey"),
-        callback: () => {
-          Services.telemetry.recordEvent("fxmonitor", "interaction", "dismiss_btn");
-        },
-      }, {
-        label: this.getFormattedString("fxmonitor.neverShowButton.label", [this.brandString]),
-        accessKey: this.getString("fxmonitor.neverShowButton.accessKey"),
-        callback: () => {
-          FirefoxMonitor.disable();
-          Services.telemetry.recordEvent("fxmonitor", "interaction", "never_show_btn");
-        },
-      },
-    ];
-  },
-
-  refresh(site) {
-    this.site = site;
-
-    let elt = this.box.querySelector(".popupText");
-
-    // If > 100k, the PwnCount is rounded down to the most significant
-    // digit and prefixed with "More than".
-    // Ex.: 12,345 -> 12,345
-    //      234,567 -> More than 200,000
-    //      345,678,901 -> More than 300,000,000
-    //      4,567,890,123 -> More than 4,000,000,000
-    let k100k = 100000;
-    let pwnCount = site.PwnCount;
-    let stringName = "fxmonitor.popupText";
-    if (pwnCount > k100k) {
-      let multiplier = 1;
-      while (pwnCount >= 10) {
-        pwnCount /= 10;
-        multiplier *= 10;
-      }
-      pwnCount = Math.floor(pwnCount) * multiplier;
-      stringName = "fxmonitor.popupTextRounded";
-    }
-
-    elt.textContent =
-      PluralForm.get(pwnCount, this.getString(stringName))
-                .replace("#1", pwnCount.toLocaleString())
-                .replace("#2", site.Name)
-                .replace("#3", site.Year)
-                .replace("#4", this.brandString);
-  },
-};
--- a/browser/extensions/fxmonitor/privileged/api.js
+++ b/browser/extensions/fxmonitor/privileged/api.js
@@ -1,32 +1,547 @@
 /* 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/. */
 
-/* globals ExtensionAPI */
+/* globals ExtensionAPI, XPCOMUtils */
 
+ChromeUtils.defineModuleGetter(this, "EveryWindow",
+                               "resource:///modules/EveryWindow.jsm");
+ChromeUtils.defineModuleGetter(this, "PluralForm",
+                               "resource://gre/modules/PluralForm.jsm");
+ChromeUtils.defineModuleGetter(this, "Preferences",
+                               "resource://gre/modules/Preferences.jsm");
+ChromeUtils.defineModuleGetter(this, "RemoteSettings",
+                               "resource://services-settings/remote-settings.js");
 ChromeUtils.defineModuleGetter(this, "Services",
                                "resource://gre/modules/Services.jsm");
 
-let FirefoxMonitorContainer = {};
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 this.fxmonitor = class extends ExtensionAPI {
   getAPI(context) {
-    Services.scriptloader.loadSubScript(context.extension.getURL("privileged/FirefoxMonitor.jsm"),
-                                        FirefoxMonitorContainer);
     return {
       fxmonitor: {
         async start() {
-          await FirefoxMonitorContainer.FirefoxMonitor.init(context.extension);
+          await FirefoxMonitor.init(context.extension);
         },
       },
     };
   }
 
   onShutdown(shutdownReason) {
-    if (Services.startup.shuttingDown || !FirefoxMonitorContainer.FirefoxMonitor) {
+    if (Services.startup.shuttingDown) {
+      return;
+    }
+
+    FirefoxMonitor.stopObserving();
+  }
+};
+
+this.FirefoxMonitor = {
+  // Map of breached site host -> breach metadata.
+  domainMap: new Map(),
+
+  // Set of hosts for which the user has already been shown,
+  // and interacted with, the popup.
+  warnedHostsSet: new Set(),
+
+  // The above set is persisted as a JSON string in this pref.
+  kWarnedHostsPref: "extensions.fxmonitor.warnedHosts",
+
+  // Reference to the extension object from the WebExtension context.
+  // Used for getting URIs for resources packaged in the extension.
+  extension: null,
+
+  // Whether we've started observing for the user visiting a breached site.
+  observerAdded: false,
+
+  // This is here for documentation, will be redefined to a lazy getter
+  // that creates and returns a string bundle in loadStrings().
+  strings: null,
+
+  // This is here for documentation, will be redefined to a pref getter
+  // using XPCOMUtils.defineLazyPreferenceGetter in init().
+  enabled: null,
+
+  kEnabledPref: "extensions.fxmonitor.enabled",
+
+  // Telemetry event recording is enabled by default.
+  // If this pref exists and is true-y, it's disabled.
+  kTelemetryDisabledPref: "extensions.fxmonitor.telemetryDisabled",
+
+  kNotificationID: "fxmonitor",
+
+  // This is here for documentation, will be redefined to a pref getter
+  // using XPCOMUtils.defineLazyPreferenceGetter in delayedInit().
+  // The value of this property is used as the URL to which the user
+  // is directed when they click "Check Firefox Monitor".
+  FirefoxMonitorURL: null,
+  kFirefoxMonitorURLPref: "extensions.fxmonitor.FirefoxMonitorURL",
+  kDefaultFirefoxMonitorURL: "https://monitor.firefox.com",
+
+  // This is here for documentation, will be redefined to a pref getter
+  // using XPCOMUtils.defineLazyPreferenceGetter in delayedInit().
+  // The pref stores whether the user has seen a breach alert already.
+  // The value is used in warnIfNeeded.
+  firstAlertShown: null,
+  kFirstAlertShownPref: "extensions.fxmonitor.firstAlertShown",
+
+  disable() {
+    Preferences.set(this.kEnabledPref, false);
+  },
+
+  getURL(aPath) {
+    return this.extension.getURL(aPath);
+  },
+
+  getString(aKey) {
+    return this.strings.GetStringFromName(aKey);
+  },
+
+  getFormattedString(aKey, args) {
+    return this.strings.formatStringFromName(aKey, args, args.length);
+  },
+
+  init(aExtension) {
+    this.extension = aExtension;
+
+    XPCOMUtils.defineLazyPreferenceGetter(
+      this, "enabled", this.kEnabledPref, true,
+      (pref, oldVal, newVal) => {
+        if (newVal) {
+          this.startObserving();
+        } else {
+          this.stopObserving();
+        }
+      }
+    );
+
+    if (this.enabled) {
+      this.startObserving();
+    }
+  },
+
+  // Used to enforce idempotency of delayedInit. delayedInit is
+  // called in startObserving() to ensure we load our strings, etc.
+  _delayedInited: false,
+  async delayedInit() {
+    if (this._delayedInited) {
+      return;
+    }
+
+    this._delayedInited = true;
+
+    // Expire our telemetry on November 1, at which time
+    // we should redo data-review.
+    let telemetryExpiryDate = new Date(2019, 10, 1); // Month is zero-index
+    let today = new Date();
+    let expired = today.getTime() > telemetryExpiryDate.getTime();
+
+    Services.telemetry.registerEvents("fxmonitor", {
+      "interaction": {
+        methods: ["interaction"],
+        objects: [
+          "doorhanger_shown",
+          "doorhanger_removed",
+          "check_btn",
+          "dismiss_btn",
+          "never_show_btn",
+        ],
+        record_on_release: true,
+        expired,
+      },
+    });
+
+    let telemetryEnabled = !Preferences.get(this.kTelemetryDisabledPref);
+    Services.telemetry.setEventRecordingEnabled("fxmonitor", telemetryEnabled);
+
+    let warnedHostsJSON = Preferences.get(this.kWarnedHostsPref, "");
+    if (warnedHostsJSON) {
+      try {
+        let json = JSON.parse(warnedHostsJSON);
+        this.warnedHostsSet = new Set(json);
+      } catch (ex) {
+        // Invalid JSON, invalidate the pref.
+        Preferences.reset(this.kWarnedHostsPref);
+      }
+    }
+
+    XPCOMUtils.defineLazyPreferenceGetter(this, "FirefoxMonitorURL",
+      this.kFirefoxMonitorURLPref, this.kDefaultFirefoxMonitorURL);
+
+    XPCOMUtils.defineLazyPreferenceGetter(this, "firstAlertShown",
+      this.kFirstAlertShownPref, false);
+
+    await this.loadStrings();
+    await this.loadBreaches();
+  },
+
+  loadStrings() {
+    let l10nManifest;
+    if (this.extension.rootURI instanceof Ci.nsIJARURI) {
+      l10nManifest = this.extension.rootURI.JARFile
+                            .QueryInterface(Ci.nsIFileURL).file;
+    } else if (this.extension.rootURI instanceof Ci.nsIFileURL) {
+      l10nManifest = this.extension.rootURI.file;
+    }
+
+    if (l10nManifest) {
+      Components.manager.addBootstrappedManifestLocation(l10nManifest);
+
+      XPCOMUtils.defineLazyGetter(this, "strings", () => {
+        return Services.strings.createBundle(
+          "chrome://fxmonitor/locale/fxmonitor.properties");
+      });
+    } else {
+      // Something is very strange if we reach this line, so we throw
+      // in order to prevent init from completing and burst the stack.
+      throw new Error("Cannot find fxmonitor chrome.manifest for registering translated strings");
+    }
+  },
+
+  kRemoteSettingsKey: "fxmonitor-breaches",
+  async loadBreaches() {
+    let populateSites = (data) => {
+      this.domainMap.clear();
+      data.forEach(site => {
+        if (!site.Domain || !site.Name || !site.PwnCount || !site.BreachDate || !site.AddedDate) {
+          Cu.reportError(`Firefox Monitor: malformed breach entry.\nSite:\n${JSON.stringify(site)}`);
+          return;
+        }
+
+        try {
+          this.domainMap.set(site.Domain, {
+            Name: site.Name,
+            PwnCount: site.PwnCount,
+            Year: (new Date(site.BreachDate)).getFullYear(),
+            AddedDate: site.AddedDate.split("T")[0],
+          });
+        } catch (e) {
+          Cu.reportError(`Firefox Monitor: malformed breach entry.\nSite:\n${JSON.stringify(site)}\nError:\n${e}`);
+        }
+      });
+    };
+
+    RemoteSettings(this.kRemoteSettingsKey).on("sync", (event) => {
+      let { data: { current } } = event;
+      populateSites(current);
+    });
+
+    let data = await RemoteSettings(this.kRemoteSettingsKey).get();
+    if (data && data.length) {
+      populateSites(data);
+    }
+  },
+
+  // nsIWebProgressListener implementation.
+  onStateChange(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
+    if (!(aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) ||
+        (!aWebProgress.isTopLevel || aWebProgress.isLoadingDocument ||
+         !Components.isSuccessCode(aStatus))) {
+      return;
+    }
+
+    let host;
+    try {
+      host = Services.eTLD.getBaseDomain(aRequest.URI);
+    } catch (e) {
+      // If we can't get the host for the URL, it's not one we
+      // care about for breach alerts anyway.
+      return;
+    }
+
+    this.warnIfNeeded(aBrowser, host);
+  },
+
+  notificationsByWindow: new WeakMap(),
+  panelUIsByWindow: new WeakMap(),
+
+  async startObserving() {
+    if (this.observerAdded) {
       return;
     }
 
-    FirefoxMonitorContainer.FirefoxMonitor.stopObserving();
-  }
+    EveryWindow.registerCallback(
+      this.kNotificationID,
+      (win) => {
+        if (this.notificationsByWindow.has(win)) {
+          // We've already set up this window.
+          return;
+        }
+
+        this.notificationsByWindow.set(win, new Set());
+
+        // Start listening across all tabs! The UI will
+        // be set up lazily when we actually need to show
+        // a notification.
+        this.delayedInit().then(() => {
+          win.gBrowser.addTabsProgressListener(this);
+        });
+      },
+      (win, closing) => {
+        // If the window is going away, don't bother doing anything.
+        if (closing) {
+          return;
+        }
+
+        let DOMWindowUtils = win.windowUtils;
+        DOMWindowUtils.removeSheetUsingURIString(this.getURL("privileged/FirefoxMonitor.css"),
+                                                 DOMWindowUtils.AUTHOR_SHEET);
+
+        this.notificationsByWindow.get(win).forEach(n => {
+          n.remove();
+        });
+        this.notificationsByWindow.delete(win);
+
+        let doc = win.document;
+        doc.getElementById(`${this.kNotificationID}-notification-anchor`).remove();
+        doc.getElementById(`${this.kNotificationID}-notification`).remove();
+        this.panelUIsByWindow.delete(win);
+
+        win.gBrowser.removeTabsProgressListener(this);
+      },
+    );
+
+    this.observerAdded = true;
+  },
+
+  setupPanelUI(win) {
+    // Inject our stylesheet.
+    let DOMWindowUtils = win.windowUtils;
+    DOMWindowUtils.loadSheetUsingURIString(this.getURL("privileged/FirefoxMonitor.css"),
+                                           DOMWindowUtils.AUTHOR_SHEET);
+
+    // Setup the popup notification stuff. First, the URL bar icon:
+    let doc = win.document;
+    let notificationBox = doc.getElementById("notification-popup-box");
+    // We create a box to use as the anchor, and put an icon image
+    // inside it. This way, when we animate the icon, its scale change
+    // does not cause the popup notification to bounce due to the anchor
+    // point moving.
+    let anchorBox = doc.createElementNS(XUL_NS, "box");
+    anchorBox.setAttribute("id", `${this.kNotificationID}-notification-anchor`);
+    anchorBox.classList.add("notification-anchor-icon");
+    let img = doc.createElementNS(XUL_NS, "image");
+    img.setAttribute("role", "button");
+    img.classList.add(`${this.kNotificationID}-icon`);
+    img.style.listStyleImage = `url(${this.getURL("assets/monitor32.svg")})`;
+    anchorBox.appendChild(img);
+    notificationBox.appendChild(anchorBox);
+    img.setAttribute("tooltiptext",
+      this.getFormattedString("fxmonitor.anchorIcon.tooltiptext",
+                              [this.getString("fxmonitor.brandName")]));
+
+    // Now, the popupnotificationcontent:
+    let parentElt = doc.defaultView.PopupNotifications.panel.parentNode;
+    let pn = doc.createElementNS(XUL_NS, "popupnotification");
+    let pnContent = doc.createElementNS(XUL_NS, "popupnotificationcontent");
+    let panelUI = new PanelUI(doc);
+    pnContent.appendChild(panelUI.box);
+    pn.appendChild(pnContent);
+    pn.setAttribute("id", `${this.kNotificationID}-notification`);
+    pn.setAttribute("hidden", "true");
+    parentElt.appendChild(pn);
+    this.panelUIsByWindow.set(win, panelUI);
+    return panelUI;
+  },
+
+  stopObserving() {
+    if (!this.observerAdded) {
+      return;
+    }
+
+    EveryWindow.unregisterCallback(this.kNotificationID);
+
+    this.observerAdded = false;
+  },
+
+  warnIfNeeded(browser, host) {
+    if (!this.enabled || this.warnedHostsSet.has(host) || !this.domainMap.has(host)) {
+      return;
+    }
+
+    let site = this.domainMap.get(host);
+
+    // We only alert for breaches that were found up to 2 months ago,
+    // except for the very first alert we show the user - in which case,
+    // we include breaches found in the last three years.
+    let breachDateThreshold = new Date();
+    if (this.firstAlertShown) {
+      breachDateThreshold.setMonth(breachDateThreshold.getMonth() - 2);
+    } else {
+      breachDateThreshold.setFullYear(breachDateThreshold.getFullYear() - 1);
+    }
+
+    if (new Date(site.AddedDate).getTime() < breachDateThreshold.getTime()) {
+      return;
+    } else if (!this.firstAlertShown) {
+      Preferences.set(this.kFirstAlertShownPref, true);
+    }
+
+    this.warnedHostsSet.add(host);
+    Preferences.set(this.kWarnedHostsPref, JSON.stringify([...this.warnedHostsSet]));
+
+    let doc = browser.ownerDocument;
+    let win = doc.defaultView;
+    let panelUI = this.panelUIsByWindow.get(win);
+    if (!panelUI) {
+      panelUI = this.setupPanelUI(win);
+    }
+
+    let animatedOnce = false;
+    let populatePanel = (event) => {
+      switch (event) {
+        case "showing":
+          panelUI.refresh(site);
+          if (animatedOnce) {
+            // If we've already animated once for this site, don't animate again.
+            doc.getElementById("notification-popup")
+               .setAttribute("fxmonitoranimationdone", "true");
+            doc.getElementById(`${this.kNotificationID}-notification-anchor`)
+               .setAttribute("fxmonitoranimationdone", "true");
+            break;
+          }
+          // Make sure we animate if we're coming from another tab that has
+          // this attribute set.
+          doc.getElementById("notification-popup")
+             .removeAttribute("fxmonitoranimationdone");
+          doc.getElementById(`${this.kNotificationID}-notification-anchor`)
+             .removeAttribute("fxmonitoranimationdone");
+          break;
+        case "shown":
+          animatedOnce = true;
+          break;
+        case "removed":
+          this.notificationsByWindow.get(win).delete(
+            win.PopupNotifications.getNotification(this.kNotificationID, browser));
+          Services.telemetry.recordEvent("fxmonitor", "interaction", "doorhanger_removed");
+          break;
+      }
+    };
+
+    let n = win.PopupNotifications.show(
+      browser, this.kNotificationID, "",
+      `${this.kNotificationID}-notification-anchor`,
+      panelUI.primaryAction, panelUI.secondaryActions, {
+        persistent: true,
+        hideClose: true,
+        eventCallback: populatePanel,
+        popupIconURL: this.getURL("assets/monitor32.svg"),
+      }
+    );
+
+    Services.telemetry.recordEvent("fxmonitor", "interaction", "doorhanger_shown");
+
+    this.notificationsByWindow.get(win).add(n);
+  },
 };
+
+function PanelUI(doc) {
+  this.site = null;
+  this.doc = doc;
+
+  let box = doc.createElementNS(XUL_NS, "vbox");
+
+  let elt = doc.createElementNS(XUL_NS, "description");
+  elt.textContent = this.getString("fxmonitor.popupHeader");
+  elt.classList.add("headerText");
+  box.appendChild(elt);
+
+  elt = doc.createElementNS(XUL_NS, "description");
+  elt.classList.add("popupText");
+  box.appendChild(elt);
+
+  this.box = box;
+}
+
+PanelUI.prototype = {
+  getString(aKey) {
+    return FirefoxMonitor.getString(aKey);
+  },
+
+  getFormattedString(aKey, args) {
+    return FirefoxMonitor.getFormattedString(aKey, args);
+  },
+
+  get brandString() {
+    if (this._brandString) {
+      return this._brandString;
+    }
+    return this._brandString = this.getString("fxmonitor.brandName");
+  },
+
+  getFirefoxMonitorURL: (aSiteName) => {
+    return `${FirefoxMonitor.FirefoxMonitorURL}/?breach=${encodeURIComponent(aSiteName)}&utm_source=firefox&utm_medium=popup`;
+  },
+
+  get primaryAction() {
+    if (this._primaryAction) {
+      return this._primaryAction;
+    }
+    return this._primaryAction = {
+      label: this.getFormattedString("fxmonitor.checkButton.label", [this.brandString]),
+      accessKey: this.getString("fxmonitor.checkButton.accessKey"),
+      callback: () => {
+        let win = this.doc.defaultView;
+        win.openTrustedLinkIn(
+          this.getFirefoxMonitorURL(this.site.Name), "tab", { });
+
+        Services.telemetry.recordEvent("fxmonitor", "interaction", "check_btn");
+      },
+    };
+  },
+
+  get secondaryActions() {
+    if (this._secondaryActions) {
+      return this._secondaryActions;
+    }
+    return this._secondaryActions = [
+      {
+        label: this.getString("fxmonitor.dismissButton.label"),
+        accessKey: this.getString("fxmonitor.dismissButton.accessKey"),
+        callback: () => {
+          Services.telemetry.recordEvent("fxmonitor", "interaction", "dismiss_btn");
+        },
+      }, {
+        label: this.getFormattedString("fxmonitor.neverShowButton.label", [this.brandString]),
+        accessKey: this.getString("fxmonitor.neverShowButton.accessKey"),
+        callback: () => {
+          FirefoxMonitor.disable();
+          Services.telemetry.recordEvent("fxmonitor", "interaction", "never_show_btn");
+        },
+      },
+    ];
+  },
+
+  refresh(site) {
+    this.site = site;
+
+    let elt = this.box.querySelector(".popupText");
+
+    // If > 100k, the PwnCount is rounded down to the most significant
+    // digit and prefixed with "More than".
+    // Ex.: 12,345 -> 12,345
+    //      234,567 -> More than 200,000
+    //      345,678,901 -> More than 300,000,000
+    //      4,567,890,123 -> More than 4,000,000,000
+    let k100k = 100000;
+    let pwnCount = site.PwnCount;
+    let stringName = "fxmonitor.popupText";
+    if (pwnCount > k100k) {
+      let multiplier = 1;
+      while (pwnCount >= 10) {
+        pwnCount /= 10;
+        multiplier *= 10;
+      }
+      pwnCount = Math.floor(pwnCount) * multiplier;
+      stringName = "fxmonitor.popupTextRounded";
+    }
+
+    elt.textContent =
+      PluralForm.get(pwnCount, this.getString(stringName))
+                .replace("#1", pwnCount.toLocaleString())
+                .replace("#2", site.Name)
+                .replace("#3", site.Year)
+                .replace("#4", this.brandString);
+  },
+};
deleted file mode 100644
--- a/browser/extensions/fxmonitor/privileged/subscripts/Globals.jsm
+++ /dev/null
@@ -1,18 +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/. */
-
-/* eslint-disable no-unused-vars */
-
-ChromeUtils.defineModuleGetter(this, "Preferences",
-                               "resource://gre/modules/Preferences.jsm");
-ChromeUtils.defineModuleGetter(this, "PluralForm",
-                               "resource://gre/modules/PluralForm.jsm");
-ChromeUtils.defineModuleGetter(this, "RemoteSettings",
-                               "resource://services-settings/remote-settings.js");
-ChromeUtils.defineModuleGetter(this, "EveryWindow",
-                               "resource:///modules/EveryWindow.jsm");
-const {setTimeout, clearTimeout} = ChromeUtils.import("resource://gre/modules/Timer.jsm", {});
-Cu.importGlobalProperties(["fetch", "btoa"]);
-
-const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
--- a/browser/locales/en-US/browser/policies/policies-descriptions.ftl
+++ b/browser/locales/en-US/browser/policies/policies-descriptions.ftl
@@ -92,16 +92,18 @@ policy-FlashPlugin = Allow or deny usage
 
 policy-HardwareAcceleration = If false, turn off hardware acceleration.
 
 # “lock” means that the user won’t be able to change this setting
 policy-Homepage = Set and optionally lock the homepage.
 
 policy-InstallAddonsPermission = Allow certain websites to install add-ons.
 
+policy-LocalFileLinks = Allow specific websites to link to local files.
+
 policy-NetworkPrediction = Enable or disable network prediction (DNS prefetching).
 
 policy-NewTabPage = Enable or disable the New Tab page.
 
 policy-NoDefaultBookmarks = Disable creation of the default bookmarks bundled with { -brand-short-name }, and the Smart Bookmarks (Most Visited, Recent Tags). Note: this policy is only effective if used before the first run of the profile.
 
 policy-OfferToSaveLogins = Enforce the setting to allow { -brand-short-name } to offer to remember saved logins and passwords. Both true and false values are accepted.
 
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/browser/toolbarContextMenu.ftl
@@ -0,0 +1,22 @@
+# 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/.
+
+toolbar-context-menu-reload-selected-tab =
+    .label = Reload Selected Tab
+    .accesskey = R
+toolbar-context-menu-reload-selected-tabs =
+    .label = Reload Selected Tabs
+    .accesskey = R
+toolbar-context-menu-bookmark-selected-tab =
+    .label = Bookmark Selected Tab…
+    .accesskey = T
+toolbar-context-menu-bookmark-selected-tabs =
+    .label = Bookmark Selected Tabs…
+    .accesskey = T
+toolbar-context-menu-select-all-tabs =
+    .label = Select All Tabs
+    .accesskey = S
+toolbar-context-menu-undo-close-tab =
+    .label = Undo Close Tab
+    .accesskey = U
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -75,41 +75,16 @@ when there are no windows but Firefox is
 
 <!ENTITY menubarCmd.label "Menu Bar">
 <!ENTITY menubarCmd.accesskey "M">
 <!ENTITY navbarCmd.label "Navigation Toolbar">
 <!ENTITY personalbarCmd.label "Bookmarks Toolbar">
 <!ENTITY personalbarCmd.accesskey "B">
 <!ENTITY bookmarksToolbarItem.label "Bookmarks Toolbar Items">
 
-<!ENTITY toolbarContextMenu.reloadSelectedTab.label "Reload Selected Tab">
-<!-- LOCALIZATION NOTE (toolbarContextMenu.reloadSelectedTab.accesskey,
-toolbarContextMenu.reloadSelectedTabs.accesskey): These share the
-same accesskey but will never be visible at the same time. -->
-<!ENTITY toolbarContextMenu.reloadSelectedTab.accesskey "R">
-<!ENTITY toolbarContextMenu.reloadSelectedTabs.label "Reload Selected Tabs">
-<!-- LOCALIZATION NOTE (toolbarContextMenu.reloadSelectedTab.accesskey,
-toolbarContextMenu.reloadSelectedTabs.accesskey): These share the
-same accesskey but will never be visible at the same time. -->
-<!ENTITY toolbarContextMenu.reloadSelectedTabs.accesskey "R">
-<!ENTITY toolbarContextMenu.bookmarkSelectedTab.label "Bookmark Selected Tab…">
-<!-- LOCALIZATION NOTE (toolbarContextMenu.bookmarkSelectedTab.accesskey,
-toolbarContextMenu.bookmarkSelectedTabs.accesskey): These share the
-same accesskey but will never be visible at the same time. -->
-<!ENTITY toolbarContextMenu.bookmarkSelectedTab.accesskey "T">
-<!ENTITY toolbarContextMenu.bookmarkSelectedTabs.label "Bookmark Selected Tabs…">
-<!-- LOCALIZATION NOTE (toolbarContextMenu.bookmarkSelectedTab.accesskey,
-toolbarContextMenu.bookmarkSelectedTabs.accesskey): These share the
-same accesskey but will never be visible at the same time. -->
-<!ENTITY toolbarContextMenu.bookmarkSelectedTabs.accesskey "T">
-<!ENTITY toolbarContextMenu.selectAllTabs.label "Select All Tabs">
-<!ENTITY toolbarContextMenu.selectAllTabs.accesskey "S">
-<!ENTITY toolbarContextMenu.undoCloseTab.label "Undo Close Tab">
-<!ENTITY toolbarContextMenu.undoCloseTab.accesskey "U">
-
 <!ENTITY pageSourceCmd.label "Page Source">
 <!ENTITY pageSourceCmd.accesskey "o">
 <!ENTITY pageSourceCmd.commandkey "u">
 <!-- LOCALIZATION NOTE (pageSourceCmd.SafariCommandKey should match the
 Option+Command keyboard shortcut letter that Safari and Chrome use for "View
 Source" on macOS. pageSourceCmd.commandkey above is Firefox's official keyboard
 shortcut shown in the GUI. SafariCommandKey is an alias provided for the
 convenience of Safari and Chrome users on macOS. See bug 1398988. -->
--- a/build/build-clang/clang-win64.json
+++ b/build/build-clang/clang-win64.json
@@ -12,11 +12,12 @@
     "python_path": "c:/mozilla-build/python/python.exe",
     "cc": "cl.exe",
     "cxx": "cl.exe",
     "ml": "ml64.exe",
     "patches": [
       "workaround-issue38586.patch",
       "unpoison-thread-stacks.patch",
       "downgrade-mangling-error.patch",
-      "loosen-msvc-detection.patch"
+      "loosen-msvc-detection.patch",
+      "revert-r355311.patch"
     ]
 }
new file mode 100644
--- /dev/null
+++ b/build/build-clang/revert-r355311.patch
@@ -0,0 +1,117 @@
+We need to revert LLVM's r355311 because it breaks exception handling in
+Windows/AArch64 builds
+
+diff --git a/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp b/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp
+index ac5bdae9f1f..09e0706e284 100644
+--- a/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp
++++ b/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp
+@@ -605,28 +605,19 @@ void AArch64AsmPrinter::PrintDebugValueComment(const MachineInstr *MI,
+ 
+ void AArch64AsmPrinter::EmitJumpTableInfo() {
+   const MachineJumpTableInfo *MJTI = MF->getJumpTableInfo();
+   if (!MJTI) return;
+ 
+   const std::vector<MachineJumpTableEntry> &JT = MJTI->getJumpTables();
+   if (JT.empty()) return;
+ 
+-  const Function &F = MF->getFunction();
+   const TargetLoweringObjectFile &TLOF = getObjFileLowering();
+-  bool JTInDiffSection =
+-      !STI->isTargetCOFF() ||
+-      !TLOF.shouldPutJumpTableInFunctionSection(
+-          MJTI->getEntryKind() == MachineJumpTableInfo::EK_LabelDifference32,
+-          F);
+-  if (JTInDiffSection) {
+-      // Drop it in the readonly section.
+-      MCSection *ReadOnlySec = TLOF.getSectionForJumpTable(F, TM);
+-      OutStreamer->SwitchSection(ReadOnlySec);
+-  }
++  MCSection *ReadOnlySec = TLOF.getSectionForJumpTable(MF->getFunction(), TM);
++  OutStreamer->SwitchSection(ReadOnlySec);
+ 
+   auto AFI = MF->getInfo<AArch64FunctionInfo>();
+   for (unsigned JTI = 0, e = JT.size(); JTI != e; ++JTI) {
+     const std::vector<MachineBasicBlock*> &JTBBs = JT[JTI].MBBs;
+ 
+     // If this jump table was deleted, ignore it.
+     if (JTBBs.empty()) continue;
+ 
+diff --git a/llvm/lib/Target/AArch64/AArch64TargetMachine.cpp b/llvm/lib/Target/AArch64/AArch64TargetMachine.cpp
+index d657fd414f3..d32f5a0ab29 100644
+--- a/llvm/lib/Target/AArch64/AArch64TargetMachine.cpp
++++ b/llvm/lib/Target/AArch64/AArch64TargetMachine.cpp
+@@ -204,18 +204,18 @@ static std::string computeDataLayout(const Triple &TT,
+     return "e-m:w-p:64:64-i32:32-i64:64-i128:128-n32:64-S128";
+   if (LittleEndian)
+     return "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128";
+   return "E-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128";
+ }
+ 
+ static Reloc::Model getEffectiveRelocModel(const Triple &TT,
+                                            Optional<Reloc::Model> RM) {
+-  // AArch64 Darwin and Windows are always PIC.
+-  if (TT.isOSDarwin() || TT.isOSWindows())
++  // AArch64 Darwin is always PIC.
++  if (TT.isOSDarwin())
+     return Reloc::PIC_;
+   // On ELF platforms the default static relocation model has a smart enough
+   // linker to cope with referencing external symbols defined in a shared
+   // library. Hence DynamicNoPIC doesn't need to be promoted to PIC.
+   if (!RM.hasValue() || *RM == Reloc::DynamicNoPIC)
+     return Reloc::Static;
+   return *RM;
+ }
+diff --git a/llvm/test/CodeGen/AArch64/win64-jumptable.ll b/llvm/test/CodeGen/AArch64/win64-jumptable.ll
+deleted file mode 100644
+index 8148a593c91..00000000000
+--- a/llvm/test/CodeGen/AArch64/win64-jumptable.ll
++++ /dev/null
+@@ -1,48 +0,0 @@
+-; RUN: llc -o - %s -mtriple=aarch64-windows -aarch64-enable-compress-jump-tables=0 | FileCheck %s
+-
+-define void @f(i32 %x) {
+-entry:
+-  switch i32 %x, label %sw.epilog [
+-    i32 0, label %sw.bb
+-    i32 1, label %sw.bb1
+-    i32 2, label %sw.bb2
+-    i32 3, label %sw.bb3
+-  ]
+-
+-sw.bb:                                            ; preds = %entry
+-  tail call void @g(i32 0) #2
+-  br label %sw.epilog
+-
+-sw.bb1:                                           ; preds = %entry
+-  tail call void @g(i32 1) #2
+-  br label %sw.epilog
+-
+-sw.bb2:                                           ; preds = %entry
+-  tail call void @g(i32 2) #2
+-  br label %sw.epilog
+-
+-sw.bb3:                                           ; preds = %entry
+-  tail call void @g(i32 3) #2
+-  br label %sw.epilog
+-
+-sw.epilog:                                        ; preds = %entry, %sw.bb3, %sw.bb2, %sw.bb1, %sw.bb
+-  tail call void @g(i32 10) #2
+-  ret void
+-}
+-
+-declare void @g(i32)
+-
+-; CHECK:		.text
+-; CHECK:		f:
+-; CHECK:		.seh_proc f
+-; CHECK:		b	g
+-; CHECK-NEXT:	.p2align	2
+-; CHECK-NEXT:	.LJTI0_0:
+-; CHECK:		.word	.LBB0_2-.LJTI0_0
+-; CHECK:		.word	.LBB0_3-.LJTI0_0
+-; CHECK:		.word	.LBB0_4-.LJTI0_0
+-; CHECK:		.word	.LBB0_5-.LJTI0_0
+-; CHECK:		.section	.xdata,"dr"
+-; CHECK:		.seh_handlerdata
+-; CHECK:		.text
+-; CHECK:		.seh_endproc
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -2204,17 +2204,61 @@ add_gcc_flag(
     when=libstdcxx_version(
         'MOZ_LIBSTDCXX_TARGET_VERSION', cxx_compiler))
 add_gcc_flag(
     '-D_GLIBCXX_USE_CXX11_ABI=0', host_cxx_compiler,
     when=libstdcxx_version(
         'MOZ_LIBSTDCXX_HOST_VERSION', host_cxx_compiler))
 
 
+# Support various fuzzing options
+# ==============================================================
+js_option('--enable-fuzzing', help='Enable fuzzing support')
+
+@depends('--enable-fuzzing')
+def enable_fuzzing(value):
+    if value:
+        return True
+
+@depends(try_compile(body='__AFL_COMPILER;',
+                     check_msg='for AFL compiler',
+                     when='--enable-fuzzing'))
+def enable_aflfuzzer(afl):
+    if afl:
+        return True
+
+@depends(enable_fuzzing,
+         enable_aflfuzzer,
+         c_compiler,
+         target)
+def enable_libfuzzer(fuzzing, afl, c_compiler, target):
+    if fuzzing and not afl and c_compiler.type == 'clang' and target.os != 'Android':
+        return True
+
+@depends(enable_fuzzing,
+         enable_aflfuzzer,
+         enable_libfuzzer)
+def enable_fuzzing_interfaces(fuzzing, afl, libfuzzer):
+    if fuzzing and (afl or libfuzzer):
+        return True
+
+set_config('FUZZING', enable_fuzzing)
+set_define('FUZZING', enable_fuzzing)
+
+set_config('LIBFUZZER', enable_libfuzzer)
+set_define('LIBFUZZER', enable_libfuzzer)
+add_old_configure_assignment('LIBFUZZER', enable_libfuzzer)
+
+set_config('FUZZING_INTERFACES', enable_fuzzing_interfaces)
+set_define('FUZZING_INTERFACES', enable_fuzzing_interfaces)
+add_old_configure_assignment('FUZZING_INTERFACES', enable_fuzzing_interfaces)
+
+
 @depends(c_compiler.try_compile(flags=['-fsanitize=fuzzer-no-link'],
+         when=enable_fuzzing,
          check_msg='whether the C compiler supports -fsanitize=fuzzer-no-link'))
 def libfuzzer_flags(value):
     if value:
         no_link_flag_supported = True
         # recommended for (and only supported by) clang >= 6
         use_flags = ['-fsanitize=fuzzer-no-link']
     else:
         no_link_flag_supported = False
--- a/build/moz.configure/windows.configure
+++ b/build/moz.configure/windows.configure
@@ -158,22 +158,22 @@ def valid_windows_sdk_dir(compiler, wind
 
 
 @imports(_from='mozbuild.shellutil', _import='quote')
 def valid_ucrt_sdk_dir_result(value):
     if value:
         return '%s in %s' % (value.version, quote(value.path))
 
 
-@depends(windows_sdk_dir, 'WINDOWSSDKDIR', c_compiler)
+@depends(windows_sdk_dir, 'WINDOWSSDKDIR')
 @checking('for Universal CRT SDK', valid_ucrt_sdk_dir_result)
 @imports('os')
 @imports(_from='__builtin__', _import='sorted')
 @imports(_import='mozpack.path', _as='mozpath')
-def valid_ucrt_sdk_dir(windows_sdk_dir, windows_sdk_dir_env, c_compiler):
+def valid_ucrt_sdk_dir(windows_sdk_dir, windows_sdk_dir_env):
     if windows_sdk_dir_env:
         windows_sdk_dir_env = windows_sdk_dir_env[0]
     sdks = {}
     for d in windows_sdk_dir:
         sdk = get_sdk_dirs(d, 'ucrt')
         if sdk:
             version = os.path.basename(sdk.include)
             # We're supposed to always find a version in the directory, because
@@ -215,36 +215,24 @@ def valid_ucrt_sdk_dir(windows_sdk_dir, 
                 'CRT.' % windows_sdk_dir_env)
 
     valid_sdks = sorted(sdks, key=lambda x: sdks[x][0], reverse=True)
     if not valid_sdks:
         raise FatalCheckError('Cannot find the Universal CRT SDK. '
                               'Please install it.')
 
     version, sdk = sdks[valid_sdks[0]]
-    minimum_ucrt_version = Version('10.0.15063.0')
+    minimum_ucrt_version = Version('10.0.17134.0')
     if version < minimum_ucrt_version:
         raise FatalCheckError('Latest Universal CRT SDK version found %s'
                               ' and minimum required is %s. This or a later'
                               ' version can be installed using the Visual'
                               ' Studio installer.'
                               % (version, minimum_ucrt_version))
 
-    broken_ucrt_version = Version('10.0.16299.0')
-    working_ucrt_version = Version('10.0.17134.0')
-    if (c_compiler.type == 'clang-cl' and version >= broken_ucrt_version and
-            version < working_ucrt_version):
-        raise FatalCheckError('Found SDK version %s but clang-cl builds'
-                              ' currently don\'t work with the SDK version.'
-                              ' You should use a different version, either'
-                              ' by uninstalling version %s or setting a'
-                              ' custom WINDOWSSDKDIR.\n'
-                              'Note: Version %s now works with clang-cl.'
-                              % (version, version, working_ucrt_version))
-
     return namespace(
         path=sdk.path,
         include=sdk.include,
         lib=sdk.lib,
         version=version,
     )
 
 
--- a/devtools/client/aboutdebugging-new/test/browser/browser.ini
+++ b/devtools/client/aboutdebugging-new/test/browser/browser.ini
@@ -57,17 +57,17 @@ skip-if = (os == 'linux' && bits == 32) 
 [browser_aboutdebugging_devtools.js]
 [browser_aboutdebugging_devtoolstoolbox_contextmenu.js]
 [browser_aboutdebugging_devtoolstoolbox_contextmenu_markupview.js]
 [browser_aboutdebugging_devtoolstoolbox_focus.js]
 [browser_aboutdebugging_devtoolstoolbox_menubar.js]
 [browser_aboutdebugging_devtoolstoolbox_performance.js]
 skip-if = os == 'linux' && e10s && (asan || debug) # Same skip-if as old perf panel test suite. Bug 1254821
 [browser_aboutdebugging_devtoolstoolbox_reload.js]
-skip-if = verify # test loads the toolbox 2 times for each panel, might timeout or OOM
+skip-if = verify || ccov # test loads the toolbox 2 times for each panel, might timeout or OOM
 [browser_aboutdebugging_devtoolstoolbox_shortcuts.js]
 skip-if = (os == "win" && ccov) # Bug 1521349
 [browser_aboutdebugging_devtoolstoolbox_target_destroyed.js]
 skip-if = debug || asan # This test leaks. See bug 1529005
 [browser_aboutdebugging_devtoolstoolbox_tooltip_markupview.js]
 [browser_aboutdebugging_message_close.js]
 [browser_aboutdebugging_navigate.js]
 [browser_aboutdebugging_persist_connection.js]
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_temporary_reload_error.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_temporary_reload_error.js
@@ -8,42 +8,44 @@ Services.scriptloader.loadSubScript(CHRO
 // Test that the reload button updates the addon list with the correct metadata.
 add_task(async function() {
   const { document, tab, window } = await openAboutDebugging();
   await selectThisFirefoxPage(document, window.AboutDebugging.store);
 
   const EXTENSION_ID = "test-devtools@mozilla.org";
   const EXTENSION_NAME = "Temporary web extension";
 
-  const addonFile = await installTemporaryExtensionFromXPI({
+  let addonFile = await installTemporaryExtensionFromXPI({
     id: EXTENSION_ID,
     name: EXTENSION_NAME,
   }, document);
 
   const target = findDebugTargetByText(EXTENSION_NAME, document);
   ok(!!target, "The temporary extension is installed with the expected name");
 
   info("Update the name of the temporary extension in the manifest");
-  updateTemporaryXPI({}, addonFile);
+  addonFile = updateTemporaryXPI({ id: EXTENSION_ID }, addonFile);
 
   info("Click on the reload button for the invalid temporary extension");
+  const waitForError =
+    waitForDispatch(window.AboutDebugging.store, "TEMPORARY_EXTENSION_RELOAD_FAILURE");
   const reloadButton = target.querySelector(".js-temporary-extension-reload-button");
   reloadButton.click();
-
-  info("Wait until the error message appears");
-  await waitUntil(() => target.querySelector(".qa-temporary-extension-reload-error"));
-  ok(true, "The error message of reloading appears");
+  await waitForError;
+  ok(target.querySelector(".qa-temporary-extension-reload-error"),
+     "The error message of reloading appears");
 
   info("Click on the reload button for the valid temporary extension");
+  const waitForSuccess =
+    waitForDispatch(window.AboutDebugging.store, "TEMPORARY_EXTENSION_RELOAD_SUCCESS");
   updateTemporaryXPI({ id: EXTENSION_ID, name: EXTENSION_NAME }, addonFile);
   reloadButton.click();
-
-  info("Wait until the error message disappears");
-  await waitUntil(() => !target.querySelector(".qa-temporary-extension-reload-error"));
-  ok(true, "The error message of reloading disappears");
+  await waitForSuccess;
+  ok(!target.querySelector(".qa-temporary-extension-reload-error"),
+     "The error message of reloading disappears");
 
   info("Click on the remove button for the temporary extension");
   const removeButton = target.querySelector(".js-temporary-extension-remove-button");
   removeButton.click();
 
   info("Wait until the debug target with the extension disappears");
   await waitUntil(() => !findDebugTargetByText(EXTENSION_NAME, document));
 
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/bin/try-runner.js
@@ -0,0 +1,129 @@
+/* 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/>. */
+
+/*
+ * A small test runner/reporter for node-based tests,
+ * which are run via taskcluster node(debugger).
+ */
+
+const { execFileSync } = require("child_process");
+const { chdir } = require("process");
+const path = require("path");
+const flow = require("flow-bin");
+
+const dbgPath = path.join(__dirname, "..");
+
+function execOut(...args) {
+  let out;
+  let err;
+  try {
+    out = execFileSync(...args);
+  } catch (e) {
+    out = e.stdout;
+    err = e.stderr;
+  }
+  return { out: out.toString(), err: err && err.toString() };
+}
+
+function logErrors(text, regexp) {
+  const errors = text.match(regexp) || [];
+  for (const error of errors) {
+    console.log(`TEST-UNEXPECTED-FAIL | ${error}`);
+  }
+  return errors;
+}
+
+function logStart(name) {
+  console.log(`TEST START | ${name}`);
+}
+
+function runFlowJson() {
+  const { out } = execOut(flow, ["check", "--json"]);
+  const results = JSON.parse(out);
+
+  if (!results.passed) {
+    for (const error of results.errors) {
+      for (const message of error.message) {
+        console.log(`TEST-UNEXPECTED-FAIL | ${message.descr}`);
+      }
+    }
+  }
+
+  return results.passed;
+}
+
+function runFlow() {
+  logStart("Flow");
+  const { out } = execOut(flow, ["check"]);
+  console.log(out);
+  return runFlowJson();
+}
+
+function eslint() {
+  logStart("Eslint");
+  const { out } = execOut("yarn", ["lint:js"]);
+  console.log(out);
+  const errors = logErrors(out, / {2}error {2}(.*)/g);
+  return errors.length == 0;
+}
+
+function jest() {
+  logStart("Jest");
+  const { out, err } = execOut("yarn", ["test"]);
+  console.log(err);
+  const errors = logErrors(err || "", / {4}✕(.*)/g);
+  return errors.length == 0;
+}
+
+function stylelint() {
+  logStart("Stylelint");
+  const { out } = execOut("yarn", ["lint:css"]);
+  console.log(out);
+  const errors = logErrors(out, / {2}✖(.*)/g);
+  return errors.length == 0;
+}
+
+function jsxAccessibility() {
+  logStart("Eslint (JSX Accessibility)");
+
+  const { out } = execOut("yarn", ["lint:jsx-a11y"]);
+  console.log(out);
+  const errors = logErrors(out, / {2}error {2}(.*)/g);
+  return errors.length == 0;
+}
+
+function lintMd() {
+  logStart("Remark");
+
+  const { out, err } = execOut("yarn", ["lint:md"]);
+  const errors = logErrors(err || "", /warning(.+)/g);
+  return errors.length == 0;
+}
+
+chdir(dbgPath);
+const flowPassed = runFlow();
+const eslintPassed = eslint();
+const jestPassed = jest();
+const styleLintPassed = stylelint();
+const jsxAccessibilityPassed = jsxAccessibility();
+const remarkPassed = lintMd();
+
+const success =
+  flowPassed &&
+  eslintPassed &&
+  jestPassed &&
+  styleLintPassed &&
+  jsxAccessibilityPassed &&
+  remarkPassed;
+
+console.log({
+  flowPassed,
+  eslintPassed,
+  jestPassed,
+  styleLintPassed,
+  jsxAccessibilityPassed,
+  remarkPassed
+});
+
+process.exitCode = success ? 0 : 1;
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -3176,19 +3176,23 @@ Toolbox.prototype = {
     menu.popup(x, y, { doc: this.doc });
   },
 
   /**
    * Connects to the Gecko Profiler when the developer tools are open. This is
    * necessary because of the WebConsole's `profile` and `profileEnd` methods.
    */
   async initPerformance() {
-    // If target does not have performance actor (addons), do not
-    // even register the shared performance connection.
-    if (!this.target.hasActor("performance")) {
+    // If:
+    // - target does not have performance actor (addons)
+    // - or client uses the new performance panel (incompatible with console.profile())
+    // do not even register the shared performance connection.
+    const isNewPerfPanel =
+      Services.prefs.getBoolPref("devtools.performance.new-panel-enabled", false);
+    if (isNewPerfPanel || !this.target.hasActor("performance")) {
       return promise.resolve();
     }
 
     const performanceFront = await this.target.getFront("performance");
     performanceFront.once(
       "console-profile-start",
       () => this._onPerformanceFrontEvent(performanceFront)
     );
--- a/devtools/client/themes/computed.css
+++ b/devtools/client/themes/computed.css
@@ -6,16 +6,23 @@
 #sidebar-panel-computedview {
   margin: 0;
   display: flex;
   flex-direction: column;
   width: 100%;
   height: 100%;
 }
 
+/* Reset the global rotation of the icon done for RTL layout.
+   Computed view is always LTR */
+#sidebar-panel-computedview .theme-twisty:not(.open):dir(rtl),
+#sidebar-panel-computedview .theme-twisty:not([open]):-moz-locale-dir(rtl) {
+  transform: rotate(-90deg);
+}
+
 #computed-container {
   overflow: auto;
   height: 100%;
 }
 
 /* This extra wrapper only serves as a way to get the content of the view focusable.
    So that when the user reaches it either via keyboard or mouse, we know that the view
    is focused and therefore can handle shortcuts.
--- a/devtools/client/themes/inspector.css
+++ b/devtools/client/themes/inspector.css
@@ -64,16 +64,21 @@ window {
   background-image: url("chrome://devtools/skin/images/close-3-pane.svg");
   transform: unset;
 }
 
 #inspector-splitter-box .sidebar-toggle.pane-collapsed::before {
   background-image: url("chrome://devtools/skin/images/open-3-pane.svg");
 }
 
+/* Flip the icon horizontally when in RTL mode */
+#inspector-splitter-box .sidebar-toggle:dir(rtl) {
+  transform: scaleX(-1);
+}
+
 .devtools-toolbar {
   background-color: var(--theme-body-background);
   display: flex;
 }
 
 .devtools-toolbar .devtools-searchbox {
   border: 1px solid transparent;
 }
--- a/devtools/client/themes/markup.css
+++ b/devtools/client/themes/markup.css
@@ -35,16 +35,23 @@ body {
 /* Force height and width (possibly overflowing) from inline elements.
  * This allows long overflows of text or input fields to still be styled with
  * the container, rather than the background disappearing when scrolling */
 #root {
   float: left;
   min-width: 100%;
 }
 
+/* Reset the global rotation of the icon done for RTL layout.
+   Markup view is always LTR */
+#root .theme-twisty:not(.open):dir(rtl),
+#root .theme-twisty:not([open]):-moz-locale-dir(rtl) {
+  transform: rotate(-90deg);
+}
+
 /* Don't display a parent-child outline for the root elements */
 #root > ul > li > .children {
   background: none;
 }
 
 html.dragging {
   overflow-x: hidden;
 }
--- a/devtools/server/actors/utils/actor-registry.js
+++ b/devtools/server/actors/utils/actor-registry.js
@@ -1,15 +1,14 @@
 /* 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 Services = require("Services");
 var { Ci } = require("chrome");
 var gRegisteredModules = Object.create(null);
 
 const ActorRegistry = {
   // Map of global actor names to actor constructors.
   globalActorFactories: {},
   // Map of target-scoped actor names to actor constructors.
   targetScopedActorFactories: {},
@@ -188,18 +187,17 @@ const ActorRegistry = {
       constructor: "ReflowActor",
       type: { target: true },
     });
     this.registerModule("devtools/server/actors/css-properties", {
       prefix: "cssProperties",
       constructor: "CssPropertiesActor",
       type: { target: true },
     });
-    if ("nsIProfiler" in Ci &&
-        !Services.prefs.getBoolPref("devtools.performance.new-panel-enabled", false)) {
+    if ("nsIProfiler" in Ci) {
       this.registerModule("devtools/server/actors/performance", {
         prefix: "performance",
         constructor: "PerformanceActor",
         type: { target: true },
       });
     }
     this.registerModule("devtools/server/actors/animation", {
       prefix: "animations",
--- a/docshell/base/moz.build
+++ b/docshell/base/moz.build
@@ -116,13 +116,10 @@ LOCAL_INCLUDES += [
     '/layout/xul',
     '/netwerk/base',
     '/netwerk/protocol/viewsource',
     '/toolkit/components/browser',
     '/toolkit/components/find',
     '/tools/profiler',
 ]
 
-if CONFIG['MOZ_TOOLKIT_SEARCH']:
-    DEFINES['MOZ_TOOLKIT_SEARCH'] = True
-
 if CONFIG['CC_TYPE'] in ('clang', 'gcc'):
     CXXFLAGS += ['-Wno-error=shadow']
--- a/docshell/base/nsDefaultURIFixup.cpp
+++ b/docshell/base/nsDefaultURIFixup.cpp
@@ -6,20 +6,17 @@
 
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
 #include "nsIProtocolHandler.h"
 
 #include "nsIFile.h"
 #include <algorithm>
 
-#ifdef MOZ_TOOLKIT_SEARCH
-#  include "nsISearchService.h"
-#endif
-
+#include "nsISearchService.h"
 #include "nsIURIFixup.h"
 #include "nsIURIMutator.h"
 #include "nsDefaultURIFixup.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/ipc/IPCStreamUtils.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "mozilla/TextUtils.h"
@@ -424,17 +421,16 @@ nsDefaultURIFixup::KeywordToURI(const ns
       postData.forget(aPostData);
     }
 
     nsCOMPtr<nsIURI> temp = DeserializeURI(uri);
     info->mPreferredURI = temp.forget();
     return NS_OK;
   }
 
-#ifdef MOZ_TOOLKIT_SEARCH
   // Try falling back to the search service's default search engine
   nsCOMPtr<nsISearchService> searchSvc =
       do_GetService("@mozilla.org/browser/search-service;1");
   if (searchSvc) {
     nsCOMPtr<nsISearchEngine> defaultEngine;
     searchSvc->GetDefaultEngine(getter_AddRefs(defaultEngine));
     if (defaultEngine) {
       nsCOMPtr<nsISearchSubmission> submission;
@@ -469,17 +465,16 @@ nsDefaultURIFixup::KeywordToURI(const ns
         }
 
         defaultEngine->GetName(info->mKeywordProviderName);
         info->mKeywordAsSent = keywordW;
         return submission->GetUri(getter_AddRefs(info->mPreferredURI));
       }
     }
   }
-#endif
 
   // out of options
   return NS_ERROR_NOT_AVAILABLE;
 }
 
 // Helper to deal with passing around uri fixup stuff
 nsresult nsDefaultURIFixup::TryKeywordFixupForURIInfo(
     const nsACString& aURIString, nsDefaultURIFixupInfo* aFixupInfo,
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -178,16 +178,17 @@
 #include "nsDOMCID.h"
 #include "nsDOMNavigationTiming.h"
 #include "nsDSURIContentListener.h"
 #include "nsEditingSession.h"
 #include "nsError.h"
 #include "nsEscape.h"
 #include "nsFocusManager.h"
 #include "nsGlobalWindow.h"
+#include "nsISearchService.h"
 #include "nsJSEnvironment.h"
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
 #include "nsObjectLoadingContent.h"
 #include "nsPingListener.h"
 #include "nsPoint.h"
 #include "nsQueryObject.h"
 #include "nsRect.h"
@@ -217,20 +218,16 @@
 #  include "mozIPlacesPendingOperation.h"
 #endif
 
 #if NS_PRINT_PREVIEW
 #  include "nsIDocumentViewerPrint.h"
 #  include "nsIWebBrowserPrint.h"
 #endif
 
-#ifdef MOZ_TOOLKIT_SEARCH
-#  include "nsISearchService.h"
-#endif
-
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::net;
 
 // Threshold value in ms for META refresh based redirects
 #define REFRESH_REDIRECT_TIMER 15000
 
 // Hint for native dispatch of events on how long to delay after
@@ -13227,32 +13224,30 @@ void nsDocShell::MaybeNotifyKeywordSearc
   if (XRE_IsContentProcess()) {
     dom::ContentChild* contentChild = dom::ContentChild::GetSingleton();
     if (contentChild) {
       contentChild->SendNotifyKeywordSearchLoading(aProvider, aKeyword);
     }
     return;
   }
 
-#ifdef MOZ_TOOLKIT_SEARCH
   nsCOMPtr<nsISearchService> searchSvc =
       do_GetService("@mozilla.org/browser/search-service;1");
   if (searchSvc) {
     nsCOMPtr<nsISearchEngine> searchEngine;
     searchSvc->GetEngineByName(aProvider, getter_AddRefs(searchEngine));
     if (searchEngine) {
       nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
       if (obsSvc) {
         // Note that "keyword-search" refers to a search via the url
         // bar, not a bookmarks keyword search.
         obsSvc->NotifyObservers(searchEngine, "keyword-search", aKeyword.get());
       }
     }
   }
-#endif
 }
 
 NS_IMETHODIMP
 nsDocShell::ShouldPrepareForIntercept(nsIURI* aURI, nsIChannel* aChannel,
                                       bool* aShouldIntercept) {
   return mInterceptController->ShouldPrepareForIntercept(aURI, aChannel,
                                                          aShouldIntercept);
 }
--- a/docshell/test/browser/browser.ini
+++ b/docshell/test/browser/browser.ini
@@ -53,16 +53,17 @@ support-files =
   browser_timelineMarkers-frame-04.js
   browser_timelineMarkers-frame-05.js
   head.js
   frame-head.js
   file_data_load_inherit_csp.html
   file_click_link_within_view_source.html
   onload_message.html
   onpageshow_message.html
+  file_cross_process_csp_inheritance.html
 
 [browser_bug1206879.js]
 [browser_bug1309900_crossProcessHistoryNavigation.js]
 [browser_bug1328501.js]
 [browser_bug1347823.js]
 [browser_bug134911.js]
 [browser_bug1415918_beforeunload_options.js]
 [browser_bug234628-1.js]
@@ -119,8 +120,10 @@ skip-if = true # Bug 1220415
 [browser_browsingContext-01.js]
 [browser_browsingContext-02.js]
 [browser_browsingContext-03.js]
 [browser_browsingContext-embedder.js]
 [browser_csp_uir.js]
 support-files =
   file_csp_uir.html
   file_csp_uir_dummy.html
+[browser_cross_process_csp_inheritance.js]
+skip-if = !e10s # e10s specific test.
new file mode 100644
--- /dev/null
+++ b/docshell/test/browser/browser_cross_process_csp_inheritance.js
@@ -0,0 +1,77 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "http://example.com");
+const TEST_URI = TEST_PATH + "file_cross_process_csp_inheritance.html";
+const DATA_URI = "data:text/html,<html>test-same-diff-process-csp-inhertiance</html>";
+
+function getCurrentPID(aBrowser) {
+  return ContentTask.spawn(aBrowser, null, () => {
+    return Services.appinfo.processID;
+  });
+}
+
+function getCurrentURI(aBrowser) {
+  return ContentTask.spawn(aBrowser, null, () => {
+    let channel = content.docShell.currentDocumentChannel;
+    return channel.URI.asciiSpec;
+  });
+}
+
+function verifyResult(aTestName, aBrowser, aDataURI, aPID, aSamePID) {
+  return ContentTask.spawn(aBrowser, {aTestName, aDataURI, aPID, aSamePID}, async function({aTestName, aDataURI, aPID, aSamePID}) {
+    // sanity, to make sure the correct URI was loaded
+    let channel = content.docShell.currentDocumentChannel;
+    is(channel.URI.asciiSpec, aDataURI, aTestName + ": correct data uri loaded");
+
+    // check that the process ID is the same/different when opening the new tab
+    let pid = Services.appinfo.processID;
+    if (aSamePID) {
+      is(pid, aPID, aTestName + ": process ID needs to be identical");
+    } else {
+      isnot(pid, aPID, aTestName + ": process ID needs to be different");
+    }
+
+    // finally, evaluate that the CSP was set.
+    let principal = channel.loadInfo.triggeringPrincipal;
+    let cspOBJ = JSON.parse(principal.cspJSON);
+    let policies = cspOBJ["csp-policies"];
+    is(policies.length, 1, "should be one policy");
+    let policy = policies[0];
+    is(policy["script-src"], "'none'", aTestName + ": script-src directive matches");
+  });
+}
+
+async function simulateCspInheritanceForNewTab(aTestName, aSamePID) {
+  await BrowserTestUtils.withNewTab(TEST_URI, async function(browser) {
+    // do some sanity checks
+    let currentURI = await getCurrentURI(gBrowser.selectedBrowser);
+    is(currentURI, TEST_URI, aTestName + ": correct test uri loaded");
+
+    let pid = await getCurrentPID(gBrowser.selectedBrowser);
+    let loadPromise = BrowserTestUtils.waitForNewTab(gBrowser, DATA_URI);
+    // simulate click
+    BrowserTestUtils.synthesizeMouseAtCenter("#testLink", {},
+                                             gBrowser.selectedBrowser);
+    let tab = await loadPromise;
+    gBrowser.selectTabAtIndex(2);
+    await verifyResult(aTestName, gBrowser.selectedBrowser, DATA_URI, pid, aSamePID);
+    await BrowserTestUtils.removeTab(tab);
+  });
+}
+
+add_task(async function test_csp_inheritance_diff_process() {
+  // forcing the new data: URI load to happen in a *new* process by flipping the pref
+  // to force <a rel="noopener" ...> to be loaded in a new process.
+  await SpecialPowers.pushPrefEnv({"set": [["dom.noopener.newprocess.enabled", true]]});
+  await simulateCspInheritanceForNewTab("diff-process-inheritance", false);
+});
+
+add_task(async function test_csp_inheritance_same_process() {
+  // forcing the new data: URI load to happen in a *same* process by resetting the pref
+  // and loaded <a rel="noopener" ...> in the *same* process.
+  await SpecialPowers.pushPrefEnv({"set": [["dom.noopener.newprocess.enabled", false]]});
+  await simulateCspInheritanceForNewTab("same-process-inheritance", true);
+});
new file mode 100644
--- /dev/null
+++ b/docshell/test/browser/file_cross_process_csp_inheritance.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Test CSP inheritance if load happens in same and different process</title>
+ <meta http-equiv="Content-Security-Policy" content="script-src 'none'">
+</head>
+<body>
+  <a href="data:text/html,<html>test-same-diff-process-csp-inhertiance</html>" id="testLink" target="_blank" rel="noopener">click to test same/diff process CSP inheritance</a>
+</body>
+</html>
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -604,27 +604,24 @@ JSObject* Element::WrapObject(JSContext*
       return nullptr;
     }
 
     if (bindingURL) {
       nsCOMPtr<nsIURI> uri = bindingURL->GetURI();
       nsCOMPtr<nsIPrincipal> principal = bindingURL->ExtraData()->Principal();
 
       // We have a binding that must be installed.
-      bool dummy;
-
       nsXBLService* xblService = nsXBLService::GetInstance();
       if (!xblService) {
         dom::Throw(aCx, NS_ERROR_NOT_AVAILABLE);
         return nullptr;
       }
 
       RefPtr<nsXBLBinding> binding;
-      xblService->LoadBindings(this, uri, principal, getter_AddRefs(binding),
-                               &dummy);
+      xblService->LoadBindings(this, uri, principal, getter_AddRefs(binding));
 
       if (binding) {
         if (nsContentUtils::IsSafeToRunScript()) {
           binding->ExecuteAttachedHandler();
         } else {
           nsContentUtils::AddScriptRunner(
               NewRunnableMethod("nsXBLBinding::ExecuteAttachedHandler", binding,
                                 &nsXBLBinding::ExecuteAttachedHandler));
--- a/dom/fetch/FetchDriver.cpp
+++ b/dom/fetch/FetchDriver.cpp
@@ -1041,17 +1041,19 @@ FetchDriver::OnStartRequest(nsIRequest* 
   // interception case.
   mRequest->MaybeIncreaseResponseTainting(loadInfo->GetTainting());
 
   // Resolves fetch() promise which may trigger code running in a worker.  Make
   // sure the Response is fully initialized before calling this.
   mResponse = BeginAndGetFilteredResponse(response, foundOpaqueRedirect);
   if (NS_WARN_IF(!mResponse)) {
     // Fail to generate a paddingInfo for opaque response.
-    MOZ_DIAGNOSTIC_ASSERT(mResponse->Type() == ResponseType::Opaque);
+    MOZ_DIAGNOSTIC_ASSERT(mRequest->GetResponseTainting() ==
+                              LoadTainting::Opaque &&
+                          !foundOpaqueRedirect);
     FailWithNetworkError(NS_ERROR_UNEXPECTED);
     return NS_ERROR_UNEXPECTED;
   }
 
   // From "Main Fetch" step 19: SRI-part1.
   if (ShouldCheckSRI(mRequest, mResponse) && mSRIMetadata.IsEmpty()) {
     nsIConsoleReportCollector* reporter = nullptr;
     if (mObserver) {
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -2621,21 +2621,20 @@ nsresult HTMLMediaElement::LoadWithChann
 
 bool HTMLMediaElement::Seeking() const {
   return mDecoder && mDecoder->IsSeeking();
 }
 
 double HTMLMediaElement::CurrentTime() const {
   if (MediaStream* stream = GetSrcMediaStream()) {
     MediaStreamGraph* graph = stream->Graph();
-    GraphTime currentTime =
-        mSrcStreamPausedGraphTime == GRAPH_TIME_MAX
-            ? graph->CurrentTime() - mSrcStreamGraphTimeOffset
-            : mSrcStreamPausedGraphTime;
-    return stream->StreamTimeToSeconds(currentTime);
+    GraphTime currentGraphTime =
+        mSrcStreamPausedGraphTime.valueOr(graph->CurrentTime());
+    StreamTime currentStreamTime = currentGraphTime - mSrcStreamGraphTimeOffset;
+    return stream->StreamTimeToSeconds(currentStreamTime);
   }
 
   if (mDefaultPlaybackStartPosition == 0.0 && mDecoder) {
     return mDecoder->GetCurrentTime();
   }
 
   return mDefaultPlaybackStartPosition;
 }
@@ -4661,19 +4660,19 @@ void HTMLMediaElement::UpdateSrcMediaStr
   mSrcStreamIsPlaying = shouldPlay;
 
   LOG(LogLevel::Debug,
       ("MediaElement %p %s playback of DOMMediaStream %p", this,
        shouldPlay ? "Setting up" : "Removing", mSrcStream.get()));
 
   if (shouldPlay) {
     mSrcStreamPlaybackEnded = false;
-    mSrcStreamGraphTimeOffset =
-        graph->CurrentTime() - mSrcStreamPausedGraphTime;
-    mSrcStreamPausedGraphTime = GRAPH_TIME_MAX;
+    mSrcStreamGraphTimeOffset +=
+        graph->CurrentTime() - mSrcStreamPausedGraphTime.ref();
+    mSrcStreamPausedGraphTime = Nothing();
 
     mWatchManager.Watch(graph->CurrentTime(),
                         &HTMLMediaElement::UpdateSrcStreamTime);
 
     stream->AddAudioOutput(this);
     SetVolumeInternal();
     if (mSink.second()) {
       NS_WARNING(
@@ -4688,18 +4687,18 @@ void HTMLMediaElement::UpdateSrcMediaStr
     }
 
     SetCapturedOutputStreamsEnabled(true);  // Unmute
     // If the input is a media stream, we don't check its data and always regard
     // it as audible when it's playing.
     SetAudibleState(true);
   } else {
     if (stream) {
-      mSrcStreamPausedGraphTime =
-          graph->CurrentTime() - mSrcStreamGraphTimeOffset;
+      MOZ_DIAGNOSTIC_ASSERT(mSrcStreamPausedGraphTime.isNothing());
+      mSrcStreamPausedGraphTime = Some(graph->CurrentTime().Ref());
 
       mWatchManager.Unwatch(graph->CurrentTime(),
                             &HTMLMediaElement::UpdateSrcStreamTime);
 
       stream->RemoveAudioOutput(this);
       VideoFrameContainer* container = GetVideoFrameContainer();
       if (mSelectedVideoStreamTrack && container) {
         mSelectedVideoStreamTrack->RemoveVideoOutput(container);
@@ -4729,21 +4728,22 @@ void HTMLMediaElement::SetupSrcMediaStre
 
   mSrcStream = aStream;
 
   nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
   if (!window) {
     return;
   }
 
-  mSrcStreamPausedGraphTime = 0;
+  mSrcStreamPausedGraphTime = Some(0);
   if (MediaStream* stream = GetSrcMediaStream()) {
     if (MediaStreamGraph* graph = stream->Graph()) {
       // The current graph time will represent 0 for this media element.
-      mSrcStreamPausedGraphTime = graph->CurrentTime();
+      mSrcStreamGraphTimeOffset = graph->CurrentTime();
+      mSrcStreamPausedGraphTime = Some(mSrcStreamGraphTimeOffset);
     }
   }
 
   UpdateSrcMediaStreamPlaying();
 
   // If we pause this media element, track changes in the underlying stream
   // will continue to fire events at this element and alter its track list.
   // That's simpler than delaying the events, but probably confusing...
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -1309,20 +1309,20 @@ class HTMLMediaElement : public nsGeneri
   // actually playing.
   // At most one of mDecoder and mSrcStream can be non-null.
   RefPtr<DOMMediaStream> mSrcStream;
 
   // True once mSrcStream's initial set of tracks are known.
   bool mSrcStreamTracksAvailable = false;
 
   // While mPaused is true and mSrcStream is set, this is the value to use for
-  // CurrentTime(). Otherwise this is set to GRAPH_TIME_MAX.
-  GraphTime mSrcStreamPausedGraphTime = GRAPH_TIME_MAX;
+  // CurrentTime(). Otherwise this is Nothing.
+  Maybe<GraphTime> mSrcStreamPausedGraphTime;
 
-  // The offset in GraphTime that this media element started playing the
+  // The offset in GraphTime at which this media element started playing the
   // playback stream of mSrcStream.
   GraphTime mSrcStreamGraphTimeOffset = 0;
 
   // True once PlaybackEnded() is called and we're playing a MediaStream.
   // Reset to false if we start playing mSrcStream again.
   bool mSrcStreamPlaybackEnded = false;
 
   // Holds a reference to the stream connecting this stream to the capture sink.
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -11591,21 +11591,23 @@ ConnectionPool::IdleConnectionRunnable::
 
   if (owningThread) {
     mDatabaseInfo->AssertIsOnConnectionThread();
 
     // The connection could be null if EnsureConnection() didn't run or was not
     // successful in TransactionDatabaseOperationBase::RunOnConnectionThread().
     if (mDatabaseInfo->mConnection) {
       mDatabaseInfo->mConnection->DoIdleProcessing(mNeedsCheckpoint);
-
-      MOZ_ALWAYS_SUCCEEDS(owningThread->Dispatch(this, NS_DISPATCH_NORMAL));
-      return NS_OK;
-    }
-  }
+    }
+
+    MOZ_ALWAYS_SUCCEEDS(owningThread->Dispatch(this, NS_DISPATCH_NORMAL));
+    return NS_OK;
+  }
+
+  AssertIsOnBackgroundThread();
 
   RefPtr<ConnectionPool> connectionPool = mDatabaseInfo->mConnectionPool;
   MOZ_ASSERT(connectionPool);
 
   if (mDatabaseInfo->mClosing || mDatabaseInfo->TotalTransactionCount()) {
     MOZ_ASSERT(!connectionPool->mDatabasesPerformingIdleMaintenance.Contains(
         mDatabaseInfo));
   } else {
--- a/dom/ipc/BrowserParent.cpp
+++ b/dom/ipc/BrowserParent.cpp
@@ -1827,17 +1827,17 @@ mozilla::ipc::IPCResult BrowserParent::R
 
 mozilla::ipc::IPCResult BrowserParent::RecvNotifyIMEFocus(
     const ContentCache& aContentCache, const IMENotification& aIMENotification,
     NotifyIMEFocusResolver&& aResolve) {
   if (mIsDestroyed) {
     return IPC_OK();
   }
 
-  nsCOMPtr<nsIWidget> widget = GetDocWidget();
+  nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
     aResolve(IMENotificationRequests());
     return IPC_OK();
   }
 
   mContentCache.AssignContent(aContentCache, widget, &aIMENotification);
   IMEStateManager::NotifyIME(aIMENotification, widget, this);
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -158,16 +158,17 @@
 #include "nsIMozBrowserFrame.h"
 #include "nsIMutable.h"
 #include "nsIObserverService.h"
 #include "nsIParentChannel.h"
 #include "nsIPresShell.h"
 #include "nsIRemoteWindowContext.h"
 #include "nsIScriptError.h"
 #include "nsIScriptSecurityManager.h"
+#include "nsISearchService.h"
 #include "nsIServiceWorkerManager.h"
 #include "nsISiteSecurityService.h"
 #include "nsISound.h"
 #include "mozilla/mozSpellChecker.h"
 #include "nsIStringBundle.h"
 #include "nsISupportsPrimitives.h"
 #include "nsITimer.h"
 #include "nsIURIFixup.h"
@@ -269,20 +270,16 @@
 #    include "mozilla/SandboxBroker.h"
 #    include "mozilla/SandboxBrokerPolicyFactory.h"
 #  endif
 #  if defined(XP_MACOSX)
 #    include "mozilla/Sandbox.h"
 #  endif
 #endif
 
-#ifdef MOZ_TOOLKIT_SEARCH
-#  include "nsISearchService.h"
-#endif
-
 #ifdef XP_WIN
 #  include "mozilla/audio/AudioNotificationSender.h"
 #  include "mozilla/widget/AudioSession.h"
 #endif
 
 #ifdef ACCESSIBILITY
 #  include "nsAccessibilityService.h"
 #endif
@@ -4197,33 +4194,31 @@ mozilla::ipc::IPCResult ContentParent::R
   nsCOMPtr<nsIURI> uri;
   info->GetPreferredURI(getter_AddRefs(uri));
   SerializeURI(uri, *aURI);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult ContentParent::RecvNotifyKeywordSearchLoading(
     const nsString& aProvider, const nsString& aKeyword) {
-#ifdef MOZ_TOOLKIT_SEARCH
   nsCOMPtr<nsISearchService> searchSvc =
       do_GetService("@mozilla.org/browser/search-service;1");
   if (searchSvc) {
     nsCOMPtr<nsISearchEngine> searchEngine;
     searchSvc->GetEngineByName(aProvider, getter_AddRefs(searchEngine));
     if (searchEngine) {
       nsCOMPtr<nsIObserverService> obsSvc =
           mozilla::services::GetObserverService();
       if (obsSvc) {
         // Note that "keyword-search" refers to a search via the url
         // bar, not a bookmarks keyword search.
         obsSvc->NotifyObservers(searchEngine, "keyword-search", aKeyword.get());
       }
     }
   }
-#endif
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult ContentParent::RecvCopyFavicon(
     const URIParams& aOldURI, const URIParams& aNewURI,
     const IPC::Principal& aLoadingPrincipal, const bool& aInPrivateBrowsing) {
   nsCOMPtr<nsIURI> oldURI = DeserializeURI(aOldURI);
   if (!oldURI) {
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -191,19 +191,16 @@ if CONFIG['OS_ARCH'] != 'WINNT':
         '/modules/libjar',
     ]
 
 DEFINES['BIN_SUFFIX'] = '"%s"' % CONFIG['BIN_SUFFIX']
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
     DEFINES['MOZ_ENABLE_FREETYPE'] = True
 
-if CONFIG['MOZ_TOOLKIT_SEARCH']:
-    DEFINES['MOZ_TOOLKIT_SEARCH'] = True
-
 JAR_MANIFESTS += ['jar.mn']
 
 BROWSER_CHROME_MANIFESTS += ['tests/browser.ini']
 MOCHITEST_CHROME_MANIFESTS += ['tests/chrome.ini']
 MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
 XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell.ini']
 
 CXXFLAGS += CONFIG['TK_CFLAGS']
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -243,17 +243,20 @@ void MediaDecoder::SetOutputStreamCORSMo
   mDecoderStateMachine->SetOutputStreamCORSMode(aCORSMode);
 }
 
 void MediaDecoder::AddOutputStream(DOMMediaStream* aStream) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mDecoderStateMachine, "Must be called after Load().");
   AbstractThread::AutoEnter context(AbstractMainThread());
   mDecoderStateMachine->EnsureOutputStreamManager(
-      aStream->GetInputStream()->Graph(), ToMaybe(mInfo.get()));
+      aStream->GetInputStream()->Graph());
+  if (mInfo) {
+    mDecoderStateMachine->EnsureOutputStreamManagerHasTracks(*mInfo);
+  }
   mDecoderStateMachine->AddOutputStream(aStream);
 }
 
 void MediaDecoder::RemoveOutputStream(DOMMediaStream* aStream) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mDecoderStateMachine, "Must be called after Load().");
   AbstractThread::AutoEnter context(AbstractMainThread());
   mDecoderStateMachine->RemoveOutputStream(aStream);
@@ -651,16 +654,17 @@ void MediaDecoder::MetadataLoaded(
       aInfo->mAudio.mChannels, aInfo->mAudio.mRate, aInfo->HasAudio(),
       aInfo->HasVideo());
 
   mMediaSeekable = aInfo->mMediaSeekable;
   mMediaSeekableOnlyInBufferedRanges =
       aInfo->mMediaSeekableOnlyInBufferedRanges;
   mInfo = aInfo.release();
   GetOwner()->ConstructMediaTracks(mInfo);
+  mDecoderStateMachine->EnsureOutputStreamManagerHasTracks(*mInfo);
 
   // Make sure the element and the frame (if any) are told about
   // our new size.
   if (aEventVisibility != MediaDecoderEventVisibility::Suppressed) {
     mFiredMetadataLoaded = true;
     GetOwner()->MetadataLoaded(mInfo, std::move(aTags));
   }
   // Invalidate() will end up calling GetOwner()->UpdateMediaSize with the last
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -3333,18 +3333,18 @@ void MediaDecoderStateMachine::FinishDec
   mReader->ReadUpdatedMetadata(mInfo.ptr());
 
   EnqueueFirstFrameLoadedEvent();
 }
 
 RefPtr<ShutdownPromise> MediaDecoderStateMachine::BeginShutdown() {
   MOZ_ASSERT(NS_IsMainThread());
   if (mOutputStreamManager) {
+    mOutputStreamManager->Disconnect();
     mNextOutputStreamTrackID = mOutputStreamManager->NextTrackID();
-    mOutputStreamManager->Disconnect();
   }
   return InvokeAsync(OwnerThread(), this, __func__,
                      &MediaDecoderStateMachine::Shutdown);
 }
 
 RefPtr<ShutdownPromise> MediaDecoderStateMachine::FinishShutdown() {
   MOZ_ASSERT(OnTaskQueue());
   LOG("Shutting down state machine task queue");
@@ -3785,38 +3785,49 @@ void MediaDecoderStateMachine::RemoveOut
         });
     nsresult rv = OwnerThread()->Dispatch(r.forget());
     MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
     Unused << rv;
   }
 }
 
 void MediaDecoderStateMachine::EnsureOutputStreamManager(
-    MediaStreamGraph* aGraph, const Maybe<MediaInfo>& aLoadedInfo) {
+    MediaStreamGraph* aGraph) {
   MOZ_ASSERT(NS_IsMainThread());
   if (mOutputStreamManager) {
     return;
   }
-  LOG("Starting output track allocations at id %d", mNextOutputStreamTrackID);
   mOutputStreamManager = new OutputStreamManager(
       aGraph->CreateSourceStream(), mNextOutputStreamTrackID,
       mOutputStreamPrincipal, mOutputStreamCORSMode, mAbstractMainThread);
-  if (!aLoadedInfo) {
+}
+
+void MediaDecoderStateMachine::EnsureOutputStreamManagerHasTracks(
+    const MediaInfo& aLoadedInfo) {
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!mOutputStreamManager) {
     return;
   }
-  TrackID mirroredTrackIDAllocation = mNextOutputStreamTrackID;
-  if (aLoadedInfo->HasAudio()) {
-    mOutputStreamManager->AddTrack(mirroredTrackIDAllocation++,
-                                   MediaSegment::AUDIO);
-    LOG("Pre-created audio track with id %d", mirroredTrackIDAllocation - 1);
+  if ((!aLoadedInfo.HasAudio() ||
+       mOutputStreamManager->HasTrackType(MediaSegment::AUDIO)) &&
+      (!aLoadedInfo.HasVideo() ||
+       mOutputStreamManager->HasTrackType(MediaSegment::VIDEO))) {
+    return;
   }
-  if (aLoadedInfo->HasVideo()) {
-    mOutputStreamManager->AddTrack(mirroredTrackIDAllocation++,
-                                   MediaSegment::VIDEO);
-    LOG("Pre-created video track with id %d", mirroredTrackIDAllocation - 1);
+  if (aLoadedInfo.HasAudio()) {
+    MOZ_ASSERT(!mOutputStreamManager->HasTrackType(MediaSegment::AUDIO));
+    mOutputStreamManager->AddTrack(MediaSegment::AUDIO);
+    LOG("Pre-created audio track with id %d",
+        mOutputStreamManager->GetLiveTrackIDFor(MediaSegment::AUDIO));
+  }
+  if (aLoadedInfo.HasVideo()) {
+    MOZ_ASSERT(!mOutputStreamManager->HasTrackType(MediaSegment::VIDEO));
+    mOutputStreamManager->AddTrack(MediaSegment::VIDEO);
+    LOG("Pre-created video track with id %d",
+        mOutputStreamManager->GetLiveTrackIDFor(MediaSegment::VIDEO));
   }
 }
 
 void MediaDecoderStateMachine::SetNextOutputStreamTrackID(
     TrackID aNextTrackID) {
   MOZ_ASSERT(NS_IsMainThread());
   LOG("SetNextOutputStreamTrackID aNextTrackID=%d", aNextTrackID);
   mNextOutputStreamTrackID = aNextTrackID;
@@ -3917,17 +3928,17 @@ void MediaDecoderStateMachine::CancelSus
   }
   mVideoDecodeSuspendTimer.Reset();
 }
 
 void MediaDecoderStateMachine::AdjustByLooping(media::TimeUnit& aTime) const {
   MOZ_ASSERT(OnTaskQueue());
   if (mAudioDecodedDuration.isSome() &&
       mAudioDecodedDuration.ref().IsPositive()) {
-    aTime = aTime % mAudioDecodedDuration.ref().ToMicroseconds();
+    aTime = aTime % mAudioDecodedDuration.ref();
   }
 }
 
 }  // namespace mozilla
 
 // avoid redefined macro in unified build
 #undef LOG
 #undef LOGV
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -181,21 +181,21 @@ class MediaDecoderStateMachine
 
   // Returns the state machine task queue.
   TaskQueue* OwnerThread() const { return mTaskQueue; }
 
   RefPtr<MediaDecoder::DebugInfoPromise> RequestDebugInfo();
 
   void SetOutputStreamPrincipal(const nsCOMPtr<nsIPrincipal>& aPrincipal);
   void SetOutputStreamCORSMode(CORSMode aCORSMode);
-  // If an OutputStreamManager does not exist, one will be created and tracks
-  // matching aLoadedInfo will be created ahead of being created by the
-  // DecodedStream sink.
-  void EnsureOutputStreamManager(MediaStreamGraph* aGraph,
-                                 const Maybe<MediaInfo>& aLoadedInfo);
+  // If an OutputStreamManager does not exist, one will be created.
+  void EnsureOutputStreamManager(MediaStreamGraph* aGraph);
+  // If an OutputStreamManager exists, tracks matching aLoadedInfo will be
+  // created unless they already exist in the manager.
+  void EnsureOutputStreamManagerHasTracks(const MediaInfo& aLoadedInfo);
   // Add an output stream to the output stream manager. The manager must have
   // been created through EnsureOutputStreamManager() before this.
   void AddOutputStream(DOMMediaStream* aStream);
   // Remove an output stream added with AddOutputStream. If the last output
   // stream was removed, we will also tear down the OutputStreamManager.
   void RemoveOutputStream(DOMMediaStream* aStream);
   // Set the TrackID to be used as the initial id by the next DecodedStream
   // sink.
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -162,17 +162,18 @@ void MediaStreamGraphImpl::UpdateCurrent
           stream->GraphTimeToStreamTime(mStateComputedTime);
       if (track->IsEnded() && track->GetEnd() <= streamCurrentTime) {
         if (!track->NotifiedEnded()) {
           // Playout of this track ended and listeners have not been notified.
           track->NotifyEnded();
           for (const TrackBound<MediaStreamTrackListener>& listener :
                stream->mTrackListeners) {
             if (listener.mTrackID == track->GetID()) {
-              listener.mListener->NotifyOutput(this, track->GetEnd());
+              listener.mListener->NotifyOutput(
+                  this, track->GetEnd() - track->GetStart());
               listener.mListener->NotifyEnded();
             }
           }
         }
       } else {
         for (const TrackBound<MediaStreamTrackListener>& listener :
              stream->mTrackListeners) {
           if (listener.mTrackID == track->GetID()) {
@@ -1841,17 +1842,16 @@ void MediaStreamGraphImpl::Dispatch(alre
 }
 
 MediaStream::MediaStream()
     : mTracksStartTime(0),
       mStartBlocking(GRAPH_TIME_MAX),
       mSuspendedCount(0),
       mFinished(false),
       mNotifiedFinished(false),
-      mHasCurrentData(false),
       mMainThreadCurrentTime(0),
       mMainThreadFinished(false),
       mFinishedNotificationSent(false),
       mMainThreadDestroyed(false),
       mNrOfMainThreadUsers(0),
       mGraph(nullptr) {
   MOZ_COUNT_CTOR(MediaStream);
 }
@@ -2506,17 +2506,18 @@ bool SourceMediaStream::PullNewData(Grap
       // (which the track will get as its start time later).
       current = GraphTimeToStreamTime(GraphImpl()->mStateComputedTime);
     } else {
       current = track.mEndOfFlushedData + track.mData->GetDuration();
     }
     if (t <= current) {
       continue;
     }
-    if (!track.mPullingEnabled) {
+    if (!track.mPullingEnabled &&
+        track.mData->GetType() == MediaSegment::AUDIO) {
       if (streamPullingEnabled) {
         LOG(LogLevel::Verbose,
             ("%p: Pulling disabled for track but enabled for stream, append "
              "null data; stream=%p track=%d t=%f current end=%f",
              GraphImpl(), this, track.mID, GraphImpl()->MediaTimeToSeconds(t),
              GraphImpl()->MediaTimeToSeconds(current)));
         track.mData->AppendNullData(t - current);
       }
@@ -2531,16 +2532,77 @@ bool SourceMediaStream::PullNewData(Grap
       if (l.mTrackID == track.mID) {
         l.mListener->NotifyPull(Graph(), current, t);
       }
     }
   }
   return true;
 }
 
+/**
+ * This moves chunks from aIn to aOut. For audio this is simple. For video
+ * we carry durations over if present, or extend up to aDesiredUpToTime if not.
+ *
+ * We also handle "resetters" from captured media elements. This type of source
+ * pushes future frames into the track, and should it need to remove some, e.g.,
+ * because of a seek or pause, it tells us by letting time go backwards. Without
+ * this, tracks would be live for too long after a seek or pause.
+ */
+static void MoveToSegment(SourceMediaStream* aStream, MediaSegment* aIn,
+                          MediaSegment* aOut, StreamTime aCurrentTime,
+                          StreamTime aDesiredUpToTime) {
+  MOZ_ASSERT(aIn->GetType() == aOut->GetType());
+  MOZ_ASSERT(aOut->GetDuration() >= aCurrentTime);
+  if (aIn->GetType() == MediaSegment::AUDIO) {
+    aOut->AppendFrom(aIn);
+  } else {
+    VideoSegment* in = static_cast<VideoSegment*>(aIn);
+    VideoSegment* out = static_cast<VideoSegment*>(aOut);
+    for (VideoSegment::ConstChunkIterator c(*in); !c.IsEnded(); c.Next()) {
+      MOZ_ASSERT(!c->mTimeStamp.IsNull());
+      VideoChunk* last = out->GetLastChunk();
+      if (!last || last->mTimeStamp.IsNull()) {
+        // This is the first frame, or the last frame pushed to `out` has been
+        // all consumed. Just append and we deal with its duration later.
+        out->AppendFrame(do_AddRef(c->mFrame.GetImage()),
+                         c->mFrame.GetIntrinsicSize(),
+                         c->mFrame.GetPrincipalHandle(),
+                         c->mFrame.GetForceBlack(), c->mTimeStamp);
+        if (c->GetDuration() > 0) {
+          out->ExtendLastFrameBy(c->GetDuration());
+        }
+        continue;
+      }
+
+      // We now know when this frame starts, aka when the last frame ends.
+
+      if (c->mTimeStamp < last->mTimeStamp) {
+        // Time is going backwards. This is a resetting frame from
+        // DecodedStream. Clear everything up to currentTime.
+        out->Clear();
+        out->AppendNullData(aCurrentTime);
+      }
+
+      // Append the current frame (will have duration 0).
+      out->AppendFrame(do_AddRef(c->mFrame.GetImage()),
+                       c->mFrame.GetIntrinsicSize(),
+                       c->mFrame.GetPrincipalHandle(),
+                       c->mFrame.GetForceBlack(), c->mTimeStamp);
+      if (c->GetDuration() > 0) {
+        out->ExtendLastFrameBy(c->GetDuration());
+      }
+    }
+    if (out->GetDuration() < aDesiredUpToTime) {
+      out->ExtendLastFrameBy(aDesiredUpToTime - out->GetDuration());
+    }
+    in->Clear();
+  }
+  MOZ_ASSERT(aIn->GetDuration() == 0, "aIn must be consumed");
+}
+
 void SourceMediaStream::ExtractPendingInput(GraphTime aCurrentTime,
                                             GraphTime aDesiredUpToTime) {
   MutexAutoLock lock(mMutex);
 
   bool finished = mFinishPending;
   StreamTime streamCurrentTime = GraphTimeToStreamTime(aCurrentTime);
   StreamTime streamDesiredUpToTime = GraphTimeToStreamTime(aDesiredUpToTime);
 
@@ -2558,60 +2620,47 @@ void SourceMediaStream::ExtractPendingIn
 
     for (TrackBound<MediaStreamTrackListener>& b : mTrackListeners) {
       if (b.mTrackID != data->mID) {
         continue;
       }
       b.mListener->NotifyQueuedChanges(GraphImpl(), offset, *data->mData);
     }
     if (data->mCommands & SourceMediaStream::TRACK_CREATE) {
-      MediaSegment* segment = data->mData.forget();
+      MediaSegment* segment = data->mData->CreateEmptyClone();
       LOG(LogLevel::Debug,
           ("%p: SourceMediaStream %p creating track %d, start %" PRId64
            ", initial end %" PRId64,
            GraphImpl(), this, data->mID, int64_t(streamCurrentTime),
            int64_t(segment->GetDuration())));
 
-      segment->InsertNullDataAtStart(streamCurrentTime);
+      segment->AppendNullData(streamCurrentTime);
+      MoveToSegment(this, data->mData, segment, streamCurrentTime,
+                    streamDesiredUpToTime);
       data->mEndOfFlushedData += segment->GetDuration();
       mTracks.AddTrack(data->mID, streamCurrentTime, segment);
-      // The track has taken ownership of data->mData, so let's replace
-      // data->mData with an empty clone.
-      data->mData = segment->CreateEmptyClone();
       data->mCommands &= ~SourceMediaStream::TRACK_CREATE;
     } else {
-      MediaSegment* dest = mTracks.FindTrack(data->mID)->GetSegment();
+      StreamTracks::Track* track = mTracks.FindTrack(data->mID);
+      MediaSegment* dest = track->GetSegment();
       LOG(LogLevel::Verbose,
           ("%p: SourceMediaStream %p track %d, advancing end from %" PRId64
            " to %" PRId64,
            GraphImpl(), this, data->mID, int64_t(dest->GetDuration()),
            int64_t(dest->GetDuration() + data->mData->GetDuration())));
       data->mEndOfFlushedData += data->mData->GetDuration();
-      dest->AppendFrom(data->mData);
+      MoveToSegment(this, data->mData, dest, streamCurrentTime,
+                    streamDesiredUpToTime);
     }
     if (data->mCommands & SourceMediaStream::TRACK_END) {
       mTracks.FindTrack(data->mID)->SetEnded();
       mUpdateTracks.RemoveElementAt(i);
-    } else if (!data->mPullingEnabled &&
-               data->mData->GetType() == MediaSegment::VIDEO) {
-      // This video track is pushed. Since we use timestamps rather than
-      // durations for video we avoid making the video track block the stream
-      // by extending the duration when there's not enough video data, so a
-      // video track always has valid data.
-      VideoSegment* segment = static_cast<VideoSegment*>(
-          mTracks.FindTrack(data->mID)->GetSegment());
-      StreamTime missingTime = streamDesiredUpToTime - segment->GetDuration();
-      segment->ExtendLastFrameBy(missingTime);
     }
   }
 
-  if (mTracks.GetEarliestTrackEnd() > 0) {
-    mHasCurrentData = true;
-  }
-
   if (finished) {
     FinishOnGraphThread();
   }
 }
 
 void SourceMediaStream::AddTrackInternal(TrackID aID, TrackRate aRate,
                                          MediaSegment* aSegment,
                                          uint32_t aFlags) {
@@ -2774,54 +2823,49 @@ void SourceMediaStream::AddDirectTrackLi
   LOG(LogLevel::Debug,
       ("%p: Added direct track listener %p", GraphImpl(), listener.get()));
   listener->NotifyDirectListenerInstalled(
       DirectMediaStreamTrackListener::InstallationResult::SUCCESS);
 
   // Pass buffered data to the listener
   VideoSegment bufferedData;
   size_t videoFrames = 0;
-  // For video we append all non-null chunks, as we're only interested in
-  // real frames and their timestamps.
   VideoSegment& trackSegment = static_cast<VideoSegment&>(*track->GetSegment());
   for (VideoSegment::ConstChunkIterator iter(trackSegment); !iter.IsEnded();
        iter.Next()) {
-    if (iter->IsNull()) {
+    if (iter->mTimeStamp.IsNull()) {
+      // No timestamp means this is only for the graph's internal book-keeping,
+      // denoting a late start of the track.
       continue;
     }
     ++videoFrames;
-    MOZ_ASSERT(!iter->mTimeStamp.IsNull());
     bufferedData.AppendFrame(do_AddRef(iter->mFrame.GetImage()),
                              iter->mFrame.GetIntrinsicSize(),
                              iter->mFrame.GetPrincipalHandle(),
                              iter->mFrame.GetForceBlack(), iter->mTimeStamp);
   }
 
   if (TrackData* updateData = FindDataForTrack(aTrackID)) {
     VideoSegment& video = static_cast<VideoSegment&>(*updateData->mData);
     for (VideoSegment::ConstChunkIterator iter(video); !iter.IsEnded();
          iter.Next()) {
-      if (iter->IsNull()) {
-        continue;
-      }
       ++videoFrames;
+      MOZ_ASSERT(!iter->mTimeStamp.IsNull());
       bufferedData.AppendFrame(do_AddRef(iter->mFrame.GetImage()),
                                iter->mFrame.GetIntrinsicSize(),
                                iter->mFrame.GetPrincipalHandle(),
                                iter->mFrame.GetForceBlack(), iter->mTimeStamp);
     }
   }
 
   LOG(LogLevel::Info,
       ("%p: Notifying direct listener %p of %zu video frames and duration "
        "%" PRId64,
        GraphImpl(), listener.get(), videoFrames, bufferedData.GetDuration()));
-  if (!bufferedData.IsNull()) {
-    listener->NotifyRealtimeTrackData(Graph(), 0, bufferedData);
-  }
+  listener->NotifyRealtimeTrackData(Graph(), 0, bufferedData);
 }
 
 void SourceMediaStream::RemoveDirectTrackListenerImpl(
     DirectMediaStreamTrackListener* aListener, TrackID aTrackID) {
   MutexAutoLock lock(mMutex);
   for (int32_t i = mDirectTrackListeners.Length() - 1; i >= 0; --i) {
     const TrackBound<DirectMediaStreamTrackListener>& source =
         mDirectTrackListeners[i];
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -485,18 +485,16 @@ class MediaStream : public mozilla::Link
    * having its blocking time calculated in UpdateGraph and its blocking time
    * taken account of in UpdateCurrentTimeForStreams.
    */
   GraphTime StreamTimeToGraphTime(StreamTime aTime) const;
 
   bool IsFinishedOnGraphThread() const { return mFinished; }
   virtual void FinishOnGraphThread();
 
-  bool HasCurrentData() const { return mHasCurrentData; }
-
   /**
    * Find track by track id.
    */
   StreamTracks::Track* FindTrack(TrackID aID) const;
 
   StreamTracks::Track* EnsureTrack(TrackID aTrack);
 
   virtual void ApplyTrackDisabling(TrackID aTrackID, MediaSegment* aSegment,
@@ -605,23 +603,16 @@ class MediaStream : public mozilla::Link
    * Only accessed on the graph thread
    */
   bool mFinished;
   /**
    * When true, mFinished is true and we've played all the data in this stream
    * and fired NotifyFinished notifications.
    */
   bool mNotifiedFinished;
-  /**
-   * True if some data can be present by this stream if/when it's unblocked.
-   * Set by the stream itself on the MediaStreamGraph thread. Only changes
-   * from false to true once a stream has data, since we won't
-   * unblock it until there's more data.
-   */
-  bool mHasCurrentData;
 
   // Main-thread views of state
   StreamTime mMainThreadCurrentTime;
   bool mMainThreadFinished;
   bool mFinishedNotificationSent;
   bool mMainThreadDestroyed;
   int mNrOfMainThreadUsers;
 
--- a/dom/media/SeekTarget.h
+++ b/dom/media/SeekTarget.h
@@ -23,33 +23,37 @@ struct SeekTarget {
     Accurate,
     NextFrame,
   };
   SeekTarget()
       : mTime(media::TimeUnit::Invalid()),
         mType(SeekTarget::Invalid),
         mVideoOnly(false) {}
   SeekTarget(const media::TimeUnit& aTime, Type aType, bool aVideoOnly = false)
-      : mTime(aTime), mType(aType), mVideoOnly(aVideoOnly) {}
+      : mTime(aTime), mType(aType), mVideoOnly(aVideoOnly) {
+    MOZ_ASSERT(mTime.IsValid());
+  }
   SeekTarget(const SeekTarget& aOther)
       : mTime(aOther.mTime),
         mType(aOther.mType),
-        mVideoOnly(aOther.mVideoOnly) {}
+        mVideoOnly(aOther.mVideoOnly) {
+    MOZ_ASSERT(mTime.IsValid());
+  }
   bool IsValid() const { return mType != SeekTarget::Invalid; }
   void Reset() {
     mTime = media::TimeUnit::Invalid();
     mType = SeekTarget::Invalid;
     mVideoOnly = false;
   }
   media::TimeUnit GetTime() const {
-    NS_ASSERTION(mTime.IsValid(), "Invalid SeekTarget");
+    MOZ_ASSERT(mTime.IsValid(), "Invalid SeekTarget");
     return mTime;
   }
   void SetTime(const media::TimeUnit& aTime) {
-    NS_ASSERTION(aTime.IsValid(), "Invalid SeekTarget destination");
+    MOZ_ASSERT(aTime.IsValid(), "Invalid SeekTarget destination");
     mTime = aTime;
   }
   void SetType(Type aType) { mType = aType; }
   void SetVideoOnly(bool aVideoOnly) { mVideoOnly = aVideoOnly; }
   bool IsFast() const { return mType == SeekTarget::Type::PrevSyncPoint; }
   bool IsAccurate() const { return mType == SeekTarget::Type::Accurate; }
   bool IsNextFrame() const { return mType == SeekTarget::Type::NextFrame; }
   bool IsVideoOnly() const { return mVideoOnly; }
--- a/dom/media/TrackUnionStream.cpp
+++ b/dom/media/TrackUnionStream.cpp
@@ -76,28 +76,24 @@ void TrackUnionStream::ProcessInput(Grap
     mappedTracksFinished.AppendElement(true);
     mappedTracksWithMatchingInputTracks.AppendElement(false);
   }
 
   AutoTArray<MediaInputPort*, 32> inputs(mInputs);
   inputs.AppendElements(mSuspendedInputs);
 
   bool allFinished = !inputs.IsEmpty();
-  bool allHaveCurrentData = !inputs.IsEmpty();
   for (uint32_t i = 0; i < inputs.Length(); ++i) {
     MediaStream* stream = inputs[i]->GetSource();
     if (!stream->IsFinishedOnGraphThread()) {
       // XXX we really should check whether 'stream' has finished within time
       // aTo, not just that it's finishing when all its queued data eventually
       // runs out.
       allFinished = false;
     }
-    if (!stream->HasCurrentData()) {
-      allHaveCurrentData = false;
-    }
     for (StreamTracks::TrackIter tracks(stream->GetStreamTracks());
          !tracks.IsEnded(); tracks.Next()) {
       bool found = false;
       for (uint32_t j = 0; j < mTrackMap.Length(); ++j) {
         TrackMapEntry* map = &mTrackMap[j];
         if (map->mInputPort == inputs[i] &&
             map->mInputTrackID == tracks->GetID()) {
           bool trackFinished = false;
@@ -139,20 +135,16 @@ void TrackUnionStream::ProcessInput(Grap
     }
   }
   if (allFinished && mAutofinish && (aFlags & ALLOW_FINISH)) {
     // All streams have finished and won't add any more tracks, and
     // all our tracks have actually finished and been removed from our map,
     // so we're finished now.
     FinishOnGraphThread();
   }
-  if (allHaveCurrentData) {
-    // We can make progress if we're not blocked
-    mHasCurrentData = true;
-  }
 }
 
 uint32_t TrackUnionStream::AddTrack(MediaInputPort* aPort,
                                     StreamTracks::Track* aTrack,
                                     GraphTime aFrom) {
   STREAM_LOG(LogLevel::Verbose,
              ("TrackUnionStream %p adding track %d for "
               "input stream %p track %d, desired id %d",
--- a/dom/media/VideoStreamTrack.cpp
+++ b/dom/media/VideoStreamTrack.cpp
@@ -30,18 +30,16 @@ static bool SetImageToBlackPixel(PlanarY
   return aImage->CopyData(data);
 }
 
 class VideoOutput : public DirectMediaStreamTrackListener {
  protected:
   virtual ~VideoOutput() = default;
 
   void DropPastFrames() {
-    mMutex.AssertCurrentThreadOwns();
-
     TimeStamp now = TimeStamp::Now();
     size_t nrChunksInPast = 0;
     for (const auto& idChunkPair : mFrames) {
       const VideoChunk& chunk = idChunkPair.second();
       if (chunk.mTimeStamp > now) {
         break;
       }
       ++nrChunksInPast;
@@ -49,19 +47,22 @@ class VideoOutput : public DirectMediaSt
     if (nrChunksInPast > 1) {
       // We need to keep one frame that starts in the past, because it only ends
       // when the next frame starts (which also needs to be in the past for it
       // to drop).
       mFrames.RemoveElementsAt(0, nrChunksInPast - 1);
     }
   }
 
-  void SendFrames() {
+  void SendFramesEnsureLocked() {
     mMutex.AssertCurrentThreadOwns();
+    SendFrames();
+  }
 
+  void SendFrames() {
     DropPastFrames();
 
     if (mFrames.IsEmpty()) {
       return;
     }
 
     // Collect any new frames produced in this iteration.
     AutoTArray<ImageContainer::NonOwningImage, 16> images;
@@ -138,38 +139,59 @@ class VideoOutput : public DirectMediaSt
         // it seeks, as the previously buffered frames would stretch into the
         // future. If this happens, we clear the buffered frames and start over.
         mFrames.ClearAndRetainStorage();
       }
       mFrames.AppendElement(MakePair(mVideoFrameContainer->NewFrameID(), *i));
       mLastFrameTime = i->mTimeStamp;
     }
 
-    SendFrames();
+    SendFramesEnsureLocked();
   }
   void NotifyRemoved() override {
     // Doesn't need locking by mMutex, since the direct listener is removed from
     // the track before we get notified.
+    if (mFrames.Length() <= 1) {
+      // The compositor has already received the last frame.
+      mFrames.ClearAndRetainStorage();
+      mVideoFrameContainer->ClearFutureFrames();
+      return;
+    }
+
+    // The compositor has multiple frames. ClearFutureFrames() would only retain
+    // the first as that's normally the current one. We however stop doing
+    // SetCurrentFrames() once we've received the last frame in a track, so
+    // there might be old frames lingering. We'll find the current one and
+    // re-send that.
+    DropPastFrames();
+    mFrames.RemoveElementsAt(1, mFrames.Length() - 1);
+    SendFrames();
     mFrames.ClearAndRetainStorage();
-    mVideoFrameContainer->ClearFutureFrames();
   }
   void NotifyEnded() override {
     // Doesn't need locking by mMutex, since for the track to end, it must have
     // been ended by the source, meaning that the source won't append more data.
+    if (mFrames.IsEmpty()) {
+      return;
+    }
+
+    // Re-send only the last one to the compositor.
+    mFrames.RemoveElementsAt(0, mFrames.Length() - 1);
+    SendFrames();
     mFrames.ClearAndRetainStorage();
   }
   void NotifyEnabledStateChanged(bool aEnabled) override {
     MutexAutoLock lock(mMutex);
     mEnabled = aEnabled;
     // Since mEnabled will affect whether frames are real, or black, we assign
     // new FrameIDs whenever this changes.
     for (auto& idChunkPair : mFrames) {
       idChunkPair.first() = mVideoFrameContainer->NewFrameID();
     }
-    SendFrames();
+    SendFramesEnsureLocked();
   }
 
   Mutex mMutex;
   TimeStamp mLastFrameTime;
   // Once the frame is forced to black, we initialize mBlackImage for use in any
   // following forced-black frames.
   RefPtr<Image> mBlackImage;
   bool mEnabled = true;
--- a/dom/media/ipc/RDDProcessHost.cpp
+++ b/dom/media/ipc/RDDProcessHost.cpp
@@ -159,17 +159,24 @@ void RDDProcessHost::InitAfterConnect(bo
 
 #if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
     // If the sandbox was started at launch time,
     // do not start the sandbox again.
     startMacSandbox = !sLaunchWithMacSandbox;
 #endif
 
     if (!mRDDChild->Init(startMacSandbox)) {
-      KillHard("ActorInitFailed");
+      // Can't just kill here because it will create a timing race that
+      // will crash the tab. We don't really want to crash the tab just
+      // because RDD linux sandbox failed to initialize.  In this case,
+      // we'll close the child channel which will cause the RDD process
+      // to shutdown nicely avoiding the tab crash (which manifests as
+      // Bug 1535335).
+      mRDDChild->Close();
+      return;
     }
   }
 
   if (mListener) {
     mListener->OnProcessLaunchComplete(this);
   }
 }
 
--- a/dom/media/mediasink/DecodedStream.cpp
+++ b/dom/media/mediasink/DecodedStream.cpp
@@ -44,68 +44,128 @@ class DecodedStreamTrackListener : publi
 
   void NotifyOutput(MediaStreamGraph* aGraph,
                     StreamTime aCurrentTrackTime) override;
   void NotifyEnded() override;
 
  private:
   const RefPtr<DecodedStreamGraphListener> mGraphListener;
   const RefPtr<SourceMediaStream> mStream;
-  const mozilla::TrackID mTrackID;
+  const TrackID mTrackID;
 };
 
 class DecodedStreamGraphListener {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodedStreamGraphListener)
  public:
   DecodedStreamGraphListener(
       SourceMediaStream* aStream, TrackID aAudioTrackID,
       MozPromiseHolder<DecodedStream::EndedPromise>&& aAudioEndedHolder,
       TrackID aVideoTrackID,
       MozPromiseHolder<DecodedStream::EndedPromise>&& aVideoEndedHolder,
       AbstractThread* aMainThread)
       : mAudioTrackListener(IsTrackIDExplicit(aAudioTrackID)
                                 ? MakeRefPtr<DecodedStreamTrackListener>(
                                       this, aStream, aAudioTrackID)
                                 : nullptr),
-        mAudioTrackID(aAudioTrackID),
         mAudioEndedHolder(std::move(aAudioEndedHolder)),
         mVideoTrackListener(IsTrackIDExplicit(aVideoTrackID)
                                 ? MakeRefPtr<DecodedStreamTrackListener>(
                                       this, aStream, aVideoTrackID)
                                 : nullptr),
-        mVideoTrackID(aVideoTrackID),
         mVideoEndedHolder(std::move(aVideoEndedHolder)),
         mStream(aStream),
+        mAudioTrackID(aAudioTrackID),
+        mVideoTrackID(aVideoTrackID),
         mAbstractMainThread(aMainThread) {
     MOZ_ASSERT(NS_IsMainThread());
     if (mAudioTrackListener) {
       mStream->AddTrackListener(mAudioTrackListener, mAudioTrackID);
     } else {
+      mAudioEnded = true;
       mAudioEndedHolder.ResolveIfExists(true, __func__);
     }
 
     if (mVideoTrackListener) {
       mStream->AddTrackListener(mVideoTrackListener, mVideoTrackID);
     } else {
+      mVideoEnded = true;
       mVideoEndedHolder.ResolveIfExists(true, __func__);
     }
   }
 
   void NotifyOutput(TrackID aTrackID, StreamTime aCurrentTrackTime) {
-    if (aTrackID != mAudioTrackID && mAudioTrackID != TRACK_NONE) {
-      // Only audio playout drives the clock forward, if present.
+    if (aTrackID == mAudioTrackID) {
+      if (aCurrentTrackTime >= mAudioEnd) {
+        mStream->EndTrack(mAudioTrackID);
+      }
+    } else if (aTrackID == mVideoTrackID) {
+      if (aCurrentTrackTime >= mVideoEnd) {
+        mStream->EndTrack(mVideoTrackID);
+      }
+    } else {
+      MOZ_CRASH("Unexpected TrackID");
+    }
+    if (aTrackID != mAudioTrackID && mAudioTrackID != TRACK_NONE &&
+        !mAudioEnded) {
+      // Only audio playout drives the clock forward, if present and live.
       return;
     }
+    MOZ_ASSERT_IF(aTrackID == mAudioTrackID, !mAudioEnded);
+    MOZ_ASSERT_IF(aTrackID == mVideoTrackID, !mVideoEnded);
     mOnOutput.Notify(mStream->StreamTimeToMicroseconds(aCurrentTrackTime));
   }
 
+  void NotifyEnded(TrackID aTrackID) {
+    if (aTrackID == mAudioTrackID) {
+      mAudioEnded = true;
+    } else if (aTrackID == mVideoTrackID) {
+      mVideoEnded = true;
+    } else {
+      MOZ_CRASH("Unexpected TrackID");
+    }
+    mStream->Graph()->DispatchToMainThreadStableState(
+        NewRunnableMethod<TrackID>(
+            "DecodedStreamGraphListener::DoNotifyTrackEnded", this,
+            &DecodedStreamGraphListener::DoNotifyTrackEnded, aTrackID));
+  }
+
   TrackID AudioTrackID() const { return mAudioTrackID; }
 
   TrackID VideoTrackID() const { return mVideoTrackID; }
 
+  /**
+   * Tell the graph listener to end the given track after it has seen at least
+   * aEnd worth of output reported as processed by the graph.
+   *
+   * A StreamTime of STREAM_TIME_MAX indicates that the track has no end and is
+   * the default.
+   *
+   * This method of ending tracks is needed because the MediaStreamGraph
+   * processes ended tracks (through SourceMediaStream::EndTrack) at the
+   * beginning of an iteration, but waits until the end of the iteration to
+   * process any ControlMessages. When such a ControlMessage is a listener that
+   * is to be added to a track that has ended in its very first iteration, the
+   * track ends before the listener tracking this ending is added. This can lead
+   * to a MediaStreamTrack ending on main thread (it uses another listener)
+   * before the listeners to render the track get added, potentially meaning a
+   * media element doesn't progress before reaching the end although data was
+   * available.
+   *
+   * Callable from any thread.
+   */
+  void EndTrackAt(TrackID aTrackID, StreamTime aEnd) {
+    if (aTrackID == mAudioTrackID) {
+      mAudioEnd = aEnd;
+    } else if (aTrackID == mVideoTrackID) {
+      mVideoEnd = aEnd;
+    } else {
+      MOZ_CRASH("Unexpected TrackID");
+    }
+  }
+
   void DoNotifyTrackEnded(TrackID aTrackID) {
     MOZ_ASSERT(NS_IsMainThread());
     if (aTrackID == mAudioTrackID) {
       mAudioEndedHolder.ResolveIfExists(true, __func__);
     } else if (aTrackID == mVideoTrackID) {
       mVideoEndedHolder.ResolveIfExists(true, __func__);
     } else {
       MOZ_CRASH("Unexpected track id");
@@ -137,41 +197,45 @@ class DecodedStreamGraphListener {
     MOZ_ASSERT(mAudioEndedHolder.IsEmpty());
     MOZ_ASSERT(mVideoEndedHolder.IsEmpty());
   }
 
   MediaEventProducer<int64_t> mOnOutput;
 
   // Main thread only.
   RefPtr<DecodedStreamTrackListener> mAudioTrackListener;
-  const TrackID mAudioTrackID;
   MozPromiseHolder<DecodedStream::EndedPromise> mAudioEndedHolder;
   RefPtr<DecodedStreamTrackListener> mVideoTrackListener;
-  const TrackID mVideoTrackID;
   MozPromiseHolder<DecodedStream::EndedPromise> mVideoEndedHolder;
 
+  // Graph thread only.
+  bool mAudioEnded = false;
+  bool mVideoEnded = false;
+
+  // Any thread.
   const RefPtr<SourceMediaStream> mStream;
+  const TrackID mAudioTrackID;
+  Atomic<StreamTime> mAudioEnd{STREAM_TIME_MAX};
+  const TrackID mVideoTrackID;
+  Atomic<StreamTime> mVideoEnd{STREAM_TIME_MAX};
   const RefPtr<AbstractThread> mAbstractMainThread;
 };
 
 DecodedStreamTrackListener::DecodedStreamTrackListener(
     DecodedStreamGraphListener* aGraphListener, SourceMediaStream* aStream,
-    mozilla::TrackID aTrackID)
+    TrackID aTrackID)
     : mGraphListener(aGraphListener), mStream(aStream), mTrackID(aTrackID) {}
 
 void DecodedStreamTrackListener::NotifyOutput(MediaStreamGraph* aGraph,
                                               StreamTime aCurrentTrackTime) {
   mGraphListener->NotifyOutput(mTrackID, aCurrentTrackTime);
 }
 
 void DecodedStreamTrackListener::NotifyEnded() {
-  mStream->Graph()->DispatchToMainThreadStableState(
-      NewRunnableMethod<mozilla::TrackID>(
-          "DecodedStreamGraphListener::DoNotifyTrackEnded", mGraphListener,
-          &DecodedStreamGraphListener::DoNotifyTrackEnded, mTrackID));
+  mGraphListener->NotifyEnded(mTrackID);
 }
 
 /*
  * All MediaStream-related data is protected by the decoder's monitor.
  * We have at most one DecodedStreamDaata per MediaDecoder. Its stream
  * is used as the input for each ProcessedMediaStream created by calls to
  * captureStream(UntilEnded). Seeking creates a new source stream, as does
  * replaying after the input as ended. In the latter case, the new source is
@@ -184,30 +248,46 @@ class DecodedStreamData {
       MozPromiseHolder<DecodedStream::EndedPromise>&& aAudioEndedPromise,
       MozPromiseHolder<DecodedStream::EndedPromise>&& aVideoEndedPromise,
       AbstractThread* aMainThread);
   ~DecodedStreamData();
   MediaEventSource<int64_t>& OnOutput();
   void Forget();
   nsCString GetDebugInfo();
 
+  void WriteVideoToSegment(layers::Image* aImage, const TimeUnit& aStart,
+                           const TimeUnit& aEnd,
+                           const gfx::IntSize& aIntrinsicSize,
+                           const TimeStamp& aTimeStamp, VideoSegment* aOutput,
+                           const PrincipalHandle& aPrincipalHandle);
+
   /* The following group of fields are protected by the decoder's monitor
    * and can be read or written on any thread.
    */
   // Count of audio frames written to the stream
   int64_t mAudioFramesWritten;
   // Count of video frames written to the stream in the stream's rate
   StreamTime mStreamVideoWritten;
   // Count of audio frames written to the stream in the stream's rate
   StreamTime mStreamAudioWritten;
-  // mNextVideoTime is the end timestamp for the last packet sent to the stream.
-  // Therefore video packets starting at or after this time need to be copied
+  // mNextAudioTime is the end timestamp for the last packet sent to the stream.
+  // Therefore audio packets starting at or after this time need to be copied
+  // to the output stream.
+  TimeUnit mNextAudioTime;
+  // mLastVideoStartTime is the start timestamp for the last packet sent to the
+  // stream. Therefore video packets starting after this time need to be copied
   // to the output stream.
-  TimeUnit mNextVideoTime;
-  TimeUnit mNextAudioTime;
+  Maybe<TimeUnit> mLastVideoStartTime;
+  // mLastVideoEndTime is the end timestamp for the last packet sent to the
+  // stream. It is used to adjust durations of chunks sent to the output stream
+  // when there are overlaps in VideoData.
+  Maybe<TimeUnit> mLastVideoEndTime;
+  // The timestamp of the last frame, so we can ensure time never goes
+  // backwards.
+  TimeStamp mLastVideoTimeStamp;
   // The last video image sent to the stream. Useful if we need to replicate
   // the image.
   RefPtr<layers::Image> mLastVideoImage;
   gfx::IntSize mLastVideoImageDisplaySize;
   bool mHaveSentFinishAudio;
   bool mHaveSentFinishVideo;
 
   // The decoder is responsible for calling Destroy() on this stream.
@@ -221,74 +301,60 @@ class DecodedStreamData {
 DecodedStreamData::DecodedStreamData(
     OutputStreamManager* aOutputStreamManager, PlaybackInfoInit&& aInit,
     MozPromiseHolder<DecodedStream::EndedPromise>&& aAudioEndedPromise,
     MozPromiseHolder<DecodedStream::EndedPromise>&& aVideoEndedPromise,
     AbstractThread* aMainThread)
     : mAudioFramesWritten(0),
       mStreamVideoWritten(0),
       mStreamAudioWritten(0),
-      mNextVideoTime(aInit.mStartTime),
       mNextAudioTime(aInit.mStartTime),
       mHaveSentFinishAudio(false),
       mHaveSentFinishVideo(false),
       mStream(aOutputStreamManager->mSourceStream),
       // DecodedStreamGraphListener will resolve these promises.
       mListener(MakeRefPtr<DecodedStreamGraphListener>(
           mStream, aInit.mAudioTrackID, std::move(aAudioEndedPromise),
           aInit.mVideoTrackID, std::move(aVideoEndedPromise), aMainThread)),
       mOutputStreamManager(aOutputStreamManager),
       mAbstractMainThread(aMainThread) {
   MOZ_ASSERT(NS_IsMainThread());
-  // Initialize tracks on main thread and in the MediaStreamGraph.
-  // Tracks on main thread may have been created early in OutputStreamManager
-  // by the state machine, since creating them here is async from the js call.
-  // If they were pre-created in OutputStreamManager and the MediaInfo has
-  // changed since then, we end them and create new tracks.
-  if (!mOutputStreamManager->HasTracks(aInit.mAudioTrackID,
-                                       aInit.mVideoTrackID)) {
-    // Because these tracks were pre-allocated, we also have to increment the
-    // internal track allocator by the same number of tracks, so we don't risk
-    // a TrackID collision.
-    for (size_t i = 0; i < mOutputStreamManager->NumberOfTracks(); ++i) {
-      Unused << mOutputStreamManager->AllocateNextTrackID();
-    }
-    mOutputStreamManager->RemoveTracks();
-  }
+  MOZ_DIAGNOSTIC_ASSERT(
+      mOutputStreamManager->HasTracks(aInit.mAudioTrackID, aInit.mVideoTrackID),
+      "Tracks must be pre-created on main thread");
   if (IsTrackIDExplicit(aInit.mAudioTrackID)) {
-    if (!mOutputStreamManager->HasTrack(aInit.mAudioTrackID)) {
-      mOutputStreamManager->AddTrack(aInit.mAudioTrackID, MediaSegment::AUDIO);
-    }
     mStream->AddAudioTrack(aInit.mAudioTrackID, aInit.mInfo.mAudio.mRate,
                            new AudioSegment());
   }
   if (IsTrackIDExplicit(aInit.mVideoTrackID)) {
-    if (!mOutputStreamManager->HasTrack(aInit.mVideoTrackID)) {
-      mOutputStreamManager->AddTrack(aInit.mVideoTrackID, MediaSegment::VIDEO);
-    }
     mStream->AddTrack(aInit.mVideoTrackID, new VideoSegment());
   }
 }
 
 DecodedStreamData::~DecodedStreamData() { MOZ_ASSERT(NS_IsMainThread()); }
 
 MediaEventSource<int64_t>& DecodedStreamData::OnOutput() {
   return mListener->OnOutput();
 }
 
 void DecodedStreamData::Forget() { mListener->Forget(); }
 
 nsCString DecodedStreamData::GetDebugInfo() {
   return nsPrintfCString(
       "DecodedStreamData=%p mAudioFramesWritten=%" PRId64
       " mStreamAudioWritten=%" PRId64 " mStreamVideoWritten=%" PRId64
-      " mNextAudioTime=%" PRId64 " mNextVideoTime=%" PRId64
-      "mHaveSentFinishAudio=%d mHaveSentFinishVideo=%d",
+      " mNextAudioTime=%" PRId64 " mLastVideoStartTime=%" PRId64
+      " mLastVideoEndTime=%" PRId64
+      " mHaveSentFinishAudio=%d mHaveSentFinishVideo=%d",
       this, mAudioFramesWritten, mStreamAudioWritten, mStreamVideoWritten,
-      mNextAudioTime.ToMicroseconds(), mNextVideoTime.ToMicroseconds(),
+      mNextAudioTime.ToMicroseconds(),
+      mLastVideoStartTime.valueOr(TimeUnit::FromMicroseconds(-1))
+          .ToMicroseconds(),
+      mLastVideoEndTime.valueOr(TimeUnit::FromMicroseconds(-1))
+          .ToMicroseconds(),
       mHaveSentFinishAudio, mHaveSentFinishVideo);
 }
 
 DecodedStream::DecodedStream(AbstractThread* aOwnerThread,
                              AbstractThread* aMainThread,
                              MediaQueue<AudioData>& aAudioQueue,
                              MediaQueue<VideoData>& aVideoQueue,
                              OutputStreamManager* aOutputStreamManager,
@@ -368,22 +434,28 @@ nsresult DecodedStream::Start(const Time
       // This happens when RemoveOutput() is called immediately after
       // StartPlayback().
       if (mOutputStreamManager->IsEmpty()) {
         // Resolve the promise to indicate the end of playback.
         mAudioEndedPromise.Resolve(true, __func__);
         mVideoEndedPromise.Resolve(true, __func__);
         return NS_OK;
       }
-      mInit.mAudioTrackID = mInit.mInfo.HasAudio()
-                                ? mOutputStreamManager->AllocateNextTrackID()
-                                : TRACK_NONE;
-      mInit.mVideoTrackID = mInit.mInfo.HasVideo()
-                                ? mOutputStreamManager->AllocateNextTrackID()
-                                : TRACK_NONE;
+      if (mInit.mInfo.HasAudio() &&
+          !mOutputStreamManager->HasTrackType(MediaSegment::AUDIO)) {
+        mOutputStreamManager->AddTrack(MediaSegment::AUDIO);
+      }
+      if (mInit.mInfo.HasVideo() &&
+          !mOutputStreamManager->HasTrackType(MediaSegment::VIDEO)) {
+        mOutputStreamManager->AddTrack(MediaSegment::VIDEO);
+      }
+      mInit.mAudioTrackID =
+          mOutputStreamManager->GetLiveTrackIDFor(MediaSegment::AUDIO);
+      mInit.mVideoTrackID =
+          mOutputStreamManager->GetLiveTrackIDFor(MediaSegment::VIDEO);
       mData = MakeUnique<DecodedStreamData>(
           mOutputStreamManager, std::move(mInit), std::move(mAudioEndedPromise),
           std::move(mVideoEndedPromise), mAbstractMainThread);
       return NS_OK;
     }
     UniquePtr<DecodedStreamData> ReleaseData() { return std::move(mData); }
 
    private:
@@ -399,17 +471,17 @@ nsresult DecodedStream::Start(const Time
   mAudioEndedPromise = audioEndedHolder.Ensure(__func__);
   MozPromiseHolder<DecodedStream::EndedPromise> videoEndedHolder;
   mVideoEndedPromise = videoEndedHolder.Ensure(__func__);
   PlaybackInfoInit init{aStartTime, aInfo, TRACK_INVALID, TRACK_INVALID};
   nsCOMPtr<nsIRunnable> r = new R(std::move(init), std::move(audioEndedHolder),
                                   std::move(videoEndedHolder),
                                   mOutputStreamManager, mAbstractMainThread);
   SyncRunnable::DispatchToThread(
-      SystemGroup::EventTargetFor(mozilla::TaskCategory::Other), r);
+      SystemGroup::EventTargetFor(TaskCategory::Other), r);
   mData = static_cast<R*>(r.get())->ReleaseData();
 
   if (mData) {
     mInfo.mAudio.mTrackId = mData->mListener->AudioTrackID();
     mInfo.mVideo.mTrackId = mData->mListener->VideoTrackID();
     mOutputListener = mData->OnOutput().Connect(mOwnerThread, this,
                                                 &DecodedStream::NotifyOutput);
     SendData();
@@ -453,19 +525,22 @@ void DecodedStream::DestroyData(UniquePt
   AssertOwnerThread();
 
   if (!aData) {
     return;
   }
 
   mOutputListener.Disconnect();
 
-  NS_DispatchToMainThread(
-      NS_NewRunnableFunction("DecodedStream::DestroyData",
-                             [data = std::move(aData)]() { data->Forget(); }));
+  NS_DispatchToMainThread(NS_NewRunnableFunction(
+      "DecodedStream::DestroyData",
+      [data = std::move(aData), manager = mOutputStreamManager]() {
+        data->Forget();
+        manager->RemoveTracks();
+      }));
 }
 
 void DecodedStream::SetPlaying(bool aPlaying) {
   AssertOwnerThread();
 
   // Resume/pause matters only when playback started.
   if (mStartTime.isNothing()) {
     return;
@@ -564,46 +639,46 @@ void DecodedStream::SendAudio(double aVo
 
   output.ApplyVolume(aVolume);
 
   if (!aIsSameOrigin) {
     output.ReplaceWithDisabled();
   }
 
   // |mNextAudioTime| is updated as we process each audio sample in
-  // SendStreamAudio(). This is consistent with how |mNextVideoTime|
-  // is updated for video samples.
+  // SendStreamAudio().
   if (output.GetDuration() > 0) {
     mData->mStreamAudioWritten +=
         sourceStream->AppendToTrack(audioTrackId, &output);
   }
 
   if (mAudioQueue.IsFinished() && !mData->mHaveSentFinishAudio) {
-    sourceStream->EndTrack(audioTrackId);
+    mData->mListener->EndTrackAt(audioTrackId, mData->mStreamAudioWritten);
     mData->mHaveSentFinishAudio = true;
   }
 }
 
-static void WriteVideoToMediaStream(MediaStream* aStream, layers::Image* aImage,
-                                    const TimeUnit& aStart,
-                                    const TimeUnit& aEnd,
-                                    const mozilla::gfx::IntSize& aIntrinsicSize,
-                                    const TimeStamp& aTimeStamp,
-                                    VideoSegment* aOutput,
-                                    const PrincipalHandle& aPrincipalHandle) {
+void DecodedStreamData::WriteVideoToSegment(
+    layers::Image* aImage, const TimeUnit& aStart, const TimeUnit& aEnd,
+    const gfx::IntSize& aIntrinsicSize, const TimeStamp& aTimeStamp,
+    VideoSegment* aOutput, const PrincipalHandle& aPrincipalHandle) {
   RefPtr<layers::Image> image = aImage;
-  auto end = aStream->MicrosecondsToStreamTimeRoundDown(aEnd.ToMicroseconds());
+  auto end = mStream->MicrosecondsToStreamTimeRoundDown(aEnd.ToMicroseconds());
   auto start =
-      aStream->MicrosecondsToStreamTimeRoundDown(aStart.ToMicroseconds());
+      mStream->MicrosecondsToStreamTimeRoundDown(aStart.ToMicroseconds());
   aOutput->AppendFrame(image.forget(), aIntrinsicSize, aPrincipalHandle, false,
                        aTimeStamp);
   // Extend this so we get accurate durations for all frames.
   // Because this track is pushed, we need durations so the graph can track
   // when playout of the track has finished.
   aOutput->ExtendLastFrameBy(end - start);
+
+  mLastVideoStartTime = Some(aStart);
+  mLastVideoEndTime = Some(aEnd);
+  mLastVideoTimeStamp = aTimeStamp;
 }
 
 static bool ZeroDurationAtLastChunk(VideoSegment& aInput) {
   // Get the last video frame's start time in VideoSegment aInput.
   // If the start time is equal to the duration of aInput, means the last video
   // frame's duration is zero.
   StreamTime lastVideoStratTime;
   aInput.GetLastFrame(&lastVideoStratTime);
@@ -631,25 +706,30 @@ void DecodedStream::ResetVideo(const Pri
   // an ugly hack because the direct listeners of the MediaStreamGraph do not
   // have an API that supports clearing the future frames. ImageContainer and
   // VideoFrameContainer do though, and we will need to move to a similar API
   // for video tracks as part of bug 1493618.
   resetter.AppendFrame(nullptr, mData->mLastVideoImageDisplaySize,
                        aPrincipalHandle, false, currentTime);
   mData->mStream->AppendToTrack(mInfo.mVideo.mTrackId, &resetter);
 
-  // Consumer buffers have been reset. We now set mNextVideoTime to the start
+  // Consumer buffers have been reset. We now set the next time to the start
   // time of the current frame, so that it can be displayed again on resuming.
   if (RefPtr<VideoData> v = mVideoQueue.PeekFront()) {
-    mData->mNextVideoTime = v->mTime;
+    mData->mLastVideoStartTime = Some(v->mTime - TimeUnit::FromMicroseconds(1));
+    mData->mLastVideoEndTime = Some(v->mTime);
   } else {
-    // There was no current frame in the queue. We set the next time to push to
-    // the current time, so we at least don't resume starting in the future.
-    mData->mNextVideoTime = currentPosition;
+    // There was no current frame in the queue. We set the next time to the
+    // current time, so we at least don't resume starting in the future.
+    mData->mLastVideoStartTime =
+        Some(currentPosition - TimeUnit::FromMicroseconds(1));
+    mData->mLastVideoEndTime = Some(currentPosition);
   }
+
+  mData->mLastVideoTimeStamp = currentTime;
 }
 
 void DecodedStream::SendVideo(bool aIsSameOrigin,
                               const PrincipalHandle& aPrincipalHandle) {
   AssertOwnerThread();
 
   if (!mInfo.HasVideo()) {
     return;
@@ -661,53 +741,63 @@ void DecodedStream::SendVideo(bool aIsSa
 
   VideoSegment output;
   TrackID videoTrackId = mInfo.mVideo.mTrackId;
   AutoTArray<RefPtr<VideoData>, 10> video;
   SourceMediaStream* sourceStream = mData->mStream;
 
   // It's OK to hold references to the VideoData because VideoData
   // is ref-counted.
-  mVideoQueue.GetElementsAfter(mData->mNextVideoTime, &video);
+  mVideoQueue.GetElementsAfter(
+      mData->mLastVideoStartTime.valueOr(mStartTime.ref()), &video);
 
   TimeStamp currentTime;
   TimeUnit currentPosition = GetPosition(&currentTime);
 
+  if (mData->mLastVideoTimeStamp.IsNull()) {
+    mData->mLastVideoTimeStamp = currentTime;
+  }
+
   for (uint32_t i = 0; i < video.Length(); ++i) {
     VideoData* v = video[i];
+    TimeUnit lastStart = mData->mLastVideoStartTime.valueOr(mStartTime.ref());
+    TimeUnit lastEnd = mData->mLastVideoEndTime.valueOr(mStartTime.ref());
 
-    if (mData->mNextVideoTime < v->mTime) {
+    if (lastEnd < v->mTime) {
       // Write last video frame to catch up. mLastVideoImage can be null here
       // which is fine, it just means there's no video.
 
       // TODO: |mLastVideoImage| should come from the last image rendered
       // by the state machine. This will avoid the black frame when capture
       // happens in the middle of playback (especially in th middle of a
       // video frame). E.g. if we have a video frame that is 30 sec long
       // and capture happens at 15 sec, we'll have to append a black frame
       // that is 15 sec long.
-      WriteVideoToMediaStream(
-          sourceStream, mData->mLastVideoImage, mData->mNextVideoTime, v->mTime,
-          mData->mLastVideoImageDisplaySize,
-          currentTime +
-              (mData->mNextVideoTime - currentPosition).ToTimeDuration(),
-          &output, aPrincipalHandle);
-      mData->mNextVideoTime = v->mTime;
-    }
-
-    if (mData->mNextVideoTime < v->GetEndTime()) {
-      WriteVideoToMediaStream(
-          sourceStream, v->mImage, mData->mNextVideoTime, v->GetEndTime(),
-          v->mDisplay,
-          currentTime +
-              (mData->mNextVideoTime - currentPosition).ToTimeDuration(),
-          &output, aPrincipalHandle);
-      mData->mNextVideoTime = v->GetEndTime();
+      TimeStamp t =
+          std::max(mData->mLastVideoTimeStamp,
+                   currentTime + (lastEnd - currentPosition).ToTimeDuration());
+      mData->WriteVideoToSegment(mData->mLastVideoImage, lastEnd, v->mTime,
+                                 mData->mLastVideoImageDisplaySize, t, &output,
+                                 aPrincipalHandle);
+    } else if (lastStart < v->mTime) {
+      // This frame starts after the last frame's start. Note that this could be
+      // before the last frame's end time for some videos. This only matters for
+      // the track's lifetime in the MSG, as rendering is based on timestamps,
+      // aka frame start times.
+      TimeStamp t =
+          std::max(mData->mLastVideoTimeStamp,
+                   currentTime + (lastEnd - currentPosition).ToTimeDuration());
+      TimeUnit end = std::max(
+          v->GetEndTime(),
+          lastEnd + TimeUnit::FromMicroseconds(
+                        sourceStream->StreamTimeToMicroseconds(1) + 1));
       mData->mLastVideoImage = v->mImage;
       mData->mLastVideoImageDisplaySize = v->mDisplay;
+      mData->WriteVideoToSegment(v->mImage, lastEnd, end, v->mDisplay, t,
+                                 &output, aPrincipalHandle);
     }
   }
 
   // Check the output is not empty.
   bool compensateEOS = false;
   if (output.GetLastFrame()) {
     compensateEOS = ZeroDurationAtLastChunk(output);
   }
@@ -717,36 +807,49 @@ void DecodedStream::SendVideo(bool aIsSa
   }
 
   if (output.GetDuration() > 0) {
     mData->mStreamVideoWritten +=
         sourceStream->AppendToTrack(videoTrackId, &output);
   }
 
   if (mVideoQueue.IsFinished() && !mData->mHaveSentFinishVideo) {
+    if (!mData->mLastVideoImage) {
+      // We have video, but the video queue finished before we received any
+      // frame. We insert a black frame to progress any consuming
+      // HTMLMediaElement. This mirrors the behavior of VideoSink.
+
+      // Force a frame - can be null
+      compensateEOS = true;
+      // Force frame to be black
+      aIsSameOrigin = false;
+      // Override the frame's size (will be 0x0 otherwise)
+      mData->mLastVideoImageDisplaySize = mInfo.mVideo.mDisplay;
+    }
     if (compensateEOS) {
       VideoSegment endSegment;
       // Calculate the deviation clock time from DecodedStream.
-      auto deviation =
-          FromMicroseconds(sourceStream->StreamTimeToMicroseconds(1));
-      WriteVideoToMediaStream(
-          sourceStream, mData->mLastVideoImage, mData->mNextVideoTime,
-          mData->mNextVideoTime + deviation, mData->mLastVideoImageDisplaySize,
-          currentTime + (mData->mNextVideoTime + deviation - currentPosition)
-                            .ToTimeDuration(),
+      // We round the nr of microseconds up, because WriteVideoToSegment
+      // will round the conversion from microseconds to StreamTime down.
+      auto deviation = TimeUnit::FromMicroseconds(
+          sourceStream->StreamTimeToMicroseconds(1) + 1);
+      auto start = mData->mLastVideoEndTime.valueOr(mStartTime.ref());
+      mData->WriteVideoToSegment(
+          mData->mLastVideoImage, start, start + deviation,
+          mData->mLastVideoImageDisplaySize,
+          currentTime + (start + deviation - currentPosition).ToTimeDuration(),
           &endSegment, aPrincipalHandle);
-      mData->mNextVideoTime += deviation;
       MOZ_ASSERT(endSegment.GetDuration() > 0);
       if (!aIsSameOrigin) {
         endSegment.ReplaceWithDisabled();
       }
       mData->mStreamVideoWritten +=
           sourceStream->AppendToTrack(videoTrackId, &endSegment);
     }
-    sourceStream->EndTrack(videoTrackId);
+    mData->mListener->EndTrackAt(videoTrackId, mData->mStreamVideoWritten);
     mData->mHaveSentFinishVideo = true;
   }
 }
 
 StreamTime DecodedStream::SentDuration() {
   AssertOwnerThread();
 
   if (!mData) {
@@ -777,17 +880,17 @@ TimeUnit DecodedStream::GetEndTime(Track
   AssertOwnerThread();
   if (aType == TrackInfo::kAudioTrack && mInfo.HasAudio() && mData) {
     auto t = mStartTime.ref() +
              FramesToTimeUnit(mData->mAudioFramesWritten, mInfo.mAudio.mRate);
     if (t.IsValid()) {
       return t;
     }
   } else if (aType == TrackInfo::kVideoTrack && mData) {
-    return mData->mNextVideoTime;
+    return mData->mLastVideoEndTime.valueOr(mStartTime.ref());
   }
   return TimeUnit::Zero();
 }
 
 TimeUnit DecodedStream::GetPosition(TimeStamp* aTimeStamp) const {
   AssertOwnerThread();
   // This is only called after MDSM starts playback. So mStartTime is
   // guaranteed to be something.
@@ -795,17 +898,22 @@ TimeUnit DecodedStream::GetPosition(Time
   if (aTimeStamp) {
     *aTimeStamp = TimeStamp::Now();
   }
   return mStartTime.ref() + mLastOutputTime;
 }
 
 void DecodedStream::NotifyOutput(int64_t aTime) {
   AssertOwnerThread();
-  mLastOutputTime = FromMicroseconds(aTime);
+  TimeUnit time = TimeUnit::FromMicroseconds(aTime);
+  if (time == mLastOutputTime) {
+    return;
+  }
+  MOZ_ASSERT(mLastOutputTime < time);
+  mLastOutputTime = time;
   auto currentTime = GetPosition();
 
   // Remove audio samples that have been played by MSG from the queue.
   RefPtr<AudioData> a = mAudioQueue.PeekFront();
   for (; a && a->mTime < currentTime;) {
     RefPtr<AudioData> releaseMe = mAudioQueue.PopFront();
     a = mAudioQueue.PeekFront();
   }
--- a/dom/media/mediasink/DecodedStream.h
+++ b/dom/media/mediasink/DecodedStream.h
@@ -68,19 +68,16 @@ class DecodedStream : public MediaSink {
   void Shutdown() override;
 
   nsCString GetDebugInfo() override;
 
  protected:
   virtual ~DecodedStream();
 
  private:
-  media::TimeUnit FromMicroseconds(int64_t aTime) {
-    return media::TimeUnit::FromMicroseconds(aTime);
-  }
   void DestroyData(UniquePtr<DecodedStreamData>&& aData);
   void SendAudio(double aVolume, bool aIsSameOrigin,
                  const PrincipalHandle& aPrincipalHandle);
   void SendVideo(bool aIsSameOrigin, const PrincipalHandle& aPrincipalHandle);
   void ResetVideo(const PrincipalHandle& aPrincipalHandle);
   StreamTime SentDuration();
   void SendData();
   void NotifyOutput(int64_t aTime);
--- a/dom/media/mediasink/OutputStreamManager.cpp
+++ b/dom/media/mediasink/OutputStreamManager.cpp
@@ -196,90 +196,87 @@ void OutputStreamManager::Remove(DOMMedi
           aData->RemoveTrack(pair.first());
         }
       },
       []() { MOZ_ASSERT_UNREACHABLE("Didn't exist"); });
   DebugOnly<bool> rv = mStreams.RemoveElement(aDOMStream, StreamComparator());
   MOZ_ASSERT(rv);
 }
 
-bool OutputStreamManager::HasTrack(TrackID aTrackID) {
+bool OutputStreamManager::HasTrackType(MediaSegment::Type aType) {
   MOZ_ASSERT(NS_IsMainThread());
 
-  return mLiveTracks.Contains(aTrackID, TrackIDComparator());
+  return mLiveTracks.Contains(aType, TrackTypeComparator());
 }
 
 bool OutputStreamManager::HasTracks(TrackID aAudioTrack, TrackID aVideoTrack) {
   MOZ_ASSERT(NS_IsMainThread());
 
   size_t nrExpectedTracks = 0;
   bool asExpected = true;
   if (IsTrackIDExplicit(aAudioTrack)) {
     Unused << ++nrExpectedTracks;
     asExpected = asExpected && mLiveTracks.Contains(
                                    MakePair(aAudioTrack, MediaSegment::AUDIO),
-                                   TrackTypeComparator());
+                                   TrackComparator());
   }
   if (IsTrackIDExplicit(aVideoTrack)) {
     Unused << ++nrExpectedTracks;
     asExpected = asExpected && mLiveTracks.Contains(
                                    MakePair(aVideoTrack, MediaSegment::VIDEO),
-                                   TrackTypeComparator());
+                                   TrackComparator());
   }
   asExpected = asExpected && mLiveTracks.Length() == nrExpectedTracks;
   return asExpected;
 }
 
 size_t OutputStreamManager::NumberOfTracks() {
   MOZ_ASSERT(NS_IsMainThread());
   return mLiveTracks.Length();
 }
 
-void OutputStreamManager::AddTrack(TrackID aTrackID, MediaSegment::Type aType) {
+void OutputStreamManager::AddTrack(MediaSegment::Type aType) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mSourceStream->IsDestroyed());
-  MOZ_ASSERT(!HasTrack(aTrackID));
+  MOZ_ASSERT(!HasTrackType(aType),
+             "Cannot have two tracks of the same type at the same time");
+
+  TrackID id = mNextTrackID++;
 
   LOG(LogLevel::Info, "Adding %s track with id %d",
-      aType == MediaSegment::AUDIO ? "audio" : "video", aTrackID);
+      aType == MediaSegment::AUDIO ? "audio" : "video", id);
 
-  mLiveTracks.AppendElement(MakePair(aTrackID, aType));
+  mLiveTracks.AppendElement(MakePair(id, aType));
   for (const auto& data : mStreams) {
-    data->AddTrack(aTrackID, aType, mPrincipal, mCORSMode, true);
+    data->AddTrack(id, aType, mPrincipal, mCORSMode, true);
   }
 }
 
 void OutputStreamManager::RemoveTrack(TrackID aTrackID) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mSourceStream->IsDestroyed());
   LOG(LogLevel::Info, "Removing track with id %d", aTrackID);
   DebugOnly<bool> rv = mLiveTracks.RemoveElement(aTrackID, TrackIDComparator());
   MOZ_ASSERT(rv);
   for (const auto& data : mStreams) {
     data->RemoveTrack(aTrackID);
   }
 }
 
 void OutputStreamManager::RemoveTracks() {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!mSourceStream->IsDestroyed());
-  for (const Pair<TrackID, MediaSegment::Type>& pair : mLiveTracks) {
-    for (const auto& data : mStreams) {
-      data->RemoveTrack(pair.first());
-    }
+  nsTArray<Pair<TrackID, MediaSegment::Type>> liveTracks(mLiveTracks);
+  for (const auto& pair : liveTracks) {
+    RemoveTrack(pair.first());
   }
-  mLiveTracks.Clear();
 }
 
 void OutputStreamManager::Disconnect() {
   MOZ_ASSERT(NS_IsMainThread());
-  nsTArray<Pair<TrackID, MediaSegment::Type>> liveTracks(mLiveTracks);
-  for (const auto& pair : liveTracks) {
-    RemoveTrack(pair.first());
-  }
+  RemoveTracks();
   MOZ_ASSERT(mLiveTracks.IsEmpty());
   nsTArray<RefPtr<DOMMediaStream>> domStreams(mStreams.Length());
   for (const auto& data : mStreams) {
     domStreams.AppendElement(data->mDOMStream);
   }
   for (auto& domStream : domStreams) {
     Remove(domStream);
   }
@@ -306,20 +303,24 @@ void OutputStreamManager::SetPrincipal(n
   }
 }
 
 TrackID OutputStreamManager::NextTrackID() const {
   MOZ_ASSERT(NS_IsMainThread());
   return mNextTrackID;
 }
 
-TrackID OutputStreamManager::AllocateNextTrackID() {
+TrackID OutputStreamManager::GetLiveTrackIDFor(MediaSegment::Type aType) const {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_RELEASE_ASSERT(IsTrackIDExplicit(mNextTrackID));
-  return mNextTrackID++;
+  for (const auto& pair : mLiveTracks) {
+    if (pair.second() == aType) {
+      return pair.first();
+    }
+  }
+  return TRACK_NONE;
 }
 
 void OutputStreamManager::SetPlaying(bool aPlaying) {
   MOZ_ASSERT(NS_IsMainThread());
   if (mPlaying == aPlaying) {
     return;
   }
 
--- a/dom/media/mediasink/OutputStreamManager.h
+++ b/dom/media/mediasink/OutputStreamManager.h
@@ -71,45 +71,43 @@ class OutputStreamManager {
   explicit OutputStreamManager(SourceMediaStream* aSourceStream,
                                TrackID aNextTrackID, nsIPrincipal* aPrincipal,
                                CORSMode aCORSMode,
                                AbstractThread* aAbstractMainThread);
   // Add the output stream to the collection.
   void Add(DOMMediaStream* aDOMStream);
   // Remove the output stream from the collection.
   void Remove(DOMMediaStream* aDOMStream);
-  // Returns true if aTrackID has been added to all output streams.
-  bool HasTrack(TrackID aTrackID);
+  // Returns true if there's a live track of the given type.
+  bool HasTrackType(MediaSegment::Type aType);
   // Returns true if the given tracks and no others are currently live.
   // Use a non-explicit TrackID to make it ignored for that type.
   bool HasTracks(TrackID aAudioTrack, TrackID aVideoTrack);
   // Returns the number of live tracks.
   size_t NumberOfTracks();
-  // Add aTrackID to all output streams.
-  void AddTrack(TrackID aTrackID, MediaSegment::Type aType);
-  // Remove aTrackID from all output streams.
-  void RemoveTrack(TrackID aTrackID);
-  // Remove all added tracks from all output streams.
+  // Add a track to all output streams.
+  void AddTrack(MediaSegment::Type aType);
+  // Remove all currently live tracks from all output streams.
   void RemoveTracks();
   // Disconnect mSourceStream from all output streams.
   void Disconnect();
   // The principal handle for the underlying decoder.
   AbstractCanonical<PrincipalHandle>* CanonicalPrincipalHandle();
   // Called when the underlying decoder's principal has changed.
   void SetPrincipal(nsIPrincipal* aPrincipal);
   // The CORSMode for the media element owning the decoder.
   AbstractCanonical<CORSMode>* CanonicalCORSMode();
   // Called when the CORSMode for the media element owning the decoder has
   // changed.
   void SetCORSMode(CORSMode aCORSMode);
-  // Returns the track id that would be used the next time a track is allocated.
+  // Returns the track id that would be used the next time a track is added.
   TrackID NextTrackID() const;
-  // Like NextTrackID() but advances internal state, so the next call returns a
-  // new unique TrackID.
-  TrackID AllocateNextTrackID();
+  // Returns the TrackID for the currently live track of the given type, or
+  // TRACK_NONE otherwise.
+  TrackID GetLiveTrackIDFor(MediaSegment::Type aType) const;
   // Called by DecodedStream when its playing state changes. While not playing
   // we suspend mSourceStream.
   void SetPlaying(bool aPlaying);
   // Return true if the collection of output streams is empty.
   bool IsEmpty() const {
     MOZ_ASSERT(NS_IsMainThread());
     return mStreams.IsEmpty();
   }
@@ -130,21 +128,31 @@ class OutputStreamManager {
   struct TrackIDComparator {
     static bool Equals(const Pair<TrackID, MediaSegment::Type>& aLiveTrack,
                        TrackID aTrackID) {
       return aLiveTrack.first() == aTrackID;
     }
   };
   struct TrackTypeComparator {
     static bool Equals(const Pair<TrackID, MediaSegment::Type>& aLiveTrack,
+                       MediaSegment::Type aType) {
+      return aLiveTrack.second() == aType;
+    }
+  };
+  struct TrackComparator {
+    static bool Equals(const Pair<TrackID, MediaSegment::Type>& aLiveTrack,
                        const Pair<TrackID, MediaSegment::Type>& aOther) {
       return aLiveTrack.first() == aOther.first() &&
              aLiveTrack.second() == aOther.second();
     }
   };
+
+  // Remove aTrackID from all output streams.
+  void RemoveTrack(TrackID aTrackID);
+
   nsTArray<UniquePtr<OutputStreamData>> mStreams;
   nsTArray<Pair<TrackID, MediaSegment::Type>> mLiveTracks;
   Canonical<PrincipalHandle> mPrincipalHandle;
   nsCOMPtr<nsIPrincipal> mPrincipal;
   const CORSMode mCORSMode;
   TrackID mNextTrackID;
   bool mPlaying;
 };
--- a/dom/media/test/manifest.js
+++ b/dom/media/test/manifest.js
@@ -195,51 +195,51 @@ var gPlayTests = [
   // Ogg stream without eof marker
   { name:"bug461281.ogg", type:"application/ogg", duration:2.208 },
 
   // oggz-chop stream
   { name:"bug482461.ogv", type:"video/ogg", duration:4.34 },
   // Theora only oggz-chop stream
   { name:"bug482461-theora.ogv", type:"video/ogg", duration:4.138 },
   // With first frame a "duplicate" (empty) frame.
-  { name:"bug500311.ogv", type:"video/ogg", duration:1.96 },
+  { name:"bug500311.ogv", type:"video/ogg", duration:1.96, contentDuration:1.958 },
   // Small audio file
   { name:"small-shot.ogg", type:"audio/ogg", duration:0.276 },
   // More audio in file than video.
   { name:"short-video.ogv", type:"video/ogg", duration:1.081 },
   // First Theora data packet is zero bytes.
   { name:"bug504613.ogv", type:"video/ogg", duration:Number.NaN },
   // Multiple audio streams.
   { name:"bug516323.ogv", type:"video/ogg", duration:4.208 },
   // oggz-chop with non-keyframe as first frame
-  { name:"bug556821.ogv", type:"video/ogg", duration:2.936 },
+  { name:"bug556821.ogv", type:"video/ogg", duration:2.936, contentDuration:2.903 },
 
   // Encoded with vorbis beta1, includes unusually sized codebooks
   { name:"beta-phrasebook.ogg", type:"audio/ogg", duration:4.01 },
   // Small file, only 1 frame with audio only.
   { name:"bug520493.ogg", type:"audio/ogg", duration:0.458 },
   // Small file with vorbis comments with 0 length values and names.
   { name:"bug520500.ogg", type:"audio/ogg", duration:0.123 },
 
   // Various weirdly formed Ogg files
-  { name:"bug499519.ogv", type:"video/ogg", duration:0.24 },
+  { name:"bug499519.ogv", type:"video/ogg", duration:0.24, contentDuration:0.22 },
   { name:"bug506094.ogv", type:"video/ogg", duration:0 },
   { name:"bug498855-1.ogv", type:"video/ogg", duration:0.24 },
   { name:"bug498855-2.ogv", type:"video/ogg", duration:0.24 },
   { name:"bug498855-3.ogv", type:"video/ogg", duration:0.24 },
-  { name:"bug504644.ogv", type:"video/ogg", duration:1.6 },
-  { name:"chain.ogv", type:"video/ogg", duration:Number.NaN },
-  { name:"bug523816.ogv", type:"video/ogg", duration:0.766 },
+  { name:"bug504644.ogv", type:"video/ogg", duration:1.6, contentDuration:1.52 },
+  { name:"chain.ogv", type:"video/ogg", duration:Number.NaN, contentDuration:0.266 },
+  { name:"bug523816.ogv", type:"video/ogg", duration:0.766, contentDuration:0 },
   { name:"bug495129.ogv", type:"video/ogg", duration:2.41 },
-  { name:"bug498380.ogv", type:"video/ogg", duration:0.7663 },
+  { name:"bug498380.ogv", type:"video/ogg", duration:0.7663, contentDuration:0 },
   { name:"bug495794.ogg", type:"audio/ogg", duration:0.3 },
   { name:"bug557094.ogv", type:"video/ogg", duration:0.24 },
   { name:"multiple-bos.ogg", type:"video/ogg", duration:0.431 },
-  { name:"audio-overhang.ogg", type:"audio/ogg", duration:2.3 },
-  { name:"video-overhang.ogg", type:"audio/ogg", duration:3.966 },
+  { name:"audio-overhang.ogg", type:"video/ogg", duration:2.3 },
+  { name:"video-overhang.ogg", type:"video/ogg", duration:3.966 },
 
   // bug461281.ogg with the middle second chopped out.
   { name:"audio-gaps.ogg", type:"audio/ogg", duration:2.208 },
 
   // Test playback/metadata work after a redirect
   { name:"redirect.sjs?domain=mochi.test:8888&file=320x240.ogv",
     type:"video/ogg", duration:0.266 },
 
@@ -256,21 +256,21 @@ var gPlayTests = [
   { name:"resolution-change.webm", type:"video/webm", duration:6.533 },
 
   // A really short, low sample rate, single channel file. This tests whether
   // we can handle playing files when only push very little audio data to the
   // hardware.
   { name:"spacestorm-1000Hz-100ms.ogg", type:"audio/ogg", duration:0.099 },
 
   // Opus data in an ogg container
-  { name:"detodos-short.opus", type:"audio/ogg; codecs=opus", duration:0.22 },
+  { name:"detodos-short.opus", type:"audio/ogg; codecs=opus", duration:0.22, contentDuration:0.2135 },
   // Opus data in a webm container
-  { name:"detodos-short.webm", type:"audio/webm; codecs=opus", duration:0.26 },
+  { name:"detodos-short.webm", type:"audio/webm; codecs=opus", duration:0.26, contentDuration:0.2535 },
   // Opus in webm channel mapping=2 sample file
-  { name:"opus-mapping2.webm", type:"audio/webm; codecs=opus", duration:10.01 },
+  { name:"opus-mapping2.webm", type:"audio/webm; codecs=opus", duration:10.01, contentDuration:9.99 },
   { name:"bug1066943.webm", type:"audio/webm; codecs=opus", duration:1.383 },
 
   // Multichannel Opus in an ogg container
   { name:"test-1-mono.opus", type:"audio/ogg; codecs=opus", duration:1.044 },
   { name:"test-2-stereo.opus", type:"audio/ogg; codecs=opus", duration:2.925 },
   { name:"test-3-LCR.opus", type:"audio/ogg; codecs=opus", duration:4.214 },
   { name:"test-4-quad.opus", type:"audio/ogg; codecs=opus", duration:6.234 },
   { name:"test-5-5.0.opus", type:"audio/ogg; codecs=opus", duration:7.558 },
@@ -280,18 +280,18 @@ var gPlayTests = [
 
   { name:"gizmo-short.mp4", type:"video/mp4", duration:0.27 },
   // Test playback of a MP4 file with a non-zero start time (and audio starting
   // a second later).
   { name:"bipbop-lateaudio.mp4", type:"video/mp4" },
   // Ambisonics AAC, requires AAC extradata to be set when creating decoder (see bug 1431169)
   // Also test 4.0 decoding.
   { name:"ambisonics.mp4", type:"audio/mp4", duration:16.48 },
-  // Opus in MP4 channel mapping=0 sample file
-  { name:"opus-sample.mp4", type:"audio/mp4; codecs=opus", duration:10.92 },
+  // Opus in MP4 channel mapping=0 sample file (content shorter due to preskip)
+  { name:"opus-sample.mp4", type:"audio/mp4; codecs=opus", duration:10.92, contentDuration:10.09 },
   // Opus in MP4 channel mapping=2 sample file
   { name:"opus-mapping2.mp4", type:"audio/mp4; codecs=opus", duration:10.0 },
 
   { name:"small-shot.m4a", type:"audio/mp4", duration:0.29 },
   { name:"small-shot.mp3", type:"audio/mpeg", duration:0.27 },
   { name:"owl.mp3", type:"audio/mpeg", duration:3.343 },
   // owl.mp3 as above, but with something funny going on in the ID3v2 tag
   // that caused DirectShow to fail.
@@ -303,23 +303,23 @@ var gPlayTests = [
   // frame is at such a high offset into the file, MP3FrameParser will give up
   // and report that the stream is not MP3. However, it does not count ID3 tags
   // in that offset. This test case makes sure that ID3 exclusion holds.
   { name:"huge-id3.mp3", type:"audio/mpeg", duration:1.00 },
   // A truncated VBR MP3 with just enough frames to keep most decoders happy.
   // The Xing header reports the length of the file to be around 10 seconds, but
   // there is really only one second worth of data. We want MP3FrameParser to
   // trust the header, so this should be reported as 10 seconds.
-  { name:"vbr-head.mp3", type:"audio/mpeg", duration:10.00 },
+  { name:"vbr-head.mp3", type:"audio/mpeg", duration:10.00, contentDuration:1.019 },
 
   // A flac file where the STREAMINFO block was removed.
   // It is necessary to parse the file to find an audio frame instead.
   { name:"flac-noheader-s16.flac", type:"audio/flac", duration:4.0 },
   { name:"flac-s24.flac", type:"audio/flac", duration:4.04 },
-  { name:"flac-sample.mp4", type:"audio/mp4; codecs=flac", duration:4.95 },
+  { name:"flac-sample.mp4", type:"audio/mp4; codecs=flac", duration:4.95, contentDuration:5.03 },
   // Ogg with theora video and flac audio.
   { name:"A4.ogv", type:"video/ogg", width:320, height:240, duration:3.13 },
 
   // Invalid file
   { name:"bogus.duh", type:"bogus/duh", duration:Number.NaN },
 ];
 
 if (!(manifestNavigator().userAgent.includes("Windows") &&
@@ -349,17 +349,17 @@ var gSeekToNextFrameTests = [
   // Various weirdly formed Ogg files
   { name:"bug498855-1.ogv", type:"video/ogg", duration:0.24 },
   { name:"bug498855-2.ogv", type:"video/ogg", duration:0.24 },
   { name:"bug498855-3.ogv", type:"video/ogg", duration:0.24 },
   { name:"bug504644.ogv", type:"video/ogg", duration:1.6 },
 
   { name:"bug523816.ogv", type:"video/ogg", duration:0.766 },
 
-  { name:"bug498380.ogv", type:"video/ogg", duration:0.766 },
+  { name:"bug498380.ogv", type:"video/ogg", duration:0.2 },
   { name:"bug557094.ogv", type:"video/ogg", duration:0.24 },
   { name:"multiple-bos.ogg", type:"video/ogg", duration:0.431 },
   // Test playback/metadata work after a redirect
   { name:"redirect.sjs?domain=mochi.test:8888&file=320x240.ogv",
     type:"video/ogg", duration:0.266 },
   // Test playback of a webm file
   { name:"seek-short.webm", type:"video/webm", duration:0.23 },
   // Test playback of a WebM file with non-zero start time.
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -1178,16 +1178,19 @@ tags=msg capturestream
 skip-if = toolkit == 'android' # bug 1372457
 tags=msg capturestream
 [test_streams_element_capture_playback.html]
 skip-if = toolkit == 'android' # android(bug 1232305)
 tags=msg capturestream
 [test_streams_element_capture_reset.html]
 skip-if = toolkit == 'android' # android(bug 1232305)
 tags=msg capturestream
+[test_streams_element_capture_twice.html]
+skip-if = toolkit == 'android' # android(bug 1232305)
+tags=msg capturestream
 [test_streams_gc.html]
 skip-if = android_version == '17' || (android_version == '19' && debug) # android(bug 1232305)
 tags=msg capturestream
 [test_streams_individual_pause.html]
 skip-if = android_version == '17' || android_version == '19' # android(bug 1232305)
 tags=msg
 [test_streams_srcObject.html]
 skip-if = toolkit == 'android' # bug 1300443, android(bug 1232305)
--- a/dom/media/test/test_streams_element_capture.html
+++ b/dom/media/test/test_streams_element_capture.html
@@ -4,97 +4,120 @@
   <title>Test that a MediaStream captured from one element plays back in another</title>
   <script src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <script type="text/javascript" src="manifest.js"></script>
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
-SimpleTest.waitForExplicitFinish();
+let manager = new MediaTestManager();
 
-// longer timeout for slow platforms
-if (isSlowPlatform()) {
-  SimpleTest.requestLongerTimeout(3);
-  SimpleTest.requestCompleteLog();
-}
-
-function checkDrawImage(vout) {
+function checkDrawImage(vout, msg) {
   var canvas = document.createElement("canvas");
   var ctx = canvas.getContext("2d");
   ctx.drawImage(vout, 0, 0);
   var imgData = ctx.getImageData(0, 0, 1, 1);
-  is(imgData.data[3], 255, "Check video frame pixel has been drawn");
+  is(imgData.data[3], 255, msg);
 }
 
 function isGreaterThanOrEqualEps(a, b, msg) {
-  ok(a >= b - 0.01,
-     "Got " + a + ", expected at least " + b + "; " + msg);
+  ok(a >= b, `Got ${a}, expected at least ${b}; ${msg}`);
 }
 
-function startTest(test) {
+function startTest(test, token) {
+  manager.started(token);
   var v = document.createElement('video');
   var vout = document.createElement('video');
 
+  v.id = "MediaDecoder";
+  vout.id = "MediaStream";
+
   v.src = test.name;
   var stream;
 
   var checkEnded = function() {
-    if (test.duration) {
-      isGreaterThanOrEqualEps(vout.currentTime, test.duration,
-         test.name + " current time at end");
+    let duration = test.duration;
+    if (typeof(test.contentDuration) == "number") {
+      duration = test.contentDuration;
+    }
+    if (duration) {
+      isGreaterThanOrEqualEps(vout.currentTime, duration,
+        `${token} current time at end`);
     }
-    is(vout.readyState, vout.HAVE_CURRENT_DATA, test.name + " checking readyState");
-    ok(vout.ended, test.name + " checking playback has ended");
-    if (test.type.match(/^video/)) {
-      checkDrawImage(vout);
+    is(vout.readyState, vout.HAVE_CURRENT_DATA,
+      `${token} checking readyState`);
+    ok(vout.ended, `${token} checking playback has ended`);
+    isnot(stream.getTracks().length, 0, `${token} results in some tracks`);
+    if (stream.getVideoTracks().length > 0) {
+      ok(test.type.match(/^video/), `${token} is a video resource`);
+      checkDrawImage(vout, `${token} checking video frame pixel has been drawn`);
     }
     vout.remove();
     removeNodeAndSource(v);
-    SimpleTest.finish();
+    manager.finished(token);
   };
-  vout.addEventListener("ended", checkEnded);
+  Promise.race([
+    Promise.all([
+      new Promise(r => vout.addEventListener("ended", r, {once:true})),
+      new Promise(r => v.addEventListener("ended", r, {once:true})),
+    ]),
+    new Promise((res, rej) => vout.addEventListener("error", rej, {once:true})),
+    new Promise((res, rej) => v.addEventListener("error", rej, {once:true})),
+  ]).then(() => checkEnded(), e => {
+    ok(false, `Error: ${e.target.id} ${token}, ${e.target.error.message}`);
+    manager.finished(token);
+  });
 
   document.body.appendChild(vout);
 
-  var onloadedmetadata = function (ev) {
+  var onloadedmetadata = async function (ev) {
     stream = v.mozCaptureStreamUntilEnded();
     vout.srcObject = stream;
-    is(vout.srcObject, stream, test.name + " set output element .srcObject correctly");
+    is(vout.srcObject, stream,
+      `${token} set output element .srcObject correctly`);
+    // Wait for the resource fetch algorithm to have run, so that the media
+    // element is hooked up to the MediaStream and ready to go. If we don't do
+    // this, we're not guaranteed to render the very first video frame, which
+    // can make this test fail the drawImage test when a video resource only
+    // contains one frame.
+    await new Promise(r => vout.addEventListener('loadstart', r));
     v.play();
     vout.play();
   }
 
   v.preload = 'metadata';
   v.addEventListener('loadedmetadata', onloadedmetadata);
 
   // Log events for debugging.
   var events = ["suspend", "play", "canplay", "canplaythrough", "loadstart", "loadedmetadata",
                 "loadeddata", "playing", "ended", "error", "stalled", "emptied", "abort",
                 "waiting", "pause"];
   function logEvent(e) {
-    Log(e.target.name, "got " + e.type);
+    Log(token, `${e.target.id} got ${e.type}`);
   }
   events.forEach(function(e) {
     v.addEventListener(e, logEvent);
     vout.addEventListener(e, logEvent);
   });
 
 }
 
-// We only test one playable video because for some of the audio files
-// --- small-shot.mp3.mp4 and small-shot.m4a --- GStreamer doesn't decode
-// as much data as indicated by the duration, causing this test to fail on
-// Linux. See bug 1084185.
-var testVideo = getPlayableVideo(gSmallTests);
-if (testVideo) {
-  SpecialPowers.pushPrefEnv(
-    { "set": [["privacy.reduceTimerPrecision", false]]},
-    function() {
-      startTest(testVideo);
-    });
-} else {
-  todo(false, "No playable video");
-}
+(async () => {
+  SimpleTest.requestCompleteLog();
+  SimpleTest.waitForExplicitFinish();
+  await SpecialPowers.pushPrefEnv(
+    { "set": [
+      ["privacy.reduceTimerPrecision", false],
+      // This test exhibits bug 1543980 with RDD enabled.
+      ["media.rdd-process.enabled", false],
+    ]});
+  let tests = gPlayTests;
+  // Filter out bug1377278.webm due to bug 1541401.
+  tests = tests.filter(t => !t.name.includes("1377278"));
+  // Filter out ambisonics.mp4 due to bug 1546655.
+  tests = tests.filter(t => !t.name.includes("ambisonics"));
+  manager.runTests(tests, startTest);
+})();
 </script>
 </pre>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/dom/media/test/test_streams_element_capture_twice.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test that capturing a media element, then reloading and capturing again, works</title>
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<video id="v"></video>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+let v = document.getElementById('v');
+
+function dumpEvent(event) {
+  let v = event.target;
+  info(v.name + " GOT EVENT " + event.type +
+       " currentTime=" + v.currentTime +
+       " paused=" + v.paused +
+       " ended=" + v.ended +
+       " readyState=" + v.readyState);
+}
+
+let events = ["timeupdate", "seeking", "seeked", "ended", "playing", "pause"];
+for (let i = 0; i < events.length; ++i) {
+  v.addEventListener(events[i], dumpEvent);
+}
+
+async function startTest(src) {
+  v.preload = "metadata";
+  v.src = src;
+  await new Promise(r => v.onloadedmetadata = r);
+  let s1 = v.mozCaptureStream();
+  is(s1.getTracks().length, 2, "Expected total tracks, s1, capture 1");
+  is(s1.getAudioTracks().length, 1, "Expected audio tracks, s1, capture 1");
+  is(s1.getVideoTracks().length, 1, "Expected video tracks, s1, capture 1");
+  is(s1.getAudioTracks()[0].readyState, "live", "Live audio, s1, capture 1");
+  is(s1.getVideoTracks()[0].readyState, "live", "Live video, s1, capture 1");
+
+  v.src = null;
+
+  v.src = src;
+  await new Promise(r => v.onloadedmetadata = r);
+  is(s1.getTracks().length, 4, "Expected total tracks, s1, metadata 2");
+  is(s1.getAudioTracks().length, 2, "Expected audio tracks, s1, metadata 2");
+  is(s1.getVideoTracks().length, 2, "Expected video tracks, s1, metadata 2");
+  is(s1.getAudioTracks()[0].readyState, "ended", "Ended audio, s1, metadata 2");
+  is(s1.getAudioTracks()[1].readyState, "live", "Live audio, s1, metadata 2");
+  is(s1.getVideoTracks()[0].readyState, "ended", "Ended video, s1, metadata 2");
+  is(s1.getVideoTracks()[1].readyState, "live", "Live video, s1, metadata 2");
+
+  let s2 = v.mozCaptureStream();
+  is(s1.getTracks().length, 4, "Expected total tracks remains, s1, capture 2");
+  is(s2.getTracks().length, 2, "Expected total tracks, s2, capture 2");
+  is(s2.getAudioTracks().length, 1, "Expected audio tracks, s2, capture 2");
+  is(s2.getVideoTracks().length, 1, "Expected video tracks, s2, capture 2");
+  is(s2.getAudioTracks()[0].readyState, "live", "Live audio, s2, capture 2");
+  is(s2.getVideoTracks()[0].readyState, "live", "Live video, s2, capture 2");
+}
+
+(async function() {
+  try {
+    await startTest("short-video.ogv");
+  } catch(e) {
+    ok(false, `Caught error: ${e}${e.stack ? '\n' + e.stack : ''}`);
+  } finally {
+    SimpleTest.finish();
+  }
+})();
+</script>
+</pre>
+</body>
+</html>
--- a/dom/media/webaudio/AudioNodeStream.cpp
+++ b/dom/media/webaudio/AudioNodeStream.cpp
@@ -38,18 +38,16 @@ AudioNodeStream::AudioNodeStream(AudioNo
       mIsActive(aEngine->IsActive()),
       mMarkAsFinishedAfterThisBlock(false),
       mAudioParamStream(false),
       mPassThrough(false) {
   MOZ_ASSERT(NS_IsMainThread());
   mSuspendedCount = !(mIsActive || mFlags & EXTERNAL_OUTPUT);
   mChannelCountMode = ChannelCountMode::Max;
   mChannelInterpretation = ChannelInterpretation::Speakers;
-  // AudioNodes are always producing data
-  mHasCurrentData = true;
   mLastChunks.SetLength(std::max(uint16_t(1), mEngine->OutputCount()));
   MOZ_COUNT_CTOR(AudioNodeStream);
 }
 
 AudioNodeStream::~AudioNodeStream() {
   MOZ_ASSERT(mActiveInputCount == 0);
   MOZ_COUNT_DTOR(AudioNodeStream);
 }
--- a/dom/xbl/moz.build
+++ b/dom/xbl/moz.build
@@ -28,18 +28,16 @@ UNIFIED_SOURCES += [
     'nsXBLDocumentInfo.cpp',
     'nsXBLEventHandler.cpp',
     'nsXBLProtoImpl.cpp',
     'nsXBLProtoImplField.cpp',
     'nsXBLProtoImplMethod.cpp',
     'nsXBLProtoImplProperty.cpp',
     'nsXBLPrototypeBinding.cpp',
     'nsXBLPrototypeHandler.cpp',
-    'nsXBLPrototypeResources.cpp',
-    'nsXBLResourceLoader.cpp',
     'nsXBLSerialize.cpp',
     'nsXBLService.cpp',
     'nsXBLWindowKeyHandler.cpp',
     'XBLChildrenElement.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/dom/base',
--- a/dom/xbl/nsBindingManager.cpp
+++ b/dom/xbl/nsBindingManager.cpp
@@ -580,50 +580,16 @@ nsresult nsBindingManager::GetBindingImp
       return rv;
     }
   }
 
   *aResult = nullptr;
   return NS_NOINTERFACE;
 }
 
-bool nsBindingManager::EnumerateBoundContentProtoBindings(
-    const BoundContentProtoBindingCallback& aCallback) const {
-  if (!mBoundContentSet) {
-    return true;
-  }
-
-  nsTHashtable<nsPtrHashKey<nsXBLPrototypeBinding>> bindings;
-  for (auto iter = mBoundContentSet->Iter(); !iter.Done(); iter.Next()) {
-    nsIContent* boundContent = iter.Get()->GetKey();
-    for (nsXBLBinding* binding = boundContent->GetXBLBinding(); binding;
-         binding = binding->GetBaseBinding()) {
-      nsXBLPrototypeBinding* proto = binding->PrototypeBinding();
-      // If we have already invoked the callback with a binding, we
-      // should have also invoked it for all its base bindings, so we
-      // don't need to continue this loop anymore.
-      if (!bindings.EnsureInserted(proto)) {
-        break;
-      }
-      if (!aCallback(proto)) {
-        return false;
-      }
-    }
-  }
-
-  return true;
-}
-
-void nsBindingManager::AppendAllSheets(nsTArray<StyleSheet*>& aArray) {
-  EnumerateBoundContentProtoBindings([&aArray](nsXBLPrototypeBinding* aProto) {
-    aProto->AppendStyleSheetsTo(aArray);
-    return true;
-  });
-}
-
 static void InsertAppendedContent(XBLChildrenElement* aPoint,
                                   nsIContent* aFirstNewContent) {
   int32_t insertionIndex;
   if (nsIContent* prevSibling = aFirstNewContent->GetPreviousSibling()) {
     // If we have a previous sibling, then it must already be in aPoint. Find
     // it and insert after it.
     insertionIndex = aPoint->IndexOfInsertedChild(prevSibling);
     MOZ_ASSERT(insertionIndex != -1);
--- a/dom/xbl/nsBindingManager.h
+++ b/dom/xbl/nsBindingManager.h
@@ -15,17 +15,16 @@
 #include "nsRefPtrHashtable.h"
 #include "nsURIHashKey.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsXBLBinding.h"
 #include "nsTArray.h"
 #include "nsThreadUtils.h"
 #include "mozilla/MediaFeatureChange.h"
 #include "mozilla/MemoryReporting.h"
-#include "mozilla/StyleSheet.h"
 #include "mozilla/EventStates.h"
 
 struct ElementDependentRuleProcessorData;
 class nsIXPConnectWrappedJS;
 class nsAtom;
 class nsIURI;
 class nsXBLDocumentInfo;
 class nsIStreamListener;
@@ -112,18 +111,16 @@ class nsBindingManager final : public ns
   nsIStreamListener* GetLoadingDocListener(nsIURI* aURL);
   void RemoveLoadingDocListener(nsIURI* aURL);
 
   void FlushSkinBindings();
 
   nsresult GetBindingImplementation(nsIContent* aContent, REFNSIID aIID,
                                     void** aResult);
 
-  void AppendAllSheets(nsTArray<mozilla::StyleSheet*>& aArray);
-
   void Traverse(nsIContent* aContent, nsCycleCollectionTraversalCallback& cb);
 
   NS_DECL_CYCLE_COLLECTION_CLASS(nsBindingManager)
 
   // Notify the binding manager when an outermost update begins and
   // ends.  The end method can execute script.
   void BeginOutermostUpdate() {
     mAttachedStackSizeOnOutermost = mAttachedStack.Length();
@@ -143,24 +140,16 @@ class nsBindingManager final : public ns
   // Called when the document is going away
   void DropDocumentReference();
 
   nsIContent* FindNestedSingleInsertionPoint(nsIContent* aContainer,
                                              bool* aMulti);
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
-  // Enumerate each bound content's bindings (including its base bindings)
-  // in mBoundContentSet. Return false from the callback to stop enumeration.
-  using BoundContentProtoBindingCallback =
-      std::function<bool(nsXBLPrototypeBinding*)>;
-
-  bool EnumerateBoundContentProtoBindings(
-      const BoundContentProtoBindingCallback&) const;
-
  protected:
   nsIXPConnectWrappedJS* GetWrappedJS(nsIContent* aContent);
   nsresult SetWrappedJS(nsIContent* aContent, nsIXPConnectWrappedJS* aResult);
 
   // Called by ContentAppended and ContentInserted to handle a single child
   // insertion.  aChild must not be null.  aContainer may be null.
   // aAppend is true if this child is being appended, not inserted.
   void HandleChildInsertion(nsIContent* aContainer, nsIContent* aChild,
--- a/dom/xbl/nsXBLBinding.cpp
+++ b/dom/xbl/nsXBLBinding.cpp
@@ -205,24 +205,16 @@ void nsXBLBinding::UnbindAnonymousConten
   }
 }
 
 void nsXBLBinding::SetBoundElement(Element* aElement) {
   mBoundElement = aElement;
   if (mNextBinding) mNextBinding->SetBoundElement(aElement);
 }
 
-bool nsXBLBinding::HasStyleSheets() const {
-  // Find out if we need to re-resolve style.  We'll need to do this
-  // if we have additional stylesheets in our binding document.
-  if (mPrototypeBinding->HasStyleSheets()) return true;
-
-  return mNextBinding ? mNextBinding->HasStyleSheets() : false;
-}
-
 void nsXBLBinding::GenerateAnonymousContent() {
   NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
                "Someone forgot a script blocker");
 
   // Fetch the content element for this binding.
   Element* content = mPrototypeBinding->GetImmediateChild(nsGkAtoms::content);
 
   if (!content) {
@@ -686,34 +678,16 @@ void nsXBLBinding::ChangeDocument(Docume
     if (mContent) {
       nsXBLBinding::UnbindAnonymousContent(aOldDocument, mContent);
     }
 
     ClearInsertionPoints();
   }
 }
 
-bool nsXBLBinding::InheritsStyle() const {
-  // XXX Will have to change if we ever allow multiple bindings to contribute
-  // anonymous content. Most derived binding with anonymous content determines
-  // style inheritance for now.
-
-  // XXX What about bindings with <content> but no kids, e.g., my treecell-text
-  // binding?
-  if (mContent) return mPrototypeBinding->InheritsStyle();
-
-  if (mNextBinding) return mNextBinding->InheritsStyle();
-
-  return true;
-}
-
-const RawServoAuthorStyles* nsXBLBinding::GetServoStyles() const {
-  return mPrototypeBinding->GetServoStyles();
-}
-
 // Internal helper methods /////////////////////////////////////////////////////
 
 // Get or create a WeakMap object on a given XBL-hosting global.
 //
 // The scheme is as follows. XBL-hosting globals (either privileged content
 // Windows or XBL scopes) get two lazily-defined WeakMap properties. Each
 // WeakMap is keyed by the grand-proto - i.e. the original prototype of the
 // content before it was bound, and the prototype of the class object that we
--- a/dom/xbl/nsXBLBinding.h
+++ b/dom/xbl/nsXBLBinding.h
@@ -87,18 +87,16 @@ class nsXBLBinding final {
                             JS::Handle<jsid> aNameAsId,
                             JS::MutableHandle<JS::PropertyDescriptor> aDesc,
                             JS::Handle<JSObject*> aXBLScope);
 
  public:
   void MarkForDeath();
   bool MarkedForDeath() const { return mMarkedForDeath; }
 
-  bool HasStyleSheets() const;
-  bool InheritsStyle() const;
   bool ImplementsInterface(REFNSIID aIID) const;
 
   void GenerateAnonymousContent();
   void BindAnonymousContent(nsIContent* aAnonParent, nsIContent* aElement);
   static void UnbindAnonymousContent(mozilla::dom::Document* aDocument,
                                      nsIContent* aAnonParent,
                                      bool aNullParent = true);
   void InstallEventHandlers();
--- a/dom/xbl/nsXBLContentSink.cpp
+++ b/dom/xbl/nsXBLContentSink.cpp
@@ -250,19 +250,16 @@ nsXBLContentSink::HandleEndElement(const
         }
       } else if (mState == eXBL_InHandlers) {
         if (localName == nsGkAtoms::handlers) {
           mState = eXBL_InBinding;
           mHandler = nullptr;
         } else if (localName == nsGkAtoms::handler)
           mSecondaryState = eXBL_None;
         return NS_OK;
-      } else if (mState == eXBL_InResources) {
-        if (localName == nsGkAtoms::resources) mState = eXBL_InBinding;
-        return NS_OK;
       } else if (mState == eXBL_InImplementation) {
         if (localName == nsGkAtoms::implementation)
           mState = eXBL_InBinding;
         else if (localName == nsGkAtoms::property) {
           mSecondaryState = eXBL_None;
           mProperty = nullptr;
         } else if (localName == nsGkAtoms::method) {
           mSecondaryState = eXBL_None;
@@ -366,26 +363,16 @@ bool nsXBLContentSink::OnOpenContainer(c
     ENSURE_XBL_STATE(mState == eXBL_InBinding && mBinding);
     mState = eXBL_InHandlers;
     ret = false;
   } else if (aTagName == nsGkAtoms::handler) {
     ENSURE_XBL_STATE(mState == eXBL_InHandlers);
     mSecondaryState = eXBL_InHandler;
     ConstructHandler(aAtts, aLineNumber);
     ret = false;
-  } else if (aTagName == nsGkAtoms::resources) {
-    ENSURE_XBL_STATE(mState == eXBL_InBinding && mBinding);
-    mState = eXBL_InResources;
-    // Note that this mState will cause us to return false, so no need
-    // to set ret to false.
-  } else if (aTagName == nsGkAtoms::stylesheet ||
-             aTagName == nsGkAtoms::image) {
-    ENSURE_XBL_STATE(mState == eXBL_InResources);
-    NS_ASSERTION(mBinding, "Must have binding here");
-    ConstructResource(aAtts, aTagName);
   } else if (aTagName == nsGkAtoms::implementation) {
     ENSURE_XBL_STATE(mState == eXBL_InBinding && mBinding);
     mState = eXBL_InImplementation;
     ConstructImplementation(aAtts);
     // Note that this mState will cause us to return false, so no need
     // to set ret to false.
   } else if (aTagName == nsGkAtoms::constructor) {
     ENSURE_XBL_STATE(mState == eXBL_InImplementation &&
@@ -457,17 +444,17 @@ bool nsXBLContentSink::OnOpenContainer(c
   } else if (aTagName == nsGkAtoms::body) {
     ENSURE_XBL_STATE(mSecondaryState == eXBL_InMethod && mMethod);
     NS_ASSERTION(mState == eXBL_InImplementation, "Unexpected state");
     // stash away the line number
     mMethod->SetLineNumber(aLineNumber);
     mSecondaryState = eXBL_InBody;
   }
 
-  return ret && mState != eXBL_InResources && mState != eXBL_InImplementation;
+  return ret && mState != eXBL_InImplementation;
 }
 
 #undef ENSURE_XBL_STATE
 
 nsresult nsXBLContentSink::ConstructBinding(uint32_t aLineNumber) {
   // This is only called from HandleStartElement, so it'd better be an element.
   RefPtr<Element> binding = GetCurrentContent()->AsElement();
   binding->GetAttr(kNameSpaceID_None, nsGkAtoms::id, mCurrentBindingID);
@@ -599,26 +586,16 @@ void nsXBLContentSink::ConstructHandler(
     // We're the first handler in the chain.
     mBinding->SetPrototypeHandlers(newHandler);
   }
   // Adjust our mHandler pointer to point to the new last handler in the
   // chain.
   mHandler = newHandler;
 }
 
-void nsXBLContentSink::ConstructResource(const char16_t** aAtts,
-                                         nsAtom* aResourceType) {
-  if (!mBinding) return;
-
-  const char16_t* src = nullptr;
-  if (FindValue(aAtts, nsGkAtoms::src, &src)) {
-    mBinding->AddResource(aResourceType, nsDependentString(src));
-  }
-}
-
 void nsXBLContentSink::ConstructImplementation(const char16_t** aAtts) {
   mImplementation = nullptr;
   mImplMember = nullptr;
   mImplField = nullptr;
 
   if (!mBinding) return;
 
   const char16_t* name = nullptr;
--- a/dom/xbl/nsXBLContentSink.h
+++ b/dom/xbl/nsXBLContentSink.h
@@ -16,17 +16,16 @@
 
 /*
  * Enum that describes the primary state of the parsing process
  */
 typedef enum {
   eXBL_InDocument,       /* outside any bindings */
   eXBL_InBindings,       /* Inside a <bindings> element */
   eXBL_InBinding,        /* Inside a <binding> */
-  eXBL_InResources,      /* Inside a <resources> */
   eXBL_InImplementation, /* Inside a <implementation> */
   eXBL_InHandlers,       /* Inside a <handlers> */
   eXBL_Error /* An error has occurred.  Suspend binding construction */
 } XBLPrimaryState;
 
 /*
  * Enum that describes our substate (typically when parsing something
  * like <handlers> or <implementation>).
@@ -84,28 +83,28 @@ class nsXBLContentSink : public nsXMLCon
   bool NotifyForDocElement() override { return false; }
 
   nsresult CreateElement(const char16_t** aAtts, uint32_t aAttsCount,
                          mozilla::dom::NodeInfo* aNodeInfo,
                          uint32_t aLineNumber, uint32_t aColumnNumber,
                          nsIContent** aResult, bool* aAppendContent,
                          mozilla::dom::FromParser aFromParser) override;
 
-  nsresult AddAttributes(const char16_t** aAtts, Element* aElement) override;
+  nsresult AddAttributes(const char16_t** aAtts,
+                         mozilla::dom::Element* aElement) override;
 
 #ifdef MOZ_XUL
   nsresult AddAttributesToXULPrototype(const char16_t** aAtts,
                                        uint32_t aAttsCount,
                                        nsXULPrototypeElement* aElement);
 #endif
 
   // Our own helpers for constructing XBL prototype objects.
   nsresult ConstructBinding(uint32_t aLineNumber);
   void ConstructHandler(const char16_t** aAtts, uint32_t aLineNumber);
-  void ConstructResource(const char16_t** aAtts, nsAtom* aResourceType);
   void ConstructImplementation(const char16_t** aAtts);
   void ConstructProperty(const char16_t** aAtts, uint32_t aLineNumber);
   void ConstructMethod(const char16_t** aAtts);
   void ConstructParameter(const char16_t** aAtts);
   void ConstructField(const char16_t** aAtts, uint32_t aLineNumber);
 
   // nsXMLContentSink overrides
   nsresult FlushText(bool aReleaseTextNode = true) override;
--- a/dom/xbl/nsXBLDocumentInfo.cpp
+++ b/dom/xbl/nsXBLDocumentInfo.cpp
@@ -280,24 +280,16 @@ nsresult nsXBLDocumentInfo::WritePrototy
   return startupCache->PutBuffer(spec.get(), std::move(buf), len);
 }
 
 void nsXBLDocumentInfo::SetFirstPrototypeBinding(
     nsXBLPrototypeBinding* aBinding) {
   mFirstBinding = aBinding;
 }
 
-void nsXBLDocumentInfo::FlushSkinStylesheets() {
-  if (mBindingTable) {
-    for (auto iter = mBindingTable->Iter(); !iter.Done(); iter.Next()) {
-      iter.UserData()->FlushSkinSheets();
-    }
-  }
-}
-
 #ifdef DEBUG
 void AssertInCompilationScope() {
   AutoJSContext cx;
   MOZ_ASSERT(xpc::CompilationScope() == JS::CurrentGlobalOrNull(cx));
 }
 #endif
 
 size_t nsXBLDocumentInfo::SizeOfIncludingThis(
--- a/dom/xbl/nsXBLDocumentInfo.h
+++ b/dom/xbl/nsXBLDocumentInfo.h
@@ -35,18 +35,16 @@ class nsXBLDocumentInfo final : public n
 
   // This removes the binding without deleting it
   void RemovePrototypeBinding(const nsACString& aRef);
 
   nsresult WritePrototypeBindings();
 
   void SetFirstPrototypeBinding(nsXBLPrototypeBinding* aBinding);
 
-  void FlushSkinStylesheets();
-
   bool IsChrome() { return mIsChrome; }
 
   void MarkInCCGeneration(uint32_t aGeneration);
 
   static nsresult ReadPrototypeBindings(nsIURI* aURI,
                                         nsXBLDocumentInfo** aDocInfo,
                                         mozilla::dom::Document* aBoundDocument);
 
--- a/dom/xbl/nsXBLPrototypeBinding.cpp
+++ b/dom/xbl/nsXBLPrototypeBinding.cpp
@@ -32,23 +32,20 @@
 #include "nsGkAtoms.h"
 #include "nsXBLProtoImpl.h"
 #include "nsCRT.h"
 #include "nsContentUtils.h"
 #include "nsTextFragment.h"
 #include "nsTextNode.h"
 #include "nsIScriptError.h"
 
-#include "nsXBLResourceLoader.h"
 #include "mozilla/dom/CDATASection.h"
 #include "mozilla/dom/CharacterData.h"
 #include "mozilla/dom/Comment.h"
 #include "mozilla/dom/Element.h"
-#include "mozilla/StyleSheet.h"
-#include "mozilla/StyleSheetInlines.h"
 
 #ifdef MOZ_XUL
 #  include "nsXULElement.h"
 #endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
@@ -105,22 +102,20 @@ size_t nsXBLAttributeEntry::SizeOfInclud
 // =============================================================================
 
 // Implementation //////////////////////////////////////////////////////////////
 
 // Constructors/Destructors
 nsXBLPrototypeBinding::nsXBLPrototypeBinding()
     : mImplementation(nullptr),
       mBaseBinding(nullptr),
-      mInheritStyle(true),
       mCheckedBaseProto(false),
       mKeyHandlersRegistered(false),
       mBindToUntrustedContent(false),
       mSimpleScopeChain(false),
-      mResources(nullptr),
       mXBLDocInfoWeak(nullptr) {
   MOZ_COUNT_CTOR(nsXBLPrototypeBinding);
 }
 
 nsresult nsXBLPrototypeBinding::Init(const nsACString& aID,
                                      nsXBLDocumentInfo* aInfo,
                                      Element* aElement, bool aFirstBinding) {
   nsresult rv;
@@ -155,30 +150,23 @@ bool nsXBLPrototypeBinding::CompareBindi
   }
   return equal;
 }
 
 void nsXBLPrototypeBinding::Traverse(
     nsCycleCollectionTraversalCallback& cb) const {
   NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "proto mBinding");
   cb.NoteXPCOMChild(mBinding);
-  if (mResources) {
-    mResources->Traverse(cb);
-  }
   ImplCycleCollectionTraverse(cb, mInterfaceTable, "proto mInterfaceTable");
 }
 
 void nsXBLPrototypeBinding::Unlink() {
   if (mImplementation) {
     mImplementation->UnlinkJSObjects();
   }
-
-  if (mResources) {
-    mResources->Unlink();
-  }
 }
 
 void nsXBLPrototypeBinding::Trace(const TraceCallbacks& aCallbacks,
                                   void* aClosure) const {
   if (mImplementation) mImplementation->Trace(aCallbacks, aClosure);
 }
 
 void nsXBLPrototypeBinding::Initialize() {
@@ -200,55 +188,31 @@ void nsXBLPrototypeBinding::SetBaseProto
     return;
   }
 
   mBaseBinding = aBinding;
 }
 
 void nsXBLPrototypeBinding::SetBindingElement(Element* aElement) {
   mBinding = aElement;
-  if (mBinding->AttrValueIs(kNameSpaceID_None, nsGkAtoms::inheritstyle,
-                            nsGkAtoms::_false, eCaseMatters))
-    mInheritStyle = false;
 
   mBindToUntrustedContent = mBinding->AttrValueIs(
       kNameSpaceID_None, nsGkAtoms::bindToUntrustedContent, nsGkAtoms::_true,
       eCaseMatters);
 
   // TODO(emilio): Should we imply mBindToUntrustedContent -> mSimpleScopeChain?
   mSimpleScopeChain =
       mBinding->AttrValueIs(kNameSpaceID_None, nsGkAtoms::simpleScopeChain,
                             nsGkAtoms::_true, eCaseMatters);
 }
 
 bool nsXBLPrototypeBinding::GetAllowScripts() const {
   return mXBLDocInfoWeak->GetScriptAccess();
 }
 
-bool nsXBLPrototypeBinding::LoadResources(nsIContent* aBoundElement) {
-  if (mResources) {
-    return mResources->LoadResources(aBoundElement);
-  }
-
-  return true;
-}
-
-nsresult nsXBLPrototypeBinding::AddResource(nsAtom* aResourceType,
-                                            const nsAString& aSrc) {
-  EnsureResources();
-
-  mResources->AddResource(aResourceType, aSrc);
-  return NS_OK;
-}
-
-nsresult nsXBLPrototypeBinding::FlushSkinSheets() {
-  if (mResources) return mResources->FlushSkinSheets();
-  return NS_OK;
-}
-
 nsresult nsXBLPrototypeBinding::BindingAttached(nsIContent* aBoundElement) {
   if (mImplementation && mImplementation->CompiledMembers() &&
       mImplementation->mConstructor)
     return mImplementation->mConstructor->Execute(aBoundElement, *this);
   return NS_OK;
 }
 
 nsresult nsXBLPrototypeBinding::BindingDetached(nsIContent* aBoundElement) {
@@ -636,25 +600,16 @@ nsresult nsXBLPrototypeBinding::Construc
 
       token = nsCRT::strtok(newStr, ", ", &newStr);
     }
   }
 
   return NS_OK;
 }
 
-nsresult nsXBLPrototypeBinding::AddResourceListener(nsIContent* aBoundElement) {
-  if (!mResources)
-    return NS_ERROR_FAILURE;  // Makes no sense to add a listener when the
-                              // binding has no resources.
-
-  mResources->AddResourceListener(aBoundElement);
-  return NS_OK;
-}
-
 void nsXBLPrototypeBinding::CreateKeyHandlers() {
   nsXBLPrototypeHandler* curr = mPrototypeHandler;
   while (curr) {
     RefPtr<nsAtom> eventAtom = curr->GetEventName();
     if (eventAtom == nsGkAtoms::keyup || eventAtom == nsGkAtoms::keydown ||
         eventAtom == nsGkAtoms::keypress) {
       uint8_t phase = curr->GetPhase();
       uint8_t type = curr->GetType();
@@ -696,17 +651,16 @@ class XBLPrototypeSetupCleanup {
 
   nsXBLDocumentInfo* mDocInfo;
   nsAutoCString mID;
 };
 
 nsresult nsXBLPrototypeBinding::Read(nsIObjectInputStream* aStream,
                                      nsXBLDocumentInfo* aDocInfo,
                                      Document* aDocument, uint8_t aFlags) {
-  mInheritStyle = (aFlags & XBLBinding_Serialize_InheritStyle) ? true : false;
   mBindToUntrustedContent =
       (aFlags & XBLBinding_Serialize_BindToUntrustedContent) ? true : false;
   mSimpleScopeChain =
       (aFlags & XBLBinding_Serialize_SimpleScopeChain) ? true : false;
 
   // nsXBLContentSink::ConstructBinding doesn't create a binding with an empty
   // id, so we don't here either.
   nsAutoCString id;
@@ -839,38 +793,16 @@ nsresult nsXBLPrototypeBinding::Read(nsI
       NS_ENSURE_SUCCESS(rv, rv);
 
       RefPtr<nsAtom> atomPrefix = NS_Atomize(attrPrefix);
       RefPtr<nsAtom> atomName = NS_Atomize(attrName);
       mBinding->SetAttr(attrNamespace, atomName, atomPrefix, attrValue, false);
     }
   }
 
-  // Finally, read in the resources.
-  while (true) {
-    XBLBindingSerializeDetails type;
-    rv = aStream->Read8(&type);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    if (type == XBLBinding_Serialize_NoMoreItems) break;
-
-    NS_ASSERTION(
-        (type & XBLBinding_Serialize_Mask) == XBLBinding_Serialize_Stylesheet ||
-            (type & XBLBinding_Serialize_Mask) == XBLBinding_Serialize_Image,
-        "invalid resource type");
-
-    nsAutoString src;
-    rv = aStream->ReadString(src);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    AddResource(type == XBLBinding_Serialize_Stylesheet ? nsGkAtoms::stylesheet
-                                                        : nsGkAtoms::image,
-                src);
-  }
-
   if (isFirstBinding) {
     aDocInfo->SetFirstPrototypeBinding(this);
   }
 
   cleanup.Disconnect();
   return NS_OK;
 }
 
@@ -896,17 +828,17 @@ nsresult nsXBLPrototypeBinding::Write(ns
 
   // We're not directly using this AutoJSAPI here, but callees use it via
   // AutoJSContext.
   AutoJSAPI jsapi;
   if (!jsapi.Init(xpc::CompilationScope())) {
     return NS_ERROR_UNEXPECTED;
   }
 
-  uint8_t flags = mInheritStyle ? XBLBinding_Serialize_InheritStyle : 0;
+  uint8_t flags = 0;
 
   // mAlternateBindingURI is only set on the first binding.
   if (mAlternateBindingURI) {
     flags |= XBLBinding_Serialize_IsFirstBinding;
   }
 
   if (mBindToUntrustedContent) {
     flags |= XBLBinding_Serialize_BindToUntrustedContent;
@@ -1005,25 +937,16 @@ nsresult nsXBLPrototypeBinding::Write(ns
       rv = aStream->WriteWStringZ(attrName.get());
       NS_ENSURE_SUCCESS(rv, rv);
 
       rv = aStream->WriteWStringZ(attrValue.get());
       NS_ENSURE_SUCCESS(rv, rv);
     }
   }
 
-  aStream->Write8(XBLBinding_Serialize_NoMoreItems);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // Write out the resources
-  if (mResources) {
-    rv = mResources->Write(aStream);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
   // Write out an end mark at the end.
   return aStream->Write8(XBLBinding_Serialize_NoMoreItems);
 }
 
 nsresult nsXBLPrototypeBinding::ReadContentNode(nsIObjectInputStream* aStream,
                                                 Document* aDocument,
                                                 nsNodeInfoManager* aNim,
                                                 nsIContent** aContent) {
@@ -1382,67 +1305,21 @@ nsresult nsXBLPrototypeBinding::ResolveB
   if (extends.IsEmpty()) {
     return NS_OK;
   }
 
   return NS_NewURI(getter_AddRefs(mBaseBindingURI), extends,
                    doc->GetDocumentCharacterSet(), doc->GetDocBaseURI());
 }
 
-void nsXBLPrototypeBinding::EnsureResources() {
-  if (!mResources) {
-    mResources = new nsXBLPrototypeResources(this);
-  }
-}
-
-void nsXBLPrototypeBinding::AppendStyleSheet(StyleSheet* aSheet) {
-  EnsureResources();
-  mResources->AppendStyleSheet(aSheet);
-}
-
-void nsXBLPrototypeBinding::RemoveStyleSheet(StyleSheet* aSheet) {
-  if (!mResources) {
-    MOZ_ASSERT(false, "Trying to remove a sheet that does not exist.");
-    return;
-  }
-
-  mResources->RemoveStyleSheet(aSheet);
-}
-void nsXBLPrototypeBinding::InsertStyleSheetAt(size_t aIndex,
-                                               StyleSheet* aSheet) {
-  EnsureResources();
-  mResources->InsertStyleSheetAt(aIndex, aSheet);
-}
-
-StyleSheet* nsXBLPrototypeBinding::StyleSheetAt(size_t aIndex) const {
-  MOZ_ASSERT(mResources);
-  return mResources->StyleSheetAt(aIndex);
-}
-
-size_t nsXBLPrototypeBinding::SheetCount() const {
-  return mResources ? mResources->SheetCount() : 0;
-}
-
-bool nsXBLPrototypeBinding::HasStyleSheets() const {
-  return mResources && mResources->HasStyleSheets();
-}
-
-void nsXBLPrototypeBinding::AppendStyleSheetsTo(
-    nsTArray<StyleSheet*>& aResult) const {
-  if (mResources) {
-    mResources->AppendStyleSheetsTo(aResult);
-  }
-}
-
 size_t nsXBLPrototypeBinding::SizeOfIncludingThis(
     MallocSizeOf aMallocSizeOf) const {
   size_t n = aMallocSizeOf(this);
   n += mPrototypeHandler ? mPrototypeHandler->SizeOfIncludingThis(aMallocSizeOf)
                          : 0;
-  n += mResources ? mResources->SizeOfIncludingThis(aMallocSizeOf) : 0;
 
   if (mAttributeTable) {
     n += mAttributeTable->ShallowSizeOfIncludingThis(aMallocSizeOf);
     for (auto iter = mAttributeTable->Iter(); !iter.Done(); iter.Next()) {
       InnerAttributeTable* table = iter.UserData();
       n += table->ShallowSizeOfIncludingThis(aMallocSizeOf);
       for (auto iter2 = table->Iter(); !iter2.Done(); iter2.Next()) {
         n += iter2.UserData()->SizeOfIncludingThis(aMallocSizeOf);
--- a/dom/xbl/nsXBLPrototypeBinding.h
+++ b/dom/xbl/nsXBLPrototypeBinding.h
@@ -12,20 +12,18 @@
 #include "nsCOMArray.h"
 #include "nsCOMPtr.h"
 #include "nsICSSLoaderObserver.h"
 #include "nsInterfaceHashtable.h"
 #include "nsXBLDocumentInfo.h"
 #include "nsXBLProtoImpl.h"
 #include "nsXBLProtoImplMethod.h"
 #include "nsXBLPrototypeHandler.h"
-#include "nsXBLPrototypeResources.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/WeakPtr.h"
-#include "mozilla/StyleSheet.h"
 
 class nsAtom;
 class nsIContent;
 
 class nsXBLAttributeEntry;
 class nsXBLBinding;
 class nsXBLProtoImplField;
 
@@ -52,24 +50,16 @@ class nsXBLPrototypeBinding final
   // binding URIs.
   bool CompareBindingURI(nsIURI* aURI) const;
 
   bool GetAllowScripts() const;
 
   nsresult BindingAttached(nsIContent* aBoundElement);
   nsresult BindingDetached(nsIContent* aBoundElement);
 
-  // aBoundElement is passed in here because we need to get owner document
-  // and PresContext in nsXBLResourceLoader::LoadResources().
-  bool LoadResources(nsIContent* aBoundElement);
-  nsresult AddResource(nsAtom* aResourceType, const nsAString& aSrc);
-
-  bool InheritsStyle() const { return mInheritStyle; }
-  void SetInheritsStyle(bool aInheritStyle) { mInheritStyle = aInheritStyle; }
-
   nsXBLPrototypeHandler* GetPrototypeHandlers() { return mPrototypeHandler; }
   void SetPrototypeHandlers(nsXBLPrototypeHandler* aHandler) {
     mPrototypeHandler = aHandler;
   }
 
   nsXBLProtoImplAnonymousMethod* GetConstructor();
   nsresult SetConstructor(nsXBLProtoImplAnonymousMethod* aConstructor);
   nsXBLProtoImplAnonymousMethod* GetDestructor();
@@ -117,52 +107,21 @@ class nsXBLPrototypeBinding final
   nsXBLPrototypeBinding* GetBasePrototype() { return mBaseBinding; }
 
   nsXBLDocumentInfo* XBLDocumentInfo() const { return mXBLDocInfoWeak; }
   bool IsChrome() { return mXBLDocInfoWeak->IsChrome(); }
 
   void SetInitialAttributes(mozilla::dom::Element* aBoundElement,
                             nsIContent* aAnonymousContent);
 
-  void AppendStyleSheet(mozilla::StyleSheet* aSheet);
-  void RemoveStyleSheet(mozilla::StyleSheet* aSheet);
-  void InsertStyleSheetAt(size_t aIndex, mozilla::StyleSheet* aSheet);
-  mozilla::StyleSheet* StyleSheetAt(size_t aIndex) const;
-  size_t SheetCount() const;
-  bool HasStyleSheets() const;
-  void AppendStyleSheetsTo(nsTArray<mozilla::StyleSheet*>& aResult) const;
-
-  const RawServoAuthorStyles* GetServoStyles() const {
-    return mResources ? mResources->GetServoStyles() : nullptr;
-  }
-
-  void SyncServoStyles() {
-    MOZ_ASSERT(mResources);
-    mResources->SyncServoStyles();
-  }
-
-  RawServoAuthorStyles* GetServoStyles() {
-    return mResources
-               ? const_cast<RawServoAuthorStyles*>(mResources->GetServoStyles())
-               : nullptr;
-  }
-
-  mozilla::ServoStyleRuleMap* GetServoStyleRuleMap() {
-    return mResources ? mResources->GetServoStyleRuleMap() : nullptr;
-  }
-
-  nsresult FlushSkinSheets();
-
   nsAtom* GetBaseTag(int32_t* aNamespaceID);
   void SetBaseTag(int32_t aNamespaceID, nsAtom* aTag);
 
   bool ImplementsInterface(REFNSIID aIID) const;
 
-  nsresult AddResourceListener(nsIContent* aBoundElement);
-
   void Initialize();
 
   nsresult ResolveBaseBinding();
 
   const nsCOMArray<nsXBLKeyEventHandler>* GetKeyEventHandlers() {
     if (!mKeyHandlersRegistered) {
       CreateKeyHandlers();
       mKeyHandlersRegistered = true;
@@ -283,19 +242,16 @@ class nsXBLPrototypeBinding final
   void EnsureAttributeTable();
   // Ad an entry to the attribute table
   void AddToAttributeTable(int32_t aSourceNamespaceID, nsAtom* aSourceTag,
                            int32_t aDestNamespaceID, nsAtom* aDestTag,
                            mozilla::dom::Element* aContent);
   void ConstructAttributeTable(mozilla::dom::Element* aElement);
   void CreateKeyHandlers();
 
- private:
-  void EnsureResources();
-
   // MEMBER VARIABLES
  protected:
   nsCOMPtr<nsIURI> mBindingURI;
   nsCOMPtr<nsIURI> mAlternateBindingURI;  // Alternate id-less URI that is only
                                           // non-null on the first binding.
   RefPtr<mozilla::dom::Element>
       mBinding;  // Strong. We own a ref to our content element in the binding
                  // doc.
@@ -306,30 +262,25 @@ class nsXBLPrototypeBinding final
   nsCOMPtr<nsIURI> mBaseBindingURI;
 
   nsXBLProtoImpl* mImplementation;  // Our prototype implementation (includes
                                     // methods, properties, fields, the
                                     // constructor, and the destructor).
 
   // Weak.  The docinfo will own our base binding.
   mozilla::WeakPtr<nsXBLPrototypeBinding> mBaseBinding;
-  // FIXME(emilio): This is dead code now.
-  bool mInheritStyle;
   bool mCheckedBaseProto;
   bool mKeyHandlersRegistered;
   // FIXME(emilio): This is dead code now.
   bool mBindToUntrustedContent;
   // True if constructors, handlers, etc for this binding would skip the scope
   // chain for parent elements and go directly to the document.
   // FIXME(emilio): This is dead code now.
   bool mSimpleScopeChain;
 
-  nsAutoPtr<nsXBLPrototypeResources>
-      mResources;  // If we have any resources, this will be non-null.
-
   nsXBLDocumentInfo* mXBLDocInfoWeak;  // A pointer back to our doc info.  Weak,
                                        // since it owns us.
 
   // A table for attribute containers. Namespace IDs are used as
   // keys in the table. Containers are InnerAttributeTables.
   // This table is used to efficiently handle attribute changes.
   nsAutoPtr<nsClassHashtable<nsUint32HashKey, InnerAttributeTable>>
       mAttributeTable;
deleted file mode 100644
--- a/dom/xbl/nsXBLPrototypeResources.cpp
+++ /dev/null
@@ -1,190 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "mozilla/dom/Document.h"
-#include "nsIContent.h"
-#include "nsIPresShellInlines.h"
-#include "nsIServiceManager.h"
-#include "nsXBLResourceLoader.h"
-#include "nsXBLPrototypeResources.h"
-#include "nsXBLPrototypeBinding.h"
-#include "nsIDocumentObserver.h"
-#include "mozilla/css/Loader.h"
-#include "nsIURI.h"
-#include "nsLayoutCID.h"
-#include "mozilla/dom/URL.h"
-#include "mozilla/DebugOnly.h"
-#include "mozilla/PresShell.h"
-#include "mozilla/ServoBindings.h"
-#include "mozilla/ServoStyleRuleMap.h"
-#include "mozilla/StyleSheet.h"
-#include "mozilla/StyleSheetInlines.h"
-
-using namespace mozilla;
-using mozilla::dom::IsChromeURI;
-
-nsXBLPrototypeResources::nsXBLPrototypeResources(
-    nsXBLPrototypeBinding* aBinding) {
-  MOZ_COUNT_CTOR(nsXBLPrototypeResources);
-
-  mLoader = new nsXBLResourceLoader(aBinding, this);
-}
-
-nsXBLPrototypeResources::~nsXBLPrototypeResources() {
-  MOZ_COUNT_DTOR(nsXBLPrototypeResources);
-  if (mLoader) {
-    mLoader->mResources = nullptr;
-  }
-}
-
-void nsXBLPrototypeResources::AddResource(nsAtom* aResourceType,
-                                          const nsAString& aSrc) {
-  if (mLoader) mLoader->AddResource(aResourceType, aSrc);
-}
-
-bool nsXBLPrototypeResources::LoadResources(nsIContent* aBoundElement) {
-  if (mLoader) {
-    return mLoader->LoadResources(aBoundElement);
-  }
-
-  return true;  // All resources loaded.
-}
-
-void nsXBLPrototypeResources::AddResourceListener(nsIContent* aBoundElement) {
-  if (mLoader) mLoader->AddResourceListener(aBoundElement);
-}
-
-nsresult nsXBLPrototypeResources::FlushSkinSheets() {
-  if (mStyleSheetList.Length() == 0) return NS_OK;
-
-  nsCOMPtr<Document> doc = mLoader->mBinding->XBLDocumentInfo()->GetDocument();
-
-  // If doc is null, we're in the process of tearing things down, so just
-  // return without rebuilding anything.
-  if (!doc) {
-    return NS_OK;
-  }
-
-  // We have scoped stylesheets.  Reload any chrome stylesheets we
-  // encounter.  (If they aren't skin sheets, it doesn't matter, since
-  // they'll still be in the chrome cache.  Skip inline sheets, which
-  // skin sheets can't be, and which in any case don't have a usable
-  // URL to reload.)
-
-  nsTArray<RefPtr<StyleSheet>> oldSheets;
-
-  oldSheets.SwapElements(mStyleSheetList);
-
-  mozilla::css::Loader* cssLoader = doc->CSSLoader();
-
-  for (size_t i = 0, count = oldSheets.Length(); i < count; ++i) {
-    StyleSheet* oldSheet = oldSheets[i];
-
-    nsIURI* uri = oldSheet->GetSheetURI();
-
-    RefPtr<StyleSheet> newSheet;
-    if (!oldSheet->IsInline() && IsChromeURI(uri)) {
-      if (NS_FAILED(cssLoader->LoadSheetSync(uri, &newSheet))) continue;
-    } else {
-      newSheet = oldSheet;
-    }
-
-    mStyleSheetList.AppendElement(newSheet);
-  }
-
-  // There may be no shell during unlink.
-  if (PresShell* presShell = doc->GetPresShell()) {
-    MOZ_ASSERT(presShell->GetPresContext());
-    ComputeServoStyles(*presShell->StyleSet());
-  }
-
-  return NS_OK;
-}
-
-nsresult nsXBLPrototypeResources::Write(nsIObjectOutputStream* aStream) {
-  if (mLoader) return mLoader->Write(aStream);
-  return NS_OK;
-}
-
-void nsXBLPrototypeResources::Traverse(nsCycleCollectionTraversalCallback& cb) {
-  NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "proto mResources mLoader");
-  cb.NoteXPCOMChild(mLoader);
-
-  ImplCycleCollectionTraverse(cb, mStyleSheetList, "mStyleSheetList");
-}
-
-void nsXBLPrototypeResources::Unlink() { mStyleSheetList.Clear(); }
-
-void nsXBLPrototypeResources::ClearLoader() { mLoader = nullptr; }
-
-void nsXBLPrototypeResources::SyncServoStyles() {
-  mStyleRuleMap.reset(nullptr);
-  mServoStyles = Servo_AuthorStyles_Create().Consume();
-  for (auto& sheet : mStyleSheetList) {
-    Servo_AuthorStyles_AppendStyleSheet(mServoStyles.get(), sheet);
-  }
-}
-
-void nsXBLPrototypeResources::ComputeServoStyles(
-    const ServoStyleSet& aMasterStyleSet) {
-  SyncServoStyles();
-  Servo_AuthorStyles_Flush(mServoStyles.get(), aMasterStyleSet.RawSet());
-}
-
-ServoStyleRuleMap* nsXBLPrototypeResources::GetServoStyleRuleMap() {
-  if (!HasStyleSheets() || !mServoStyles) {
-    return nullptr;
-  }
-
-  if (!mStyleRuleMap) {
-    mStyleRuleMap = MakeUnique<ServoStyleRuleMap>();
-  }
-
-  mStyleRuleMap->EnsureTable(*this);
-  return mStyleRuleMap.get();
-}
-
-void nsXBLPrototypeResources::AppendStyleSheet(StyleSheet* aSheet) {
-  mStyleSheetList.AppendElement(aSheet);
-}
-
-void nsXBLPrototypeResources::RemoveStyleSheet(StyleSheet* aSheet) {
-  mStyleSheetList.RemoveElement(aSheet);
-}
-
-void nsXBLPrototypeResources::InsertStyleSheetAt(size_t aIndex,
-                                                 StyleSheet* aSheet) {
-  mStyleSheetList.InsertElementAt(aIndex, aSheet);
-}
-
-void nsXBLPrototypeResources::AppendStyleSheetsTo(
-    nsTArray<StyleSheet*>& aResult) const {
-  aResult.AppendElements(mStyleSheetList);
-}
-
-MOZ_DEFINE_MALLOC_SIZE_OF(ServoAuthorStylesMallocSizeOf)
-MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(ServoAuthorStylesMallocEnclosingSizeOf)
-
-size_t nsXBLPrototypeResources::SizeOfIncludingThis(
-    MallocSizeOf aMallocSizeOf) const {
-  size_t n = aMallocSizeOf(this);
-  n += mStyleSheetList.ShallowSizeOfExcludingThis(aMallocSizeOf);
-  for (const auto& sheet : mStyleSheetList) {
-    n += sheet->SizeOfIncludingThis(aMallocSizeOf);
-  }
-  n += mServoStyles
-           ? Servo_AuthorStyles_SizeOfIncludingThis(
-                 ServoAuthorStylesMallocSizeOf,
-                 ServoAuthorStylesMallocEnclosingSizeOf, mServoStyles.get())
-           : 0;
-  n += mStyleRuleMap ? mStyleRuleMap->SizeOfIncludingThis(aMallocSizeOf) : 0;
-
-  // Measurement of the following members may be added later if DMD finds it
-  // is worthwhile:
-  // - mLoader
-
-  return n;
-}
deleted file mode 100644
--- a/dom/xbl/nsXBLPrototypeResources.h
+++ /dev/null
@@ -1,92 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef nsXBLPrototypeResources_h__
-#define nsXBLPrototypeResources_h__
-
-#include "mozilla/MemoryReporting.h"
-#include "mozilla/StyleSheet.h"
-#include "nsICSSLoaderObserver.h"
-
-class nsAtom;
-class nsIContent;
-class nsXBLPrototypeBinding;
-class nsXBLResourceLoader;
-struct RawServoAuthorStyles;
-
-namespace mozilla {
-class CSSStyleSheet;
-class ServoStyleSet;
-class ServoStyleRuleMap;
-}  // namespace mozilla
-
-// *********************************************************************/
-// The XBLPrototypeResources class
-
-class nsXBLPrototypeResources {
- public:
-  explicit nsXBLPrototypeResources(nsXBLPrototypeBinding* aBinding);
-  ~nsXBLPrototypeResources();
-
-  bool LoadResources(nsIContent* aBoundElement);
-  void AddResource(nsAtom* aResourceType, const nsAString& aSrc);
-  void AddResourceListener(nsIContent* aElement);
-  nsresult FlushSkinSheets();
-
-  nsresult Write(nsIObjectOutputStream* aStream);
-
-  void Traverse(nsCycleCollectionTraversalCallback& cb);
-  void Unlink();
-
-  void ClearLoader();
-
-  void AppendStyleSheet(mozilla::StyleSheet* aSheet);
-  void RemoveStyleSheet(mozilla::StyleSheet* aSheet);
-  void InsertStyleSheetAt(size_t aIndex, mozilla::StyleSheet* aSheet);
-
-  mozilla::StyleSheet* StyleSheetAt(size_t aIndex) const {
-    return mStyleSheetList[aIndex];
-  }
-
-  size_t SheetCount() const { return mStyleSheetList.Length(); }
-
-  bool HasStyleSheets() const { return !mStyleSheetList.IsEmpty(); }
-
-  void AppendStyleSheetsTo(nsTArray<mozilla::StyleSheet*>& aResult) const;
-
-  const RawServoAuthorStyles* GetServoStyles() const {
-    return mServoStyles.get();
-  }
-
-  void SyncServoStyles();
-
-  mozilla::ServoStyleRuleMap* GetServoStyleRuleMap();
-
-  // Updates the ServoStyleSet object that holds the result of cascading the
-  // sheets in mStyleSheetList. Equivalent to GatherRuleProcessor(), but for
-  // the Servo style backend.
-  void ComputeServoStyles(const mozilla::ServoStyleSet& aMasterStyleSet);
-
-  size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
-
- private:
-  // A loader object. Exists only long enough to load resources, and then it
-  // dies.
-  RefPtr<nsXBLResourceLoader> mLoader;
-
-  // A list of loaded stylesheets for this binding.
-  //
-  // FIXME(emilio): Remove when the old style system is gone, defer to
-  // mServoStyles.
-  nsTArray<RefPtr<mozilla::StyleSheet>> mStyleSheetList;
-
-  // The result of cascading the XBL style sheets like mRuleProcessor, but
-  // for the Servo style backend.
-  mozilla::UniquePtr<RawServoAuthorStyles> mServoStyles;
-  mozilla::UniquePtr<mozilla::ServoStyleRuleMap> mStyleRuleMap;
-};
-
-#endif
deleted file mode 100644
--- a/dom/xbl/nsXBLResourceLoader.cpp
+++ /dev/null
@@ -1,251 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "nsTArray.h"
-#include "nsString.h"
-#include "mozilla/dom/Document.h"
-#include "nsIContent.h"
-#include "nsIPresShellInlines.h"
-#include "nsXBLService.h"
-#include "nsIServiceManager.h"
-#include "nsXBLResourceLoader.h"
-#include "nsXBLPrototypeResources.h"
-#include "nsIDocumentObserver.h"
-#include "imgILoader.h"
-#include "imgRequestProxy.h"
-#include "mozilla/ComputedStyle.h"
-#include "mozilla/PresShell.h"
-#include "mozilla/StyleSheet.h"
-#include "mozilla/StyleSheetInlines.h"
-#include "mozilla/css/Loader.h"
-#include "nsIURI.h"
-#include "nsNetUtil.h"
-#include "nsGkAtoms.h"
-#include "nsXBLPrototypeBinding.h"
-#include "nsContentUtils.h"
-#include "nsIScriptSecurityManager.h"
-
-using namespace mozilla;
-
-NS_IMPL_CYCLE_COLLECTION(nsXBLResourceLoader, mBoundElements)
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXBLResourceLoader)
-  NS_INTERFACE_MAP_ENTRY(nsICSSLoaderObserver)
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXBLResourceLoader)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXBLResourceLoader)
-
-struct nsXBLResource {
-  nsXBLResource* mNext;
-  nsAtom* mType;
-  nsString mSrc;
-
-  nsXBLResource(nsAtom* aType, const nsAString& aSrc) {
-    MOZ_COUNT_CTOR(nsXBLResource);
-    mNext = nullptr;
-    mType = aType;
-    mSrc = aSrc;
-  }
-
-  ~nsXBLResource() {
-    MOZ_COUNT_DTOR(nsXBLResource);
-    NS_CONTENT_DELETE_LIST_MEMBER(nsXBLResource, this, mNext);
-  }
-};
-
-nsXBLResourceLoader::nsXBLResourceLoader(nsXBLPrototypeBinding* aBinding,
-                                         nsXBLPrototypeResources* aResources)
-    : mBinding(aBinding),
-      mResources(aResources),
-      mResourceList(nullptr),
-      mLastResource(nullptr),
-      mLoadingResources(false),
-      mInLoadResourcesFunc(false),
-      mPendingSheets(0),
-      mBoundDocument(nullptr) {}
-
-nsXBLResourceLoader::~nsXBLResourceLoader() { delete mResourceList; }
-
-bool nsXBLResourceLoader::LoadResources(nsIContent* aBoundElement) {
-  mInLoadResourcesFunc = true;
-
-  if (mLoadingResources) {
-    mInLoadResourcesFunc = false;
-    return mPendingSheets == 0;
-  }
-
-  mLoadingResources = true;
-
-  // Declare our loaders.
-  nsCOMPtr<Document> doc = mBinding->XBLDocumentInfo()->GetDocument();
-  mBoundDocument = aBoundElement->OwnerDoc();
-
-  mozilla::css::Loader* cssLoader = doc->CSSLoader();
-  MOZ_ASSERT(cssLoader->GetDocument(), "Loader must have document");
-
-  nsIURI* docURL = doc->GetDocumentURI();
-  nsIPrincipal* docPrincipal = doc->NodePrincipal();
-
-  nsCOMPtr<nsIURI> url;
-
-  for (nsXBLResource* curr = mResourceList; curr; curr = curr->mNext) {
-    if (curr->mSrc.IsEmpty()) continue;
-
-    if (NS_FAILED(NS_NewURI(getter_AddRefs(url), curr->mSrc,
-                            doc->GetDocumentCharacterSet(), docURL)))
-      continue;
-
-    if (curr->mType == nsGkAtoms::image) {
-      // Now kick off the image load...
-      // Passing nullptr for pretty much everything -- cause we don't care!
-      // XXX: initialDocumentURI is nullptr!
-      RefPtr<imgRequestProxy> req;
-      nsContentUtils::LoadImage(url, doc, doc, docPrincipal, 0, docURL,
-                                doc->GetReferrerPolicy(), nullptr,
-                                nsIRequest::LOAD_BACKGROUND, EmptyString(),
-                                getter_AddRefs(req));
-    } else if (curr->mType == nsGkAtoms::stylesheet) {
-      // Kick off the load of the stylesheet.
-
-      // Always load chrome synchronously
-      // XXXbz should that still do a content policy check?
-      bool chrome;
-      nsresult rv;
-      if (NS_SUCCEEDED(url->SchemeIs("chrome", &chrome)) && chrome) {
-        rv = nsContentUtils::GetSecurityManager()->CheckLoadURIWithPrincipal(
-            docPrincipal, url, nsIScriptSecurityManager::ALLOW_CHROME);
-        if (NS_SUCCEEDED(rv)) {
-          RefPtr<StyleSheet> sheet;
-          rv = cssLoader->LoadSheetSync(url, &sheet);
-          NS_ASSERTION(NS_SUCCEEDED(rv), "Load failed!!!");
-          if (NS_SUCCEEDED(rv)) {
-            rv = StyleSheetLoaded(sheet, false, NS_OK);
-            NS_ASSERTION(NS_SUCCEEDED(rv),
-                         "Processing the style sheet failed!!!");
-          }
-        }
-      } else {
-        rv = cssLoader->LoadSheet(url, false, docPrincipal, nullptr, this);
-        if (NS_SUCCEEDED(rv)) ++mPendingSheets;
-      }
-    }
-  }
-
-  mInLoadResourcesFunc = false;
-
-  // Destroy our resource list.
-  delete mResourceList;
-  mResourceList = nullptr;
-
-  return mPendingSheets == 0;
-}
-
-// nsICSSLoaderObserver
-NS_IMETHODIMP
-nsXBLResourceLoader::StyleSheetLoaded(StyleSheet* aSheet, bool aWasDeferred,
-                                      nsresult aStatus) {
-  if (!mResources) {
-    // Our resources got destroyed -- just bail out
-    return NS_OK;
-  }
-
-  mResources->AppendStyleSheet(aSheet);
-
-  if (!mInLoadResourcesFunc) mPendingSheets--;
-
-  if (mPendingSheets == 0) {
-    // All stylesheets are loaded.
-
-    // Our document might have been undisplayed after this sheet load
-    // was started, so check before building the XBL cascade data.
-    if (PresShell* presShell = mBoundDocument->GetPresShell()) {
-      mResources->ComputeServoStyles(*presShell->StyleSet());
-    }
-
-    // XXX Check for mPendingScripts when scripts also come online.
-    if (!mInLoadResourcesFunc) NotifyBoundElements();
-  }
-  return NS_OK;
-}
-
-void nsXBLResourceLoader::AddResource(nsAtom* aResourceType,
-                                      const nsAString& aSrc) {
-  nsXBLResource* res = new nsXBLResource(aResourceType, aSrc);
-  if (!mResourceList)
-    mResourceList = res;
-  else
-    mLastResource->mNext = res;
-
-  mLastResource = res;
-}
-
-void nsXBLResourceLoader::AddResourceListener(nsIContent* aBoundElement) {
-  if (aBoundElement) {
-    mBoundElements.AppendObject(aBoundElement);
-    aBoundElement->OwnerDoc()->BlockOnload();
-  }
-}
-
-void nsXBLResourceLoader::NotifyBoundElements() {
-  nsXBLService* xblService = nsXBLService::GetInstance();
-  if (!xblService) return;
-
-  nsIURI* bindingURI = mBinding->BindingURI();
-
-  uint32_t eltCount = mBoundElements.Count();
-  for (uint32_t j = 0; j < eltCount; j++) {
-    nsCOMPtr<nsIContent> content = mBoundElements.ObjectAt(j);
-    MOZ_ASSERT(content->IsElement());
-    content->OwnerDoc()->UnblockOnload(/* aFireSync = */ false);
-
-    bool ready = false;
-    xblService->BindingReady(content, bindingURI, &ready);
-
-    if (!ready) {
-      continue;
-    }
-
-    Document* doc = content->GetUncomposedDoc();
-    if (!doc) {
-      continue;
-    }
-
-    PresShell* presShell = doc->GetPresShell();
-    if (!presShell) {
-      continue;
-    }
-
-    presShell->PostRecreateFramesFor(content->AsElement());
-  }
-
-  // Clear out the whole array.
-  mBoundElements.Clear();
-
-  // Delete ourselves.
-  mResources->ClearLoader();
-}
-
-nsresult nsXBLResourceLoader::Write(nsIObjectOutputStream* aStream) {
-  nsresult rv;
-
-  for (nsXBLResource* curr = mResourceList; curr; curr = curr->mNext) {
-    if (curr->mType == nsGkAtoms::image)
-      rv = aStream->Write8(XBLBinding_Serialize_Image);
-    else if (curr->mType == nsGkAtoms::stylesheet)
-      rv = aStream->Write8(XBLBinding_Serialize_Stylesheet);
-    else
-      continue;
-
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = aStream->WriteWStringZ(curr->mSrc.get());
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  return NS_OK;
-}
deleted file mode 100644
--- a/dom/xbl/nsXBLResourceLoader.h
+++ /dev/null
@@ -1,74 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef nsXBLResourceLoader_h
-#define nsXBLResourceLoader_h
-
-#include "mozilla/Attributes.h"
-#include "nsCOMPtr.h"
-#include "nsICSSLoaderObserver.h"
-#include "nsCOMArray.h"
-#include "nsCycleCollectionParticipant.h"
-
-class nsIContent;
-class nsAtom;
-class nsXBLPrototypeResources;
-class nsXBLPrototypeBinding;
-struct nsXBLResource;
-class nsIObjectOutputStream;
-
-// *********************************************************************/
-// The XBLResourceLoader class
-
-class nsXBLResourceLoader : public nsICSSLoaderObserver {
- public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_CLASS(nsXBLResourceLoader)
-
-  // nsICSSLoaderObserver
-  NS_IMETHOD StyleSheetLoaded(mozilla::StyleSheet* aSheet, bool aWasAlternate,
-                              nsresult aStatus) override;
-
-  bool LoadResources(nsIContent* aBoundElement);
-  void AddResource(nsAtom* aResourceType, const nsAString& aSrc);
-  void AddResourceListener(nsIContent* aElement);
-
-  nsXBLResourceLoader(nsXBLPrototypeBinding* aBinding,
-                      nsXBLPrototypeResources* aResources);
-
-  void NotifyBoundElements();
-
-  nsresult Write(nsIObjectOutputStream* aStream);
-
-  // MEMBER VARIABLES
-  nsXBLPrototypeBinding* mBinding;      // A pointer back to our binding.
-  nsXBLPrototypeResources* mResources;  // A pointer back to our resources
-                                        // information.  May be null if the
-                                        // resources have already been
-                                        // destroyed.
-
-  nsXBLResource* mResourceList;  // The list of resources we need to load.
-  nsXBLResource* mLastResource;
-
-  bool mLoadingResources;
-  // We need mInLoadResourcesFunc because we do a mixture of sync and
-  // async loads.
-  bool mInLoadResourcesFunc;
-  int16_t mPendingSheets;  // The number of stylesheets that have yet to load.
-
-  // Bound elements that are waiting on the stylesheets and scripts.
-  nsCOMArray<nsIContent> mBoundElements;
-
- protected:
-  virtual ~nsXBLResourceLoader();
-
- private:
-  // The bound document is needed in StyleSheetLoaded() for servo style
-  // backend, which will be set in LoadResources().
-  dom::Document* MOZ_NON_OWNING_REF mBoundDocument;
-};
-
-#endif
--- a/dom/xbl/nsXBLSerialize.h
+++ b/dom/xbl/nsXBLSerialize.h
@@ -11,24 +11,21 @@
 #include "nsIObjectOutputStream.h"
 #include "mozilla/dom/NameSpaceConstants.h"
 #include "js/TypeDecls.h"
 
 typedef uint8_t XBLBindingSerializeDetails;
 
 // A version number to ensure we don't load cached data in a different
 // file format.
-#define XBLBinding_Serialize_Version 0x00000006
+#define XBLBinding_Serialize_Version 0x00000007
 
 // Set for the first binding in a document
 #define XBLBinding_Serialize_IsFirstBinding (1 << 0)
 
-// Set to indicate that nsXBLPrototypeBinding::mInheritStyle should be true
-#define XBLBinding_Serialize_InheritStyle (1 << 1)
-
 // Set to indicate that nsXBLPrototypeBinding::mBindToUntrustedContent should be
 // true
 #define XBLBinding_Serialize_BindToUntrustedContent (1 << 3)
 
 // Set to indicate that nsXBLPrototypeBinding::mSimpleScopeChain should be true
 #define XBLBinding_Serialize_SimpleScopeChain (1 << 4)
 
 // Appears at the end of the serialized data to indicate that no more bindings
@@ -44,18 +41,16 @@ typedef uint8_t XBLBindingSerializeDetai
 #define XBLBinding_Serialize_Field 1
 #define XBLBinding_Serialize_GetterProperty 2
 #define XBLBinding_Serialize_SetterProperty 3
 #define XBLBinding_Serialize_GetterSetterProperty 4
 #define XBLBinding_Serialize_Method 5
 #define XBLBinding_Serialize_Constructor 6
 #define XBLBinding_Serialize_Destructor 7
 #define XBLBinding_Serialize_Handler 8
-#define XBLBinding_Serialize_Image 9
-#define XBLBinding_Serialize_Stylesheet 10
 #define XBLBinding_Serialize_Attribute 0xA
 #define XBLBinding_Serialize_Mask 0x0F
 #define XBLBinding_Serialize_ReadOnly 0x80
 
 // Appears at the end of the list of insertion points to indicate that there
 // are no more.
 #define XBLBinding_Serialize_NoMoreInsertionPoints 0xFFFFFFFF
 
--- a/dom/xbl/nsXBLService.cpp
+++ b/dom/xbl/nsXBLService.cpp
@@ -394,45 +394,34 @@ class MOZ_RAII AutoEnsureSubtreeStyled {
 
  private:
   Element* mElement;
 };
 
 // RAII class to restyle the XBL bound element when it shuffles the flat tree.
 class MOZ_RAII AutoStyleElement {
  public:
-  AutoStyleElement(Element* aElement, bool* aResolveStyle)
-      : mElement(aElement),
-        mHadData(aElement->HasServoData()),
-        mResolveStyle(aResolveStyle) {
-    MOZ_ASSERT(mResolveStyle);
+  explicit AutoStyleElement(Element* aElement)
+      : mElement(aElement), mHadData(aElement->HasServoData()) {
     if (mHadData) {
       RestyleManager::ClearServoDataFromSubtree(
           mElement, RestyleManager::IncludeRoot::No);
     }
   }
 
   ~AutoStyleElement() {
     PresShell* presShell = mElement->OwnerDoc()->GetPresShell();
     if (!mHadData || !presShell || !presShell->DidInitialize()) {
       return;
     }
-
-    if (*mResolveStyle) {
-      mElement->ClearServoData();
-
-      ServoStyleSet* servoSet = presShell->StyleSet();
-      servoSet->StyleNewSubtree(mElement);
-    }
   }
 
  private:
   Element* mElement;
   bool mHadData;
-  bool* mResolveStyle;
 };
 
 static bool IsSystemOrChromeURLPrincipal(nsIPrincipal* aPrincipal) {
   if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
     return true;
   }
 
   nsCOMPtr<nsIURI> uri;
@@ -442,22 +431,20 @@ static bool IsSystemOrChromeURLPrincipal
   bool isChrome = false;
   return NS_SUCCEEDED(uri->SchemeIs("chrome", &isChrome)) && isChrome;
 }
 
 // This function loads a particular XBL file and installs all of the bindings
 // onto the element.
 nsresult nsXBLService::LoadBindings(Element* aElement, nsIURI* aURL,
                                     nsIPrincipal* aOriginPrincipal,
-                                    nsXBLBinding** aBinding,
-                                    bool* aResolveStyle) {
+                                    nsXBLBinding** aBinding) {
   MOZ_ASSERT(aOriginPrincipal, "Must have an origin principal");
 
   *aBinding = nullptr;
-  *aResolveStyle = false;
 
   AutoEnsureSubtreeStyled subtreeStyled(aElement);
 
   if (MOZ_UNLIKELY(!aURL)) {
     return NS_OK;
   }
 
 #ifdef DEBUG
@@ -491,17 +478,17 @@ nsresult nsXBLService::LoadBindings(Elem
   nsCOMPtr<Document> document = aElement->OwnerDoc();
 
   nsAutoCString urlspec;
   nsresult rv = aURL->GetSpec(urlspec);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  AutoStyleElement styleElement(aElement, aResolveStyle);
+  AutoStyleElement styleElement(aElement);
 
   if (binding) {
     FlushStyleBindings(aElement);
     binding = nullptr;
   }
 
   bool ready;
   RefPtr<nsXBLBinding> newBinding;
@@ -542,19 +529,16 @@ nsresult nsXBLService::LoadBindings(Elem
 
     // Tell the binding to install event handlers
     newBinding->InstallEventHandlers();
 
     // Set up our properties
     rv = newBinding->InstallImplementation();
     NS_ENSURE_SUCCESS(rv, rv);
 
-    // Figure out if we have any scoped sheets.  If so, we do a second resolve.
-    *aResolveStyle = newBinding->HasStyleSheets();
-
     newBinding.forget(aBinding);
   }
 
   return NS_OK;
 }
 
 void nsXBLService::FlushStyleBindings(Element* aElement) {
   nsCOMPtr<Document> document = aElement->OwnerDoc();
@@ -766,25 +750,16 @@ nsresult nsXBLService::GetBinding(nsICon
   }
 
   aDontExtendURIs.AppendElement(protoBinding->BindingURI());
   nsCOMPtr<nsIURI> altBindingURI = protoBinding->AlternateBindingURI();
   if (altBindingURI) {
     aDontExtendURIs.AppendElement(altBindingURI);
   }
 
-  // Our prototype binding must have all its resources loaded.
-  bool ready = protoBinding->LoadResources(aBoundElement);
-  if (!ready) {
-    // Add our bound element to the protos list of elts that should
-    // be notified when the stylesheets and scripts finish loading.
-    protoBinding->AddResourceListener(aBoundElement);
-    return NS_ERROR_FAILURE;  // The binding isn't ready yet.
-  }
-
   rv = protoBinding->ResolveBaseBinding();
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIURI> baseBindingURI;
   WeakPtr<nsXBLPrototypeBinding> baseProto = protoBinding->GetBasePrototype();
   if (baseProto) {
     baseBindingURI = baseProto->BindingURI();
   } else {
--- a/dom/xbl/nsXBLService.h
+++ b/dom/xbl/nsXBLService.h
@@ -19,16 +19,17 @@ class nsXBLBinding;
 class nsXBLDocumentInfo;
 class nsIContent;
 class nsIURI;
 class nsIPrincipal;
 
 namespace mozilla {
 namespace dom {
 class Document;
+class Element;
 class EventTarget;
 }  // namespace dom
 }  // namespace mozilla
 
 class nsXBLService final : public nsSupportsWeakReference {
   NS_DECL_ISUPPORTS
 
   static nsXBLService* gInstance;
@@ -39,18 +40,18 @@ class nsXBLService final : public nsSupp
 
   static nsXBLService* GetInstance() { return gInstance; }
 
   static bool IsChromeOrResourceURI(nsIURI* aURI);
 
   // This function loads a particular XBL file and installs all of the bindings
   // onto the element.  aOriginPrincipal must not be null here.
   nsresult LoadBindings(mozilla::dom::Element* aElement, nsIURI* aURL,
-                        nsIPrincipal* aOriginPrincipal, nsXBLBinding** aBinding,
-                        bool* aResolveStyle);
+                        nsIPrincipal* aOriginPrincipal,
+                        nsXBLBinding** aBinding);
 
   // Indicates whether or not a binding is fully loaded.
   nsresult BindingReady(nsIContent* aBoundElement, nsIURI* aURI,
                         bool* aIsReady);
 
   // This method checks the hashtable and then calls FetchBindingDocument on a
   // miss.  aOriginPrincipal or aBoundDocument may be null to bypass security
   // checks.
--- a/dom/xul/nsXULPrototypeCache.cpp
+++ b/dom/xul/nsXULPrototypeCache.cpp
@@ -224,23 +224,16 @@ void nsXULPrototypeCache::FlushSkinFiles
   // Now flush out our skin stylesheets from the cache.
   for (auto iter = mStyleSheetTable.Iter(); !iter.Done(); iter.Next()) {
     nsAutoCString str;
     iter.Data()->GetSheetURI()->GetPathQueryRef(str);
     if (strncmp(str.get(), "/skin", 5) == 0) {
       iter.Remove();
     }
   }
-
-  // Iterate over all the remaining XBL and make sure cached
-  // scoped skin stylesheets are flushed and refetched by the
-  // prototype bindings.
-  for (auto iter = mXBLDocTable.Iter(); !iter.Done(); iter.Next()) {
-    iter.Data()->FlushSkinStylesheets();
-  }
 }
 
 void nsXULPrototypeCache::FlushScripts() { mScriptTable.Clear(); }
 
 void nsXULPrototypeCache::Flush() {
   mPrototypeTable.Clear();
   mScriptTable.Clear();
   mStyleSheetTable.Clear();
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -1782,23 +1782,21 @@ nsEventStatus APZCTreeManager::ProcessTo
 
     if (mApzcForInputBlock) {
       MOZ_ASSERT(mHitResultForInputBlock != CompositorHitTestInvisibleToHit);
 
       mApzcForInputBlock->GetGuid(aOutTargetGuid);
       uint64_t inputBlockId = 0;
       result = mInputQueue->ReceiveInputEvent(
           mApzcForInputBlock, TargetConfirmationFlags{mHitResultForInputBlock},
-          aInput, &inputBlockId);
+          aInput, &inputBlockId,
+          touchBehaviors.IsEmpty() ? Nothing() : Some(touchBehaviors));
       if (aOutInputBlockId) {
         *aOutInputBlockId = inputBlockId;
       }
-      if (!touchBehaviors.IsEmpty()) {
-        mInputQueue->SetAllowedTouchBehavior(inputBlockId, touchBehaviors);
-      }
 
       // For computing the event to pass back to Gecko, use up-to-date
       // transforms (i.e. not anything cached in an input block). This ensures
       // that transformToApzc and transformToGecko are in sync.
       ScreenToParentLayerMatrix4x4 transformToApzc =
           GetScreenToApzcTransform(mApzcForInputBlock);
       ParentLayerToScreenMatrix4x4 transformToGecko =
           GetApzcToGeckoTransform(mApzcForInputBlock);
--- a/gfx/layers/apz/src/InputBlockState.cpp
+++ b/gfx/layers/apz/src/InputBlockState.cpp
@@ -602,16 +602,20 @@ bool TouchBlockState::GetAllowedTouchBeh
     nsTArray<TouchBehaviorFlags>& aOutBehaviors) const {
   if (!mAllowedTouchBehaviorSet) {
     return false;
   }
   aOutBehaviors.AppendElements(mAllowedTouchBehaviors);
   return true;
 }
 
+bool TouchBlockState::HasAllowedTouchBehaviors() const {
+  return mAllowedTouchBehaviorSet;
+}
+
 void TouchBlockState::CopyPropertiesFrom(const TouchBlockState& aOther) {
   TBS_LOG("%p copying properties from %p\n", this, &aOther);
   if (gfxPrefs::TouchActionEnabled()) {
     MOZ_ASSERT(aOther.mAllowedTouchBehaviorSet ||
                aOther.IsContentResponseTimerExpired());
     SetAllowedTouchBehaviors(aOther.mAllowedTouchBehaviors);
   }
   mTransformToApzc = aOther.mTransformToApzc;
--- a/gfx/layers/apz/src/InputBlockState.h
+++ b/gfx/layers/apz/src/InputBlockState.h
@@ -395,16 +395,22 @@ class TouchBlockState : public Cancelabl
   /**
    * If the allowed touch behaviors have been set, populate them into
    * |aOutBehaviors| and return true. Else, return false.
    */
   bool GetAllowedTouchBehaviors(
       nsTArray<TouchBehaviorFlags>& aOutBehaviors) const;
 
   /**
+   * Returns true if the allowed touch behaviours have been set, or if touch
+   * action is disabled.
+   */
+  bool HasAllowedTouchBehaviors() const;
+
+  /**
    * Copy various properties from another block.
    */
   void CopyPropertiesFrom(const TouchBlockState& aOther);
 
   /*
    * @return true iff this block has received all the information it could
    *         have gotten from the content thread.
    */
--- a/gfx/layers/apz/src/InputQueue.cpp
+++ b/gfx/layers/apz/src/InputQueue.cpp
@@ -22,25 +22,26 @@ namespace layers {
 
 InputQueue::InputQueue() = default;
 
 InputQueue::~InputQueue() { mQueuedInputs.Clear(); }
 
 nsEventStatus InputQueue::ReceiveInputEvent(
     const RefPtr<AsyncPanZoomController>& aTarget,
     TargetConfirmationFlags aFlags, const InputData& aEvent,
-    uint64_t* aOutInputBlockId) {
+    uint64_t* aOutInputBlockId,
+    const Maybe<nsTArray<TouchBehaviorFlags>>& aTouchBehaviors) {
   APZThreadUtils::AssertOnControllerThread();
 
   AutoRunImmediateTimeout timeoutRunner{this};
 
   switch (aEvent.mInputType) {
     case MULTITOUCH_INPUT: {
       const MultiTouchInput& event = aEvent.AsMultiTouchInput();
-      return ReceiveTouchInput(aTarget, aFlags, event, aOutInputBlockId);
+      return ReceiveTouchInput(aTarget, aFlags, event, aOutInputBlockId, aTouchBehaviors);
     }
 
     case SCROLLWHEEL_INPUT: {
       const ScrollWheelInput& event = aEvent.AsScrollWheelInput();
       return ReceiveScrollWheelInput(aTarget, aFlags, event, aOutInputBlockId);
     }
 
     case PANGESTURE_INPUT: {
@@ -69,17 +70,18 @@ nsEventStatus InputQueue::ReceiveInputEv
       // for non-touch events as well.
       return aTarget->HandleInputEvent(aEvent, aTarget->GetTransformToThis());
   }
 }
 
 nsEventStatus InputQueue::ReceiveTouchInput(
     const RefPtr<AsyncPanZoomController>& aTarget,
     TargetConfirmationFlags aFlags, const MultiTouchInput& aEvent,
-    uint64_t* aOutInputBlockId) {
+    uint64_t* aOutInputBlockId,
+    const Maybe<nsTArray<TouchBehaviorFlags>>& aTouchBehaviors) {
   TouchBlockState* block = nullptr;
   if (aEvent.mType == MultiTouchInput::MULTITOUCH_START) {
     nsTArray<TouchBehaviorFlags> currentBehaviors;
     bool haveBehaviors = false;
     if (!gfxPrefs::TouchActionEnabled()) {
       haveBehaviors = true;
     } else if (mActiveTouchBlock) {
       haveBehaviors =
@@ -110,22 +112,31 @@ nsEventStatus InputQueue::ReceiveTouchIn
       block->SetConfirmedTargetApzc(
           aTarget, InputBlockState::TargetConfirmationState::eConfirmed,
           nullptr /* the block was just created so it has no events */,
           false /* not a scrollbar drag */);
       if (gfxPrefs::TouchActionEnabled()) {
         block->SetAllowedTouchBehaviors(currentBehaviors);
       }
       INPQ_LOG("block %p tagged as fast-motion\n", block);
+    } else if (aTouchBehaviors) {
+      // If this block isn't started during a fast-fling, and APZCTM has
+      // provided touch behavior information, then put it on the block so
+      // that the ArePointerEventsConsumable call below can use it.
+      block->SetAllowedTouchBehaviors(*aTouchBehaviors);
     }
 
     CancelAnimationsForNewBlock(block);
 
     MaybeRequestContentResponse(aTarget, block);
   } else {
+    // for touch inputs that don't start a block, APZCTM shouldn't be giving
+    // us any touch behaviors.
+    MOZ_ASSERT(aTouchBehaviors.isNothing());
+
     block = mActiveTouchBlock.get();
     if (!block) {
       NS_WARNING(
           "Received a non-start touch event while no touch blocks active!");
       return nsEventStatus_eIgnore;
     }
 
     INPQ_LOG("received new event in block %p\n", block);
@@ -415,22 +426,17 @@ void InputQueue::MaybeRequestContentResp
   bool waitForMainThread = false;
   if (aBlock->IsTargetConfirmed()) {
     // Content won't prevent-default this, so we can just set the flag directly.
     INPQ_LOG("not waiting for content response on block %p\n", aBlock);
     aBlock->SetContentResponse(false);
   } else {
     waitForMainThread = true;
   }
-  if (aBlock->AsTouchBlock() && gfxPrefs::TouchActionEnabled()) {
-    // waitForMainThread is set to true unconditionally here, but if the APZCTM
-    // has the touch-action behaviours for this block, it will set it
-    // immediately after we unwind out of this ReceiveInputEvent call. So even
-    // though we are scheduling the main-thread timeout, we might end up not
-    // waiting.
+  if (aBlock->AsTouchBlock() && !aBlock->AsTouchBlock()->HasAllowedTouchBehaviors()) {
     INPQ_LOG("waiting for main thread touch-action info on block %p\n", aBlock);
     waitForMainThread = true;
   }
   if (waitForMainThread) {
     // We either don't know for sure if aTarget is the right APZC, or we may
     // need to wait to give content the opportunity to prevent-default the
     // touch events. Either way we schedule a timeout so the main thread stuff
     // can run.
--- a/gfx/layers/apz/src/InputQueue.h
+++ b/gfx/layers/apz/src/InputQueue.h
@@ -49,17 +49,18 @@ class InputQueue {
    * Notifies the InputQueue of a new incoming input event. The APZC that the
    * input event was targeted to should be provided in the |aTarget| parameter.
    * See the documentation on APZCTreeManager::ReceiveInputEvent for info on
    * return values from this function, including |aOutInputBlockId|.
    */
   nsEventStatus ReceiveInputEvent(const RefPtr<AsyncPanZoomController>& aTarget,
                                   TargetConfirmationFlags aFlags,
                                   const InputData& aEvent,
-                                  uint64_t* aOutInputBlockId);
+                                  uint64_t* aOutInputBlockId,
+                                  const Maybe<nsTArray<TouchBehaviorFlags>>& aTouchBehaviors = Nothing());
   /**
    * This function should be invoked to notify the InputQueue when web content
    * decides whether or not it wants to cancel a block of events. The block
    * id to which this applies should be provided in |aInputBlockId|.
    */
   void ContentReceivedInputBlock(uint64_t aInputBlockId, bool aPreventDefault);
   /**
    * This function should be invoked to notify the InputQueue once the target
@@ -169,17 +170,18 @@ class InputQueue {
    */
   void MaybeRequestContentResponse(
       const RefPtr<AsyncPanZoomController>& aTarget,
       CancelableBlockState* aBlock);
 
   nsEventStatus ReceiveTouchInput(const RefPtr<AsyncPanZoomController>& aTarget,
                                   TargetConfirmationFlags aFlags,
                                   const MultiTouchInput& aEvent,
-                                  uint64_t* aOutInputBlockId);
+                                  uint64_t* aOutInputBlockId,
+                                  const Maybe<nsTArray<TouchBehaviorFlags>>& aTouchBehaviors);
   nsEventStatus ReceiveMouseInput(const RefPtr<AsyncPanZoomController>& aTarget,
                                   TargetConfirmationFlags aFlags,
                                   const MouseInput& aEvent,
                                   uint64_t* aOutInputBlockId);
   nsEventStatus ReceiveScrollWheelInput(
       const RefPtr<AsyncPanZoomController>& aTarget,
       TargetConfirmationFlags aFlags, const ScrollWheelInput& aEvent,
       uint64_t* aOutInputBlockId);
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_bug1544966_zoom_on_touch_action_none.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width; initial-scale=1.0">
+  <title>Test for Bug 1544966</title>
+  <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+  <script type="application/javascript" src="apz_test_utils.js"></script>
+  <script src="/tests/SimpleTest/paint_listener.js"></script>
+  <script src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript">
+
+function* test(testDriver) {
+  var target = document.getElementById("target");
+
+  var pointersDown = 0;
+  var pointersUp = 0;
+  var pointerMoveCount = 0;
+
+  target.addEventListener("pointerdown", function(e) {
+    dump(`Got pointerdown, pointer id ${e.pointerId}\n`);
+    pointersDown++;
+  });
+  target.addEventListener("pointermove", function(e) {
+    dump(`Got pointermove, pointer id ${e.pointerId}, at ${e.clientX}, ${e.clientY}\n`);
+    pointerMoveCount++;
+  });
+  target.addEventListener("pointercancel", function(e) {
+    dump(`Got pointercancel, pointer id ${e.pointerId}\n`);
+    ok(false, "Should not have gotten pointercancel");
+    pointersUp++;
+    if (pointersDown == pointersUp) {
+      // All pointers lifted, let's continue the test
+      setTimeout(testDriver, 0);
+    }
+  });
+  target.addEventListener("pointerup", function(e) {
+    dump(`Got pointerup, pointer id ${e.pointerId}\n`);
+    pointersUp++;
+    if (pointersDown == pointersUp) {
+      // All pointers lifted, let's continue the test
+      setTimeout(testDriver, 0);
+    }
+  });
+
+  var zoom_in = [
+      [ { x: 125, y: 175 }, { x: 175, y: 225 } ],
+      [ { x: 120, y: 150 }, { x: 180, y: 250 } ],
+      [ { x: 115, y: 125 }, { x: 185, y: 275 } ],
+      [ { x: 110, y: 100 }, { x: 190, y: 300 } ],
+      [ { x: 105, y:  75 }, { x: 195, y: 325 } ],
+      [ { x: 100, y:  50 }, { x: 200, y: 350 } ],
+  ];
+
+  var touchIds = [0, 1];
+  yield* synthesizeNativeTouchSequences(document.getElementById("target"), zoom_in, null, touchIds);
+
+  dump("All touch events synthesized, waiting for final pointerup...\n");
+  yield true;
+
+  // Should get at least one pointermove per pointer, even if the events
+  // get coalesced somewhere.
+  is(pointersDown, 2, "Got expected numbers of pointers recorded");
+  ok(pointerMoveCount >= 2, "Got " + pointerMoveCount + " pointermove events");
+}
+
+waitUntilApzStable()
+.then(runContinuation(test))
+.then(subtestDone);
+
+  </script>
+  <style>
+    body {
+        height: 5000px;
+    }
+    #target {
+        touch-action: none;
+        height: 400px
+    }
+  </style>
+</head>
+<body>
+ <div id="target">
+  Put down two fingers at the same time and do a pinch action.
+ </div>
+</body>
+</html>
--- a/gfx/layers/apz/test/mochitest/test_group_pointerevents.html
+++ b/gfx/layers/apz/test/mochitest/test_group_pointerevents.html
@@ -3,28 +3,31 @@
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=1285070
 -->
 <head>
   <meta charset="utf-8">
   <title>Test for Bug 1285070</title>
   <script src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="apz_test_utils.js"></script>
+  <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript">
 
   let isWindows = navigator.platform.indexOf("Win") == 0;
   let enablePE = ["dom.w3c_pointer_events.enabled", true];
+  var touch_action_prefs = getPrefs("TOUCH_ACTION");
   var subtests = [
     {"file": "helper_bug1285070.html", "prefs": [enablePE]},
     {"file": "helper_bug1299195.html", "prefs": [enablePE]},
     {"file": "helper_bug1414336.html", "prefs": [enablePE,
       ["apz.test.fails_with_native_injection", isWindows],
     ]},
     {"file": "helper_bug1502010_unconsumed_pan.html", "prefs": [enablePE]},
+    {"file": "helper_bug1544966_zoom_on_touch_action_none.html", "prefs": [enablePE, ...touch_action_prefs]},
   ];
 
   if (isApzEnabled()) {
     SimpleTest.waitForExplicitFinish();
     window.onload = function() {
       runSubtestsSeriallyInFreshWindows(subtests)
       .then(SimpleTest.finish, SimpleTest.finish);
     };
--- a/gfx/skia/skia/include/config/SkUserConfig.h
+++ b/gfx/skia/skia/include/config/SkUserConfig.h
@@ -150,9 +150,11 @@
 #    define MOZ_IMPLICIT
 #  endif
 #endif
 
 #define MOZ_SKIA
 
 #define SK_IGNORE_MAC_BLENDING_MATCH_FIX
 
+#define I_ACKNOWLEDGE_SKIA_DOES_NOT_SUPPORT_BIG_ENDIAN
+
 #endif
--- a/gfx/wr/webrender/res/shared.glsl
+++ b/gfx/wr/webrender/res/shared.glsl
@@ -14,18 +14,22 @@
 
 #ifdef WR_FEATURE_TEXTURE_EXTERNAL
 // Please check https://www.khronos.org/registry/OpenGL/extensions/OES/OES_EGL_image_external_essl3.txt
 // for this extension.
 #extension GL_OES_EGL_image_external_essl3 : require
 #endif
 
 #ifdef WR_FEATURE_DUAL_SOURCE_BLENDING
+#ifdef GL_ES
+#extension GL_EXT_blend_func_extended : require
+#else
 #extension GL_ARB_explicit_attrib_location : require
 #endif
+#endif
 
 #include base
 
 #if defined(WR_FEATURE_TEXTURE_EXTERNAL) || defined(WR_FEATURE_TEXTURE_RECT) || defined(WR_FEATURE_TEXTURE_2D)
 #define TEX_SAMPLE(sampler, tex_coord) texture(sampler, tex_coord.xy)
 #else
 #define TEX_SAMPLE(sampler, tex_coord) texture(sampler, tex_coord)
 #endif
--- a/gfx/wr/webrender/src/renderer.rs
+++ b/gfx/wr/webrender/src/renderer.rs
@@ -2013,23 +2013,27 @@ impl Renderer {
         let mut device = Device::new(
             gl,
             options.resource_override_path.clone(),
             options.upload_method.clone(),
             options.cached_programs.take(),
             options.allow_pixel_local_storage_support,
         );
 
-        let ext_dual_source_blending =
+        let supports_dual_source_blending = match gl_type {
+            gl::GlType::Gl => device.supports_extension("GL_ARB_blend_func_extended") &&
+                device.supports_extension("GL_ARB_explicit_attrib_location"),
+            gl::GlType::Gles => device.supports_extension("GL_EXT_blend_func_extended"),
+        };
+        let use_dual_source_blending =
+            supports_dual_source_blending &&
             !options.disable_dual_source_blending &&
             // If using pixel local storage, subpixel AA isn't supported (we disable it on all
             // mobile devices explicitly anyway).
-            !device.get_capabilities().supports_pixel_local_storage &&
-            device.supports_extension("GL_ARB_blend_func_extended") &&
-            device.supports_extension("GL_ARB_explicit_attrib_location");
+            !device.get_capabilities().supports_pixel_local_storage;
 
         // 512 is the minimum that the texture cache can work with.
         const MIN_TEXTURE_SIZE: i32 = 512;
         if let Some(user_limit) = options.max_texture_size {
             assert!(user_limit >= MIN_TEXTURE_SIZE);
             device.clamp_max_texture_size(user_limit);
         }
         if device.max_texture_size() < MIN_TEXTURE_SIZE {
@@ -2190,17 +2194,17 @@ impl Renderer {
             (true, true) => FontRenderMode::Subpixel,
             (true, false) => FontRenderMode::Alpha,
             (false, _) => FontRenderMode::Mono,
         };
 
         let config = FrameBuilderConfig {
             default_font_render_mode,
             dual_source_blending_is_enabled: true,
-            dual_source_blending_is_supported: ext_dual_source_blending,
+            dual_source_blending_is_supported: use_dual_source_blending,
             chase_primitive: options.chase_primitive,
             enable_picture_caching: options.enable_picture_caching,
             testing: options.testing,
             gpu_supports_fast_clears: options.gpu_supports_fast_clears,
         };
 
         let device_pixel_ratio = options.device_pixel_ratio;
         let debug_flags = options.debug_flags;
--- a/js/moz.configure
+++ b/js/moz.configure
@@ -416,59 +416,16 @@ set_define('JS_HAS_CTYPES', js_has_ctype
 
 @depends('--enable-ctypes', '--enable-compile-environment')
 def ctypes_and_compile_environment(ctypes, compile_environment):
     return ctypes and compile_environment
 
 include('ffi.configure', when=ctypes_and_compile_environment)
 
 
-# Support various fuzzing options
-# ==============================================================
-with only_when('--enable-compile-environment'):
-    js_option('--enable-fuzzing', help='Enable fuzzing support')
-
-    @depends('--enable-fuzzing')
-    def enable_fuzzing(value):
-        if value:
-            return True
-
-    @depends(try_compile(body='__AFL_COMPILER;',
-                         check_msg='for AFL compiler',
-                         when='--enable-fuzzing'))
-    def enable_aflfuzzer(afl):
-        if afl:
-            return True
-
-    @depends(enable_fuzzing,
-             enable_aflfuzzer,
-             c_compiler,
-             target)
-    def enable_libfuzzer(fuzzing, afl, c_compiler, target):
-        if fuzzing and not afl and c_compiler.type == 'clang' and target.os != 'Android':
-            return True
-
-    @depends(enable_fuzzing,
-             enable_aflfuzzer,
-             enable_libfuzzer)
-    def enable_fuzzing_interfaces(fuzzing, afl, libfuzzer):
-        if fuzzing and (afl or libfuzzer):
-            return True
-
-    set_config('FUZZING', enable_fuzzing)
-    set_define('FUZZING', enable_fuzzing)
-
-    set_config('LIBFUZZER', enable_libfuzzer)
-    set_define('LIBFUZZER', enable_libfuzzer)
-    add_old_configure_assignment('LIBFUZZER', enable_libfuzzer)
-
-    set_config('FUZZING_INTERFACES', enable_fuzzing_interfaces)
-    set_define('FUZZING_INTERFACES', enable_fuzzing_interfaces)
-    add_old_configure_assignment('FUZZING_INTERFACES', enable_fuzzing_interfaces)
-
 # Enable pipeline operator
 # ===================================================
 js_option('--enable-pipeline-operator', default=False, help='Enable pipeline operator')
 
 @depends('--enable-pipeline-operator')
 def enable_pipeline_operator(value):
     if value:
         return True
--- a/js/public/Utility.h
+++ b/js/public/Utility.h
@@ -346,20 +346,25 @@ struct MOZ_RAII JS_PUBLIC_DATA AutoEnter
 } /* namespace js */
 
 // Malloc allocation.
 
 namespace js {
 
 extern JS_PUBLIC_DATA arena_id_t MallocArena;
 extern JS_PUBLIC_DATA arena_id_t ArrayBufferContentsArena;
+extern JS_PUBLIC_DATA arena_id_t StringBufferArena;
 
 extern void InitMallocAllocator();
 extern void ShutDownMallocAllocator();
 
+#  ifdef MOZ_DEBUG
+extern void AssertJSStringBufferInCorrectArena(const void* ptr);
+#  endif
+
 } /* namespace js */
 
 static inline void* js_arena_malloc(arena_id_t arena, size_t bytes) {
   JS_OOM_POSSIBLY_FAIL();
   return moz_arena_malloc(arena, bytes);
 }
 
 static inline void* js_malloc(size_t bytes) {
--- a/js/src/builtin/String.cpp
+++ b/js/src/builtin/String.cpp
@@ -3622,17 +3622,18 @@ bool js::str_fromCodePoint(JSContext* cx
   }
 
   // Steps 1-2 (omitted).
 
   // Step 3.
   static_assert(
       ARGS_LENGTH_MAX < std::numeric_limits<decltype(args.length())>::max() / 2,
       "|args.length() * 2 + 1| does not overflow");
-  auto elements = cx->make_pod_array<char16_t>(args.length() * 2 + 1);
+  auto elements = cx->make_pod_array<char16_t>(args.length() * 2 + 1,
+                                               js::StringBufferArena);
   if (!elements) {
     return false;
   }
 
   // Steps 4-5.
   unsigned length = 0;
   for (unsigned nextIndex = 0; nextIndex < args.length(); nextIndex++) {
     // Steps 5.a-d.
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -2491,17 +2491,17 @@ static bool ReadGeckoProfilingStack(JSCo
         case JS::ProfilingFrameIterator::Frame_Wasm:
           frameKindStr = "wasm";
           break;
         default:
           frameKindStr = "unknown";
       }
 
       UniqueChars label =
-          DuplicateStringToArena(js::MallocArena, cx, frames[i].label);
+          DuplicateStringToArena(js::StringBufferArena, cx, frames[i].label);
       if (!label) {
         return false;
       }
 
       if (!frameInfo.back().emplaceBack(frameKindStr, std::move(label))) {
         return false;
       }
     }
@@ -3554,17 +3554,17 @@ struct FindPathHandler {
     // nothing to be done on subsequent visits.
     if (!first) {
       return true;
     }
 
     // Record how we reached this node. This is the last edge on a
     // shortest path to this node.
     EdgeName edgeName =
-        DuplicateStringToArena(js::MallocArena, cx, edge.name.get());
+        DuplicateStringToArena(js::StringBufferArena, cx, edge.name.get());
     if (!edgeName) {
       return false;
     }
     *backEdge = BackEdge(origin, std::move(edgeName));
 
     // Have we reached our final target node?
     if (edge.referent == target) {
       // Record the path that got us here, which must be a shortest path.
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -7776,31 +7776,31 @@ static bool ReadStringCommon(JSContext* 
   }
 
   args.rval().setString(result);
   return true;
 }
 
 bool CData::ReadString(JSContext* cx, unsigned argc, Value* vp) {
   return ReadStringCommon(cx, JS::UTF8CharsToNewTwoByteCharsZ, argc, vp,
-                          "CData.prototype.readString", js::MallocArena);
+                          "CData.prototype.readString", js::StringBufferArena);
 }
 
 bool CDataFinalizer::Methods::ReadString(JSContext* cx, unsigned argc,
                                          Value* vp) {
   return ReadStringCommon(cx, JS::UTF8CharsToNewTwoByteCharsZ, argc, vp,
                           "CDataFinalizer.prototype.readString",
-                          js::MallocArena);
+                          js::StringBufferArena);
 }
 
 bool CData::ReadStringReplaceMalformed(JSContext* cx, unsigned argc,
                                        Value* vp) {
   return ReadStringCommon(cx, JS::LossyUTF8CharsToNewTwoByteCharsZ, argc, vp,
                           "CData.prototype.readStringReplaceMalformed",
-                          js::MallocArena);
+                          js::StringBufferArena);
 }
 
 JSString* CData::GetSourceString(JSContext* cx, HandleObject typeObj,
                                  void* data) {
   // Walk the types, building up the toSource() string.
   // First, we build up the type expression:
   // 't.ptr' for pointers;
   // 't.array([n])' for arrays;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/auto-regress/bug1544364.js
@@ -0,0 +1,8 @@
+// |jit-test| error:Error
+
+let sandbox = evalcx("lazy");
+
+let domObject = new FakeDOMObject();
+let {object, transplant} = transplantableObject({object: domObject});
+
+transplant(sandbox);
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -5570,44 +5570,92 @@ bool BaselineCodeGen<Handler>::emit_JSOP
     return false;
   }
 
   masm.loadValue(frame.addressOfReturnValue(), JSReturnOperand);
   return emitReturn();
 }
 
 template <>
-bool BaselineCompilerCodeGen::emit_JSOP_RESUME() {
-  auto resumeKind = AbstractGeneratorObject::getResumeKind(handler.pc());
-
+void BaselineCompilerCodeGen::emitJumpToInterpretOpLabel() {
+  MOZ_CRASH("NYI: Interpreter emitJumpToInterpretOpLabel");
+}
+
+template <>
+void BaselineInterpreterCodeGen::emitJumpToInterpretOpLabel() {
+  masm.jump(handler.interpretOpLabel());
+}
+
+template <typename Handler>
+bool BaselineCodeGen<Handler>::emitEnterGeneratorCode(Register script,
+                                                      Register resumeIndex,
+                                                      Register scratch) {
+  Address baselineAddr(script, JSScript::offsetOfBaselineScript());
+
+  auto emitEnterBaseline = [&]() {
+    masm.loadPtr(baselineAddr, script);
+    masm.load32(Address(script, BaselineScript::offsetOfResumeEntriesOffset()),
+                scratch);
+    masm.addPtr(scratch, script);
+    masm.loadPtr(
+        BaseIndex(script, resumeIndex, ScaleFromElemWidth(sizeof(uintptr_t))),
+        scratch);
+    masm.jump(scratch);
+  };
+
+  if (!JitOptions.baselineInterpreter) {
+    // We must have a BaselineScript.
+    emitEnterBaseline();
+    return true;
+  }
+
+  // If the Baseline Interpreter is enabled we resume in either the
+  // BaselineScript (if present) or Baseline Interpreter.
+  Label noBaselineScript;
+  masm.branchPtr(Assembler::BelowOrEqual, baselineAddr,
+                 ImmPtr(BASELINE_DISABLED_SCRIPT), &noBaselineScript);
+  emitEnterBaseline();
+
+  masm.bind(&noBaselineScript);
+  MOZ_CRASH("NYI: enter interpreted generator");
+}
+
+template <typename Handler>
+bool BaselineCodeGen<Handler>::emitGeneratorResume(
+    GeneratorResumeKind resumeKind) {
   frame.syncStack(0);
   masm.assertStackAlignment(sizeof(Value), 0);
 
   AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All());
   regs.take(BaselineFrameReg);
 
   // Load generator object.
   Register genObj = regs.takeAny();
   masm.unboxObject(frame.addressOfStackValue(-2), genObj);
 
   // Load callee.
   Register callee = regs.takeAny();
   masm.unboxObject(
       Address(genObj, AbstractGeneratorObject::offsetOfCalleeSlot()), callee);
 
-  // Load the script. Note that we don't relazify generator scripts, so it's
-  // guaranteed to be non-lazy.
+  // Load the return value.
+  ValueOperand retVal = regs.takeAnyValue();
+  masm.loadValue(frame.addressOfStackValue(-1), retVal);
+
+  // Branch to interpret if the script does not have a BaselineScript (if the
+  // Baseline Interpreter is not enabled). Note that we don't relazify generator
+  // scripts, so the function is guaranteed to be non-lazy.
+  Label interpret;
   Register scratch1 = regs.takeAny();
   masm.loadPtr(Address(callee, JSFunction::offsetOfScript()), scratch1);
-
-  // Load the BaselineScript or call a stub if we don't have one.
-  Label interpret;
-  masm.loadPtr(Address(scratch1, JSScript::offsetOfBaselineScript()), scratch1);
-  masm.branchPtr(Assembler::BelowOrEqual, scratch1,
-                 ImmPtr(BASELINE_DISABLED_SCRIPT), &interpret);
+  if (!JitOptions.baselineInterpreter) {
+    Address baselineAddr(scratch1, JSScript::offsetOfBaselineScript());
+    masm.branchPtr(Assembler::BelowOrEqual, baselineAddr,
+                   ImmPtr(BASELINE_DISABLED_SCRIPT), &interpret);
+  }
 
 #ifdef JS_TRACE_LOGGING
   if (JS::TraceLoggerSupported() && !emitTraceLoggerResume(scratch1, regs)) {
     return false;
   }
 #endif
 
   // Push |undefined| for all formals.
@@ -5640,20 +5688,16 @@ bool BaselineCompilerCodeGen::emit_JSOP_
   masm.push(scratch2);  // frame descriptor
 
   // PushCalleeToken bumped framePushed. Reset it.
   MOZ_ASSERT(masm.framePushed() == sizeof(uintptr_t));
   masm.setFramePushed(0);
 
   regs.add(callee);
 
-  // Load the return value.
-  ValueOperand retVal = regs.takeAnyValue();
-  masm.loadValue(frame.addressOfStackValue(-1), retVal);
-
   // Push a fake return address on the stack. We will resume here when the
   // generator returns.
   Label genStart, returnTarget;
 #ifdef JS_USE_LINK_REGISTER
   masm.call(&genStart);
 #else
   masm.callAndPushReturnAddress(&genStart);
 #endif
@@ -5726,49 +5770,48 @@ bool BaselineCompilerCodeGen::emit_JSOP_
         Imm32(0),
         Address(scratch2, ObjectElements::offsetOfInitializedLength()));
 
     Label loop, loopDone;
     masm.bind(&loop);
     masm.branchTest32(Assembler::Zero, initLength, initLength, &loopDone);
     {
       masm.pushValue(Address(scratch2, 0));
-      masm.guardedCallPreBarrier(Address(scratch2, 0), MIRType::Value);
+      masm.guardedCallPreBarrierAnyZone(Address(scratch2, 0), MIRType::Value,
+                                        scratch1);
       masm.addPtr(Imm32(sizeof(Value)), scratch2);
       masm.sub32(Imm32(1), initLength);
       masm.jump(&loop);
     }
     masm.bind(&loopDone);
     regs.add(initLength);
   }
 
   masm.bind(&noExprStack);
   masm.pushValue(retVal);
 
   masm.switchToObjectRealm(genObj, scratch2);
 
   if (resumeKind == GeneratorResumeKind::Next) {
-    // Determine the resume address based on the resumeIndex and the
-    // resumeIndex -> native table in the BaselineScript.
-    masm.load32(
-        Address(scratch1, BaselineScript::offsetOfResumeEntriesOffset()),
-        scratch2);
-    masm.addPtr(scratch2, scratch1);
-    masm.unboxInt32(
-        Address(genObj, AbstractGeneratorObject::offsetOfResumeIndexSlot()),
-        scratch2);
-    masm.loadPtr(
-        BaseIndex(scratch1, scratch2, ScaleFromElemWidth(sizeof(uintptr_t))),
+    // Load script in scratch1.
+    masm.unboxObject(
+        Address(genObj, AbstractGeneratorObject::offsetOfCalleeSlot()),
         scratch1);
-
-    // Mark as running and jump to the generator's JIT code.
-    masm.storeValue(
-        Int32Value(AbstractGeneratorObject::RESUME_INDEX_RUNNING),
-        Address(genObj, AbstractGeneratorObject::offsetOfResumeIndexSlot()));
-    masm.jump(scratch1);
+    masm.loadPtr(Address(scratch1, JSFunction::offsetOfScript()), scratch1);
+
+    // Load resume index in scratch2 and mark generator as running.
+    Address resumeIndexSlot(genObj,
+                            AbstractGeneratorObject::offsetOfResumeIndexSlot());
+    masm.unboxInt32(resumeIndexSlot, scratch2);
+    masm.storeValue(Int32Value(AbstractGeneratorObject::RESUME_INDEX_RUNNING),
+                    resumeIndexSlot);
+
+    if (!emitEnterGeneratorCode(scratch1, scratch2, regs.getAny())) {
+      return false;
+    }
   } else {
     MOZ_ASSERT(resumeKind == GeneratorResumeKind::Throw ||
                resumeKind == GeneratorResumeKind::Return);
 
     // Update the frame's frameSize field.
     masm.computeEffectiveAddress(
         Address(BaselineFrameReg, BaselineFrame::FramePointerOffset), scratch2);
     masm.movePtr(scratch2, scratch1);
@@ -5815,52 +5858,98 @@ bool BaselineCompilerCodeGen::emit_JSOP_
     masm.jump(code);
 
     // Pop arguments and frame pointer (pushed by prepareVMCall) from
     // framePushed.
     masm.implicitPop((fun.explicitStackSlots() + 1) * sizeof(void*));
   }
 
   // If the generator script has no JIT code, call into the VM.
-  masm.bind(&interpret);
-
-  prepareVMCall();
-  if (resumeKind == GeneratorResumeKind::Next) {
-    pushArg(ImmGCPtr(cx->names().next));
-  } else if (resumeKind == GeneratorResumeKind::Throw) {
-    pushArg(ImmGCPtr(cx->names().throw_));
-  } else {
-    MOZ_ASSERT(resumeKind == GeneratorResumeKind::Return);
-    pushArg(ImmGCPtr(cx->names().return_));
-  }
-
-  masm.loadValue(frame.addressOfStackValue(-1), retVal);
-  pushArg(retVal);
-  pushArg(genObj);
-
-  using Fn = bool (*)(JSContext*, HandleObject, HandleValue, HandlePropertyName,
-                      MutableHandleValue);
-  if (!callVM<Fn, jit::InterpretResume>()) {
-    return false;
+  if (interpret.used()) {
+    masm.bind(&interpret);
+
+    prepareVMCall();
+    if (resumeKind == GeneratorResumeKind::Next) {
+      pushArg(ImmGCPtr(cx->names().next));
+    } else if (resumeKind == GeneratorResumeKind::Throw) {
+      pushArg(ImmGCPtr(cx->names().throw_));
+    } else {
+      MOZ_ASSERT(resumeKind == GeneratorResumeKind::Return);
+      pushArg(ImmGCPtr(cx->names().return_));
+    }
+
+    masm.loadValue(frame.addressOfStackValue(-1), retVal);
+    pushArg(retVal);
+    pushArg(genObj);
+
+    using Fn = bool (*)(JSContext*, HandleObject, HandleValue,
+                        HandlePropertyName, MutableHandleValue);
+    if (!callVM<Fn, jit::InterpretResume>()) {
+      return false;
+    }
   }
 
   // After the generator returns, we restore the stack pointer, switch back to
   // the current realm, push the return value, and we're done.
   masm.bind(&returnTarget);
   masm.computeEffectiveAddress(frame.addressOfStackValue(-1),
                                masm.getStackPointer());
-  masm.switchToRealm(handler.script()->realm(), R2.scratchReg());
+  if (JSScript* script = handler.maybeScript()) {
+    masm.switchToRealm(script->realm(), R2.scratchReg());
+  } else {
+    masm.switchToBaselineFrameRealm(R2.scratchReg());
+  }
   frame.popn(2);
   frame.push(R0);
   return true;
 }
 
 template <>
+bool BaselineCompilerCodeGen::emit_JSOP_RESUME() {
+  auto resumeKind = AbstractGeneratorObject::getResumeKind(handler.pc());
+  return emitGeneratorResume(resumeKind);
+}
+
+template <>
 bool BaselineInterpreterCodeGen::emit_JSOP_RESUME() {
-  MOZ_CRASH("NYI: interpreter JSOP_RESUME");
+  Register scratch = R0.scratchReg();
+
+  LoadUint8Operand(masm, PCRegAtStart, scratch);
+
+  Label throw_, return_, done;
+  masm.branch32(Assembler::Equal, scratch,
+                Imm32(int32_t(GeneratorResumeKind::Throw)), &throw_);
+  masm.branch32(Assembler::Equal, scratch,
+                Imm32(int32_t(GeneratorResumeKind::Return)), &return_);
+
+#ifdef DEBUG
+  Label ok;
+  masm.branch32(Assembler::Equal, scratch,
+                Imm32(int32_t(GeneratorResumeKind::Next)), &ok);
+  masm.assumeUnreachable("JSOP_RESUME invalid ResumeKind");
+  masm.bind(&ok);
+#endif
+  if (!emitGeneratorResume(GeneratorResumeKind::Next)) {
+    return false;
+  }
+  masm.jump(&done);
+
+  masm.bind(&throw_);
+  if (!emitGeneratorResume(GeneratorResumeKind::Throw)) {
+    return false;
+  }
+  masm.jump(&done);
+
+  masm.bind(&return_);
+  if (!emitGeneratorResume(GeneratorResumeKind::Return)) {
+    return false;
+  }
+
+  masm.bind(&done);
+  return true;
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_DEBUGCHECKSELFHOSTED() {
 #ifdef DEBUG
   frame.syncStack(0);
 
   masm.loadValue(frame.addressOfStackValue(-1), R0);
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -9,16 +9,19 @@
 
 #include "jit/BaselineFrameInfo.h"
 #include "jit/BaselineIC.h"
 #include "jit/BytecodeAnalysis.h"
 #include "jit/FixedList.h"
 #include "jit/MacroAssembler.h"
 
 namespace js {
+
+enum class GeneratorResumeKind;
+
 namespace jit {
 
 #define OPCODE_LIST(_)          \
   _(JSOP_NOP)                   \
   _(JSOP_NOP_DESTRUCTURING)     \
   _(JSOP_LABEL)                 \
   _(JSOP_ITERNEXT)              \
   _(JSOP_POP)                   \
@@ -389,16 +392,22 @@ class BaselineCodeGen {
   template <typename F>
   MOZ_MUST_USE bool emitTestScriptFlag(JSScript::ImmutableFlags flag,
                                        bool value, const F& emit,
                                        Register scratch);
   template <typename F>
   MOZ_MUST_USE bool emitTestScriptFlag(JSScript::MutableFlags flag, bool value,
                                        const F& emit, Register scratch);
 
+  MOZ_MUST_USE bool emitGeneratorResume(GeneratorResumeKind resumeKind);
+  MOZ_MUST_USE bool emitEnterGeneratorCode(Register script,
+                                           Register resumeIndex,
+                                           Register scratch);
+  void emitJumpToInterpretOpLabel();
+
   MOZ_MUST_USE bool emitCheckThis(ValueOperand val, bool reinit = false);
   void emitLoadReturnValue(ValueOperand val);
   void emitPushNonArrowFunctionNewTarget();
   void emitGetAliasedVar(ValueOperand dest);
 
   MOZ_MUST_USE bool emitNextIC();
   MOZ_MUST_USE bool emitInterruptCheck();
   MOZ_MUST_USE bool emitWarmUpCounterIncrement();
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1131,25 +1131,25 @@ JS_PUBLIC_API void* JS_realloc(JSContext
 }
 
 JS_PUBLIC_API void JS_free(JSContext* cx, void* p) { return js_free(p); }
 
 JS_PUBLIC_API void* JS_string_malloc(JSContext* cx, size_t nbytes) {
   AssertHeapIsIdle();
   CHECK_THREAD(cx);
   return static_cast<void*>(
-      cx->maybe_pod_malloc<uint8_t>(nbytes, js::MallocArena));
+      cx->maybe_pod_malloc<uint8_t>(nbytes, js::StringBufferArena));
 }
 
 JS_PUBLIC_API void* JS_string_realloc(JSContext* cx, void* p, size_t oldBytes,
                                       size_t newBytes) {
   AssertHeapIsIdle();
   CHECK_THREAD(cx);
   return static_cast<void*>(cx->maybe_pod_realloc<uint8_t>(
-      static_cast<uint8_t*>(p), oldBytes, newBytes, js::MallocArena));
+      static_cast<uint8_t*>(p), oldBytes, newBytes, js::StringBufferArena));
 }
 
 JS_PUBLIC_API void JS_string_free(JSContext* cx, void* p) { return js_free(p); }
 
 JS_PUBLIC_API void JS_freeop(JSFreeOp* fop, void* p) {
   return FreeOp::get(fop)->free_(p);
 }
 
--- a/js/src/jsutil.cpp
+++ b/js/src/jsutil.cpp
@@ -78,28 +78,46 @@ void FailureSimulator::reset() {
 }  // namespace oom
 }  // namespace js
 #endif  // defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
 
 bool js::gDisablePoisoning = false;
 
 JS_PUBLIC_DATA arena_id_t js::MallocArena;
 JS_PUBLIC_DATA arena_id_t js::ArrayBufferContentsArena;
+JS_PUBLIC_DATA arena_id_t js::StringBufferArena;
 
 void js::InitMallocAllocator() {
   MallocArena = moz_create_arena();
   ArrayBufferContentsArena = moz_create_arena();
+  StringBufferArena = moz_create_arena();
 }
 
 void js::ShutDownMallocAllocator() {
   // Until Bug 1364359 is fixed it is unsafe to call moz_dispose_arena.
   // moz_dispose_arena(MallocArena);
   // moz_dispose_arena(ArrayBufferContentsArena);
 }
 
+#ifdef MOZ_DEBUG
+extern void js::AssertJSStringBufferInCorrectArena(const void* ptr) {
+//  `jemalloc_ptr_info()` only exists if MOZ_MEMORY is defined, and it only
+//  returns an arenaId if MOZ_DEBUG is defined. Otherwise, this function is
+//  a no-op.
+#  if defined(MOZ_MEMORY) && defined(MOZ_DEBUG)
+  if (ptr) {
+    jemalloc_ptr_info_t ptrInfo{};
+    jemalloc_ptr_info(ptr, &ptrInfo);
+    MOZ_ASSERT(ptrInfo.tag != TagUnknown);
+    MOZ_ASSERT(ptrInfo.arenaId == js::StringBufferArena);
+  }
+#  endif
+}
+#endif
+
 JS_PUBLIC_API void JS_Assert(const char* s, const char* file, int ln) {
   MOZ_ReportAssertionFailure(s, file, ln);
   MOZ_CRASH();
 }
 
 #ifdef __linux__
 
 #  include <malloc.h>
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -8100,17 +8100,17 @@ static bool WasmLoop(JSContext* cx, unsi
   return true;
 #endif
 }
 
 static constexpr uint32_t DOM_OBJECT_SLOT = 0;
 
 static const JSClass* GetDomClass();
 
-static JSObject* GetDOMPrototype(JSObject* global);
+static JSObject* GetDOMPrototype(JSContext* cx, JSObject* global);
 
 static const JSClass TransplantableDOMObjectClass = {
     "TransplantableDOMObject",
     JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(1)};
 
 static const Class TransplantableDOMProxyObjectClass =
     PROXY_CLASS_DEF("TransplantableDOMProxyObject",
                     JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(1));
@@ -8224,22 +8224,22 @@ static bool TransplantObject(JSContext* 
     expandoObject =
         TransplantableDOMProxyHandler::GetAndClearExpandoObject(source);
   }
 
   JSAutoRealm ar(cx, newGlobal);
 
   RootedObject proto(cx);
   if (JS_GetClass(source) == GetDomClass()) {
-    proto = GetDOMPrototype(newGlobal);
+    proto = GetDOMPrototype(cx, newGlobal);
   } else {
     proto = JS::GetRealmObjectPrototype(cx);
-    if (!proto) {
-      return false;
-    }
+  }
+  if (!proto) {
+    return false;
   }
 
   RootedObject target(cx, JS_CloneObject(cx, source, proto));
   if (!target) {
     return false;
   }
 
   if (GetObjectCompartment(source) != GetObjectCompartment(target) &&
@@ -9815,18 +9815,22 @@ static bool dom_genericMethod(JSContext*
   return method(cx, obj, val.toPrivate(), JSJitMethodCallArgs(args));
 }
 
 static void InitDOMObject(HandleObject obj) {
   SetReservedSlot(obj, DOM_OBJECT_SLOT,
                   PrivateValue(const_cast<void*>(DOM_PRIVATE_VALUE)));
 }
 
-static JSObject* GetDOMPrototype(JSObject* global) {
+static JSObject* GetDOMPrototype(JSContext* cx, JSObject* global) {
   MOZ_ASSERT(JS_IsGlobalObject(global));
+  if (GetObjectJSClass(global) != &global_class) {
+    JS_ReportErrorASCII(cx, "Can't get FakeDOMObject prototype in sandbox");
+    return nullptr;
+  }
   MOZ_ASSERT(GetReservedSlot(global, DOM_PROTOTYPE_SLOT).isObject());
   return &GetReservedSlot(global, DOM_PROTOTYPE_SLOT).toObject();
 }
 
 static bool dom_constructor(JSContext* cx, unsigned argc, JS::Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
 
   RootedObject callee(cx, &args.callee());
--- a/js/src/util/StringBuffer.cpp
+++ b/js/src/util/StringBuffer.cpp
@@ -45,17 +45,17 @@ char16_t* StringBuffer::stealChars() {
   }
 
   return ExtractWellSized<char16_t>(twoByteChars());
 }
 
 bool StringBuffer::inflateChars() {
   MOZ_ASSERT(isLatin1());
 
-  TwoByteCharBuffer twoByte(cx);
+  TwoByteCharBuffer twoByte(TempAllocPolicy{cx_, arenaId_});
 
   /*
    * Note: we don't use Vector::capacity() because it always returns a
    * value >= sInlineCapacity. Since Latin1CharBuffer::sInlineCapacity >
    * TwoByteCharBuffer::sInlineCapacitychars, we'd always malloc here.
    */
   size_t capacity = Max(reserved_, latin1Chars().length());
   if (!twoByte.reserve(capacity)) {
@@ -101,45 +101,45 @@ JSFlatString* StringBuffer::finishString
   cx->updateMallocCounter(sizeof(CharT) * len);
 
   return str;
 }
 
 JSFlatString* JSStringBuilder::finishString() {
   size_t len = length();
   if (len == 0) {
-    return cx->names().empty;
+    return cx_->names().empty;
   }
 
-  if (!JSString::validateLength(cx, len)) {
+  if (!JSString::validateLength(cx_, len)) {
     return nullptr;
   }
 
   JS_STATIC_ASSERT(JSFatInlineString::MAX_LENGTH_TWO_BYTE <
                    TwoByteCharBuffer::InlineLength);
   JS_STATIC_ASSERT(JSFatInlineString::MAX_LENGTH_LATIN1 <
                    Latin1CharBuffer::InlineLength);
 
-  return isLatin1() ? finishStringInternal<Latin1Char>(cx)
-                    : finishStringInternal<char16_t>(cx);
+  return isLatin1() ? finishStringInternal<Latin1Char>(cx_)
+                    : finishStringInternal<char16_t>(cx_);
 }
 
 JSAtom* StringBuffer::finishAtom() {
   size_t len = length();
   if (len == 0) {
-    return cx->names().empty;
+    return cx_->names().empty;
   }
 
   if (isLatin1()) {
-    JSAtom* atom = AtomizeChars(cx, latin1Chars().begin(), len);
+    JSAtom* atom = AtomizeChars(cx_, latin1Chars().begin(), len);
     latin1Chars().clear();
     return atom;
   }
 
-  JSAtom* atom = AtomizeChars(cx, twoByteChars().begin(), len);
+  JSAtom* atom = AtomizeChars(cx_, twoByteChars().begin(), len);
   twoByteChars().clear();
   return atom;
 }
 
 bool js::ValueToStringBufferSlow(JSContext* cx, const Value& arg,
                                  StringBuffer& sb) {
   RootedValue v(cx, arg);
   if (!ToPrimitive(cx, JSTYPE_STRING, &v)) {
--- a/js/src/util/StringBuffer.h
+++ b/js/src/util/StringBuffer.h
@@ -34,17 +34,18 @@ class StringBuffer {
 
   /*
    * The Vector's buffer may be either stolen or copied, so we need to use
    * TempAllocPolicy and account for the memory manually when stealing.
    */
   using Latin1CharBuffer = BufferType<Latin1Char>;
   using TwoByteCharBuffer = BufferType<char16_t>;
 
-  JSContext* cx;
+  JSContext* cx_;
+  const arena_id_t& arenaId_;
 
   /*
    * If Latin1 strings are enabled, cb starts out as a Latin1CharBuffer. When
    * a TwoByte char is appended, inflateChars() constructs a TwoByteCharBuffer
    * and copies the Latin1 chars.
    */
   mozilla::MaybeOneOf<Latin1CharBuffer, TwoByteCharBuffer> cb;
 
@@ -92,18 +93,20 @@ class StringBuffer {
   }
 
   MOZ_MUST_USE bool inflateChars();
 
   template <typename CharT>
   JSFlatString* finishStringInternal(JSContext* cx);
 
  public:
-  explicit StringBuffer(JSContext* cx) : cx(cx), reserved_(0) {
-    cb.construct<Latin1CharBuffer>(cx);
+  explicit StringBuffer(JSContext* cx,
+                        const arena_id_t& arenaId = js::MallocArena)
+      : cx_(cx), arenaId_(arenaId), reserved_(0) {
+    cb.construct<Latin1CharBuffer>(TempAllocPolicy{cx_, arenaId_});
   }
 
   void clear() {
     if (isLatin1()) {
       latin1Chars().clear();
     } else {
       twoByteChars().clear();
     }
@@ -273,17 +276,18 @@ class StringBuffer {
    * exactly the characters in this buffer (inflated to TwoByte), it is *not*
    * null-terminated unless the last appended character was '\0'.
    */
   char16_t* stealChars();
 };
 
 class JSStringBuilder : public StringBuffer {
  public:
-  explicit JSStringBuilder(JSContext* cx) : StringBuffer(cx) {}
+  explicit JSStringBuilder(JSContext* cx)
+      : StringBuffer(cx, js::StringBufferArena) {}
 
   /*
    * Creates a string from the characters in this buffer, then (regardless
    * whether string creation succeeded or failed) empties the buffer.
    */
   JSFlatString* finishString();
 };
 
@@ -352,26 +356,26 @@ inline bool StringBuffer::appendSubstrin
   }
   return base->hasLatin1Chars()
              ? twoByteChars().append(base->latin1Chars(nogc) + off, len)
              : twoByteChars().append(base->twoByteChars(nogc) + off, len);
 }
 
 inline bool StringBuffer::appendSubstring(JSString* base, size_t off,
                                           size_t len) {
-  JSLinearString* linear = base->ensureLinear(cx);
+  JSLinearString* linear = base->ensureLinear(cx_);
   if (!linear) {
     return false;
   }
 
   return appendSubstring(linear, off, len);
 }
 
 inline bool StringBuffer::append(JSString* str) {
-  JSLinearString* linear = str->ensureLinear(cx);
+  JSLinearString* linear = str->ensureLinear(cx_);
   if (!linear) {
     return false;
   }
 
   return append(linear);
 }
 
 /* ES5 9.8 ToString, appending the result to the string buffer. */
--- a/js/src/vm/AsyncFunction.cpp
+++ b/js/src/vm/AsyncFunction.cpp
@@ -111,25 +111,26 @@ static bool AsyncFunctionResume(JSContex
                                    : cx->names().AsyncFunctionThrow;
   FixedInvokeArgs<1> args(cx);
   args[0].set(valueOrReason);
   RootedValue generatorOrValue(cx, ObjectValue(*generator));
   if (!CallSelfHostedFunction(cx, funName, generatorOrValue, args,
                               &generatorOrValue)) {
     if (!generator->isClosed()) {
       generator->setClosed();
+    }
 
-      // Handle the OOM case mentioned above.
-      if (cx->isExceptionPending()) {
-        RootedValue exn(cx);
-        if (!GetAndClearException(cx, &exn)) {
-          return false;
-        }
-        return AsyncFunctionThrown(cx, resultPromise, exn);
+    // Handle the OOM case mentioned above.
+    if (resultPromise->state() == JS::PromiseState::Pending &&
+        cx->isExceptionPending()) {
+      RootedValue exn(cx);
+      if (!GetAndClearException(cx, &exn)) {
+        return false;
       }
+      return AsyncFunctionThrown(cx, resultPromise, exn);
     }
     return false;
   }
 
   MOZ_ASSERT_IF(generator->isClosed(), generatorOrValue.isObject());
   MOZ_ASSERT_IF(generator->isClosed(),
                 &generatorOrValue.toObject() == resultPromise);
   MOZ_ASSERT_IF(!generator->isClosed(), generator->isAfterAwait());
--- a/js/src/vm/CharacterEncoding.cpp
+++ b/js/src/vm/CharacterEncoding.cpp
@@ -697,17 +697,17 @@ bool StringBuffer::append(const Utf8Unit
 
   // Determine how many UTF-16 code units are required to represent the
   // remaining units.
   size_t utf16Len = 0;
   auto countInflated = [&utf16Len](char16_t c) -> LoopDisposition {
     utf16Len++;
     return LoopDisposition::Continue;
   };
-  if (!InflateUTF8ToUTF16<OnUTF8Error::Throw>(cx, remainingUtf8,
+  if (!InflateUTF8ToUTF16<OnUTF8Error::Throw>(cx_, remainingUtf8,
                                               countInflated)) {
     return false;
   }
 
   TwoByteCharBuffer& buf = twoByteChars();
 
   size_t i = buf.length();
   if (!buf.growByUninitialized(utf16Len)) {
@@ -718,12 +718,12 @@ bool StringBuffer::append(const Utf8Unit
 
   char16_t* toFill = &buf[i];
   auto appendUtf16 = [&toFill](char16_t unit) {
     *toFill++ = unit;
     return LoopDisposition::Continue;
   };
 
   MOZ_ALWAYS_TRUE(
-      InflateUTF8ToUTF16<OnUTF8Error::Throw>(cx, remainingUtf8, appendUtf16));
+      InflateUTF8ToUTF16<OnUTF8Error::Throw>(cx_, remainingUtf8, appendUtf16));
   MOZ_ASSERT(toFill == buf.end());
   return true;
 }
--- a/js/src/vm/Compartment.cpp
+++ b/js/src/vm/Compartment.cpp
@@ -107,26 +107,26 @@ static JSString* CopyStringPure(JSContex
     return chars.isLatin1() ? NewStringCopyN<CanGC>(
                                   cx, chars.latin1Range().begin().get(), len)
                             : NewStringCopyNDontDeflate<CanGC>(
                                   cx, chars.twoByteRange().begin().get(), len);
   }
 
   if (str->hasLatin1Chars()) {
     UniquePtr<Latin1Char[], JS::FreePolicy> copiedChars =
-        str->asRope().copyLatin1CharsZ(cx, js::MallocArena);
+        str->asRope().copyLatin1CharsZ(cx, js::StringBufferArena);
     if (!copiedChars) {
       return nullptr;
     }
 
     return NewString<CanGC>(cx, std::move(copiedChars), len);
   }
 
   UniqueTwoByteChars copiedChars =
-      str->asRope().copyTwoByteCharsZ(cx, js::MallocArena);
+      str->asRope().copyTwoByteCharsZ(cx, js::StringBufferArena);
   if (!copiedChars) {
     return nullptr;
   }
 
   return NewStringDontDeflate<CanGC>(cx, std::move(copiedChars), len);
 }
 
 bool Compartment::wrap(JSContext* cx, MutableHandleString strp) {
--- a/js/src/vm/InlineCharBuffer-inl.h
+++ b/js/src/vm/InlineCharBuffer-inl.h
@@ -69,40 +69,42 @@ class MOZ_NON_PARAM InlineCharBuffer {
   bool maybeAlloc(JSContext* cx, size_t length) {
     assertValidRequest(0, length);
 
     if (length <= InlineCapacity) {
       return true;
     }
 
     MOZ_ASSERT(!heapStorage, "heap storage already allocated");
-    heapStorage = cx->make_pod_array<CharT>(length + 1);
+    heapStorage = cx->make_pod_array<CharT>(length + 1, js::StringBufferArena);
     return !!heapStorage;
   }
 
   bool maybeRealloc(JSContext* cx, size_t oldLength, size_t newLength) {
     assertValidRequest(oldLength, newLength);
 
     if (newLength <= InlineCapacity) {
       return true;
     }
 
     if (!heapStorage) {
-      heapStorage = cx->make_pod_array<CharT>(newLength + 1);
+      heapStorage =
+          cx->make_pod_array<CharT>(newLength + 1, js::StringBufferArena);
       if (!heapStorage) {
         return false;
       }
 
       MOZ_ASSERT(oldLength <= InlineCapacity);
       mozilla::PodCopy(heapStorage.get(), inlineStorage, oldLength);
       return true;
     }
 
     CharT* oldChars = heapStorage.release();
-    CharT* newChars = cx->pod_realloc(oldChars, oldLength + 1, newLength + 1);
+    CharT* newChars = cx->pod_realloc(oldChars, oldLength + 1, newLength + 1,
+                                      js::StringBufferArena);
     if (!newChars) {
       js_free(oldChars);
       return false;
     }
 
     heapStorage.reset(newChars);
     return true;
   }
--- a/js/src/vm/JSAtom.cpp
+++ b/js/src/vm/JSAtom.cpp
@@ -894,17 +894,18 @@ static MOZ_ALWAYS_INLINE JSFlatString* M
                                          chars->encoding);
     return str;
   }
 
   // MakeAtomUTF8Helper is called from deep in the Atomization path, which
   // expects functions to fail gracefully with nullptr on OOM, without throwing.
   //
   // Flat strings are null-terminated. Leave room with length + 1
-  UniquePtr<CharT[], JS::FreePolicy> newStr(js_pod_malloc<CharT>(length + 1));
+  UniquePtr<CharT[], JS::FreePolicy> newStr(
+      js_pod_arena_malloc<CharT>(js::StringBufferArena, length + 1));
   if (!newStr) {
     return nullptr;
   }
 
   InflateUTF8CharsToBufferAndTerminate(chars->utf8, newStr.get(), length,
                                        chars->encoding);
 
   return JSFlatString::new_<NoGC>(cx, std::move(newStr), length);
deleted file mode 100644
--- a/js/src/vm/Stopwatch.h
+++ /dev/null
@@ -1,400 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- * vim: set ts=8 sts=2 et sw=2 tw=80:
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef vm_Stopwatch_h
-#define vm_Stopwatch_h
-
-#include "mozilla/RefPtr.h"
-#include "mozilla/Vector.h"
-
-#include "jsapi.h"
-
-/*
-  An API for following in real-time the amount of CPU spent executing
-  webpages, add-ons, etc.
-*/
-
-namespace js {
-
-/**
- * A container for performance groups.
- *
- * Performance monitoring deals with the execution duration of code
- * that belongs to components, for a notion of components defined by
- * the embedding.  Typically, in a web browser, a component may be a
- * webpage and/or a frame and/or a module and/or an add-on and/or a
- * sandbox and/or a process etc.
- *
- * A PerformanceGroupHolder is owned by a JS::Realm and maps that realm
- * to all the components to which it belongs.
- */
-struct PerformanceGroupHolder {
-  /**
-   * Get the groups to which this compartment belongs.
-   *
-   * Pre-condition: Execution must have entered the compartment.
-   *
-   * May return `nullptr` if the embedding has not initialized
-   * support for performance groups.
-   */
-  const PerformanceGroupVector* getGroups(JSContext*);
-
-  explicit PerformanceGroupHolder(JSRuntime* runtime)
-      : runtime_(runtime), initialized_(false) {}
-  ~PerformanceGroupHolder();
-  void unlink();
-
- private:
-  JSRuntime* runtime_;
-
-  // `true` once a call to `getGroups` has succeeded.
-  bool initialized_;
-
-  // The groups to which this compartment belongs. Filled if and only
-  // if `initialized_` is `true`.
-  PerformanceGroupVector groups_;
-};
-
-/**
- * Container class for everything related to performance monitoring.
- */
-struct PerformanceMonitoring {
-  /**
-   * The number of the current iteration of the event loop.
-   */
-  uint64_t iteration() { return iteration_; }
-
-  explicit PerformanceMonitoring()
-      : totalCPOWTime(0),
-        stopwatchStartCallback(nullptr),
-        stopwatchStartClosure(nullptr),
-        stopwatchCommitCallback(nullptr),
-        stopwatchCommitClosure(nullptr),
-        getGroupsCallback(nullptr),
-        getGroupsClosure(nullptr),
-        isMonitoringJank_(false),
-        isMonitoringCPOW_(false),
-        iteration_(0),
-        startedAtIteration_(0),
-        highestTimestampCounter_(0) {}
-
-  /**
-   * Reset the stopwatch.
-   *
-   * This method is meant to be called whenever we start
-   * processing an event, to ensure that we stop any ongoing
-   * measurement that would otherwise provide irrelevant
-   * results.
-   */
-  void reset();
-
-  /**
-   * Start the stopwatch.
-   *
-   * This method is meant to be called once we know that the
-   * current event contains JavaScript code to execute. Calling
-   * this several times during the same iteration is idempotent.
-   */
-  void start();
-
-  /**
-   * Commit the performance data collected since the last call
-   * to `start()`, unless `reset()` has been called since then.
-   */
-  bool commit();
-
-  /**
-   * Liberate memory and references.
-   */
-  void dispose(JSRuntime* rtx);
-
-  /**
-   * Activate/deactivate stopwatch measurement of jank.
-   *
-   * Noop if `value` is `true` and the stopwatch is already
-   * measuring jank, or if `value` is `false` and the stopwatch
-   * is not measuring jank.
-   *
-   * Otherwise, any pending measurements are dropped, but previous
-   * measurements remain stored.
-   *
-   * May return `false` if the underlying hashtable cannot be allocated.
-   */
-  bool setIsMonitoringJank(bool value) {
-    if (isMonitoringJank_ != value) {
-      reset();
-    }
-
-    isMonitoringJank_ = value;
-    return true;
-  }
-  bool isMonitoringJank() const { return isMonitoringJank_; }
-
-  /**
-   * Mark that a group has been used in this iteration.
-   */
-  bool addRecentGroup(PerformanceGroup* group);
-
-  /**
-   * Activate/deactivate stopwatch measurement of CPOW.
-   *
-   * Noop if `value` is `true` and the stopwatch is already
-   * measuring CPOW, or if `value` is `false` and the stopwatch
-   * is not measuring CPOW.
-   *
-   * Otherwise, any pending measurements are dropped, but previous
-   * measurements remain stored.
-   *
-   * May return `false` if the underlying hashtable cannot be allocated.
-   */
-  bool setIsMonitoringCPOW(bool value) {
-    if (isMonitoringCPOW_ != value) {
-      reset();
-    }
-
-    isMonitoringCPOW_ = value;
-    return true;
-  }
-
-  bool isMonitoringCPOW() const { return isMonitoringCPOW_; }
-
-  /**
-   * Callbacks called when we start executing an event/when we have
-   * run to completion (including enqueued microtasks).
-   *
-   * If there are no nested event loops, each call to
-   * `stopwatchStartCallback` is followed by a call to
-   * `stopwatchCommitCallback`. However, embedders should not assume
-   * that this will always be the case, unless they take measures to
-   * prevent nested event loops.
-   *
-   * In presence of nested event loops, several calls to
-   * `stopwatchStartCallback` may occur before a call to
-   * `stopwatchCommitCallback`. Embedders should assume that a
-   * second call to `stopwatchStartCallback` cancels any measure
-   * started by the previous calls to `stopwatchStartCallback` and
-   * which have not been committed by `stopwatchCommitCallback`.
-   */
-  void setStopwatchStartCallback(js::StopwatchStartCallback cb, void* closure) {
-    stopwatchStartCallback = cb;
-    stopwatchStartClosure = closure;
-  }
-  void setStopwatchCommitCallback(js::StopwatchCommitCallback cb,
-                                  void* closure) {
-    stopwatchCommitCallback = cb;
-    stopwatchCommitClosure = closure;
-  }
-
-  /**
-   * Callback called to associate a JS::Realm to the set of
-   * `PerformanceGroup`s that represent the components to which
-   * it belongs.
-   */
-  void setGetGroupsCallback(js::GetGroupsCallback cb, void* closure) {
-    getGroupsCallback = cb;
-    getGroupsClosure = closure;
-  }
-
-  /**
-   * The total amount of time spent waiting on CPOWs since the
-   * start of the process, in microseconds.
-   */
-  uint64_t totalCPOWTime;
-
-  /**
-   * A variant of RDTSC artificially made monotonic.
-   *
-   * Always return 0 on platforms that do not support RDTSC.
-   */
-  uint64_t monotonicReadTimestampCounter();
-
-  /**
-   * Data extracted by the AutoStopwatch to determine how often
-   * we reschedule the process to a different CPU during the
-   * execution of JS.
-   *
-   * Warning: These values are incremented *only* on platforms
-   * that offer a syscall/libcall to check on which CPU a
-   * process is currently executed.
-   */
-  struct TestCpuRescheduling {
-    // Incremented once we have finished executing code
-    // in a group, if the CPU on which we started
-    // execution is the same as the CPU on which
-    // we finished.
-    uint64_t stayed;
-    // Incremented once we have finished executing code
-    // in a group, if the CPU on which we started
-    // execution is different from the CPU on which
-    // we finished.
-    uint64_t moved;
-    TestCpuRescheduling() : stayed(0), moved(0) {}
-  };
-  TestCpuRescheduling testCpuRescheduling;
-
- private:
-  PerformanceMonitoring(const PerformanceMonitoring&) = delete;
-  PerformanceMonitoring& operator=(const PerformanceMonitoring&) = delete;
-
- private:
-  friend struct PerformanceGroupHolder;
-  js::StopwatchStartCallback stopwatchStartCallback;
-  void* stopwatchStartClosure;
-  js::StopwatchCommitCallback stopwatchCommitCallback;
-  void* stopwatchCommitClosure;
-
-  js::GetGroupsCallback getGroupsCallback;
-  void* getGroupsClosure;
-
-  /**
-   * `true` if stopwatch monitoring is active for Jank, `false` otherwise.
-   */
-  bool isMonitoringJank_;
-  /**
-   * `true` if stopwatch monitoring is active for CPOW, `false` otherwise.
-   */
-  bool isMonitoringCPOW_;
-
-  /**
-   * The number of times we have entered the event loop.
-   * Used to reset counters whenever we enter the loop,
-   * which may be caused either by having completed the
-   * previous run of the event loop, or by entering a
-   * nested loop.
-   *
-   * Always incremented by 1, may safely overflow.
-   */
-  uint64_t iteration_;
-
-  /**
-   * The iteration at which the stopwatch was last started.
-   *
-   * Used both to avoid starting the stopwatch several times
-   * during the same event loop and to avoid committing stale
-   * stopwatch results.
-   */
-  uint64_t startedAtIteration_;
-
-  /**
-   * Groups used in the current iteration.
-   */
-  PerformanceGroupVector recentGroups_;
-
-  /**
-   * The highest value of the timestamp counter encountered
-   * during this iteration.
-   */
-  uint64_t highestTimestampCounter_;
-};
-
-// Temporary disable untested code path.
-#if 0  // WINVER >= 0x0600
-struct cpuid_t {
-    uint16_t group_;
-    uint8_t number_;
-    cpuid_t(uint16_t group, uint8_t number)
-        : group_(group),
-          number_(number)
-    { }
-    cpuid_t()
-        : group_(0),
-          number_(0)
-    { }
-};
-#else
-typedef struct {
-} cpuid_t;
-#endif  // defined(WINVER >= 0x0600)
-
-/**
- * RAII class to start/stop measuring performance when
- * entering/leaving a compartment.
- */
-class AutoStopwatch final {
-  // The context with which this object was initialized.
-  // Non-null.
-  JSContext* const cx_;
-
-  // An indication of the number of times we have entered the event
-  // loop.  Used only for comparison.
-  uint64_t iteration_;
-
-  // `true` if we are monitoring jank, `false` otherwise.
-  bool isMonitoringJank_;
-  // `true` if we are monitoring CPOW, `false` otherwise.
-  bool isMonitoringCPOW_;
-
-  // Timestamps captured while starting the stopwatch.
-  uint64_t cyclesStart_;
-  uint64_t CPOWTimeStart_;
-
-  // The CPU on which we started the measure. Defined only
-  // if `isMonitoringJank_` is `true`.
-  cpuid_t cpuStart_;
-
-  PerformanceGroupVector groups_;
-
- public:
-  // If the stopwatch is active, constructing an instance of
-  // AutoStopwatch causes it to become the current owner of the
-  // stopwatch.
-  //
-  // Previous owner is restored upon destruction.
-  explicit AutoStopwatch(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
-  ~AutoStopwatch();
-
- private:
-  void inline enter();
-
-  bool inline exit();
-
-  // Attempt to acquire a group
-  // If the group is inactive or if the group already has a stopwatch,
-  // do nothing and return `null`.
-  // Otherwise, bind the group to `this` for the current iteration
-  // and return `group`.
-  PerformanceGroup* acquireGroup(PerformanceGroup* group);
-
-  // Release a group. Noop if `this` is not the stopwatch of
-  // `group` for the current iteration.
-  void releaseGroup(PerformanceGroup* group);
-
-  // Add recent changes to all the groups owned by this stopwatch.
-  // Mark the groups as changed recently.
-  bool addToGroups(uint64_t cyclesDelta, uint64_t CPOWTimeDelta);
-
-  // Add recent changes to a single group. Mark the group as changed recently.
-  bool addToGroup(JSRuntime* runtime, uint64_t cyclesDelta,
-                  uint64_t CPOWTimeDelta, PerformanceGroup* group);
-
-  // Update telemetry statistics.
-  void updateTelemetry(const cpuid_t& a, const cpuid_t& b);
-
-  // Perform a subtraction for a quantity that should be monotonic
-  // but is not guaranteed to be so.
-  //
-  // If `start <= end`, return `end - start`.
-  // Otherwise, return `0`.
-  uint64_t inline getDelta(const uint64_t end, const uint64_t start) const;
-
-  // Return the value of the Timestamp Counter, as provided by the CPU.
-  // 0 on platforms for which we do not have access to a Timestamp Counter.
-  uint64_t inline getCycles(JSRuntime*) const;
-
-  // Return the identifier of the current CPU, on platforms for which we have
-  // access to the current CPU.
-  cpuid_t inline getCPU() const;
-
-  // Compare two CPU identifiers.
-  bool inline isSameCPU(const cpuid_t& a, const cpuid_t& b) const;
-
- private:
-  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER;
-};
-
-}  // namespace js
-
-#endif  // vm_Stopwatch_h
--- a/js/src/vm/StringType-inl.h
+++ b/js/src/vm/StringType-inl.h
@@ -244,22 +244,26 @@ MOZ_ALWAYS_INLINE JSLinearString* JSDepe
   }
   str->init(cx, base, start, length);
   return str;
 }
 
 MOZ_ALWAYS_INLINE void JSFlatString::init(const char16_t* chars,
                                           size_t length) {
   setLengthAndFlags(length, INIT_FLAT_FLAGS);
+  // Check that the new buffer is located in the StringBufferArena
+  checkStringCharsArena(chars);
   d.s.u2.nonInlineCharsTwoByte = chars;
 }
 
 MOZ_ALWAYS_INLINE void JSFlatString::init(const JS::Latin1Char* chars,
                                           size_t length) {
   setLengthAndFlags(length, INIT_FLAT_FLAGS | LATIN1_CHARS_BIT);
+  // Check that the new buffer is located in the StringBufferArena
+  checkStringCharsArena(chars);
   d.s.u2.nonInlineCharsLatin1 = chars;
 }
 
 template <js::AllowGC allowGC, typename CharT>
 MOZ_ALWAYS_INLINE JSFlatString* JSFlatString::new_(
     JSContext* cx, js::UniquePtr<CharT[], JS::FreePolicy> chars,
     size_t length) {
   MOZ_ASSERT(chars[length] == CharT(0));
--- a/js/src/vm/StringType.cpp
+++ b/js/src/vm/StringType.cpp
@@ -288,17 +288,17 @@ static MOZ_ALWAYS_INLINE bool AllocChars
   static const size_t DOUBLING_MAX = 1024 * 1024;
   numChars = numChars > DOUBLING_MAX ? numChars + (numChars / 8)
                                      : RoundUpPow2(numChars);
 
   /* Like length, capacity does not include the null char, so take it out. */
   *capacity = numChars - 1;
 
   JS_STATIC_ASSERT(JSString::MAX_LENGTH * sizeof(CharT) < UINT32_MAX);
-  *chars = str->zone()->pod_malloc<CharT>(numChars);
+  *chars = str->zone()->pod_malloc<CharT>(numChars, js::StringBufferArena);
   return *chars != nullptr;
 }
 
 UniqueLatin1Chars JSRope::copyLatin1CharsZ(JSContext* maybecx,
                                            arena_id_t destArenaId) const {
   return copyCharsInternal<Latin1Char>(maybecx, true, destArenaId);
 }
 
@@ -856,17 +856,17 @@ template <typename Dest, typename Src,
 static void FillFromCompatibleAndTerminate(Dest* dest, Src* src,
                                            size_t length) {
   FillFromCompatibleAndTerminate(dest, const_cast<const Src*>(src), length);
 }
 
 template <typename CharT>
 JSFlatString* JSDependentString::undependInternal(JSContext* cx) {
   size_t n = length();
-  auto s = cx->make_pod_array<CharT>(n + 1);
+  auto s = cx->make_pod_array<CharT>(n + 1, js::StringBufferArena);
   if (!s) {
     return nullptr;
   }
 
   if (!isTenured()) {
     if (!cx->runtime()->gc.nursery().registerMallocedBuffer(s.get())) {
       ReportOutOfMemory(cx);
       return nullptr;
@@ -1440,17 +1440,17 @@ JSFlatString* JSString::ensureFlat(JSCon
   }
   return asExternal().ensureFlat(cx);
 }
 
 JSFlatString* JSExternalString::ensureFlat(JSContext* cx) {
   MOZ_ASSERT(hasTwoByteChars());
 
   size_t n = length();
-  auto s = cx->make_pod_array<char16_t>(n + 1);
+  auto s = cx->make_pod_array<char16_t>(n + 1, js::StringBufferArena);
   if (!s) {
     return nullptr;
   }
 
   if (!isTenured()) {
     if (!cx->runtime()->gc.nursery().registerMallocedBuffer(s.get())) {
       ReportOutOfMemory(cx);
       return nullptr;
@@ -1575,17 +1575,17 @@ static JSFlatString* NewStringDeflated(J
     return str;
   }
 
   if (JSInlineString::lengthFits<Latin1Char>(n)) {
     return NewInlineStringDeflated<allowGC>(
         cx, mozilla::Range<const char16_t>(s, n));
   }
 
-  auto news = cx->make_pod_array<Latin1Char>(n + 1);
+  auto news = cx->make_pod_array<Latin1Char>(n + 1, js::StringBufferArena);
   if (!news) {
     return nullptr;
   }
 
   MOZ_ASSERT(CanStoreCharsAsLatin1(s, n));
   FillFromCompatibleAndTerminate(news.get(), s, n);
 
   return JSFlatString::new_<allowGC>(cx, std::move(news), n);
@@ -1607,17 +1607,17 @@ static JSFlatString* NewStringDeflatedFr
     if (!str) {
       return nullptr;
     }
 
     FillFromCompatibleAndTerminate(storage, chars, length);
     return str;
   }
 
-  auto news = cx->make_pod_array<Latin1Char>(length + 1);
+  auto news = cx->make_pod_array<Latin1Char>(length + 1, js::StringBufferArena);
   if (!news) {
     cx->recoverFromOutOfMemory();
     return nullptr;
   }
 
   FillFromCompatibleAndTerminate(news.get(), chars, length);
 
   return JSFlatString::new_<NoGC>(cx, std::move(news), length);
@@ -1736,17 +1736,17 @@ JSFlatString* NewStringCopyNDontDeflate(
   if (JSFlatString* str = TryEmptyOrStaticString(cx, s, n)) {
     return str;
   }
 
   if (JSInlineString::lengthFits<CharT>(n)) {
     return NewInlineString<allowGC>(cx, mozilla::Range<const CharT>(s, n));
   }
 
-  auto news = cx->make_pod_array<CharT>(n + 1);
+  auto news = cx->make_pod_array<CharT>(n + 1, js::StringBufferArena);
   if (!news) {
     if (!allowGC) {
       cx->recoverFromOutOfMemory();
     }
     return nullptr;
   }
 
   FillAndTerminate(news.get(), s, n);
@@ -1778,17 +1778,17 @@ static JSFlatString* NewUndeflatedString
     if (!str) {
       return nullptr;
     }
 
     FillAndTerminate(storage, chars, length);
     return str;
   }
 
-  auto news = cx->make_pod_array<char16_t>(length + 1);
+  auto news = cx->make_pod_array<char16_t>(length + 1, js::StringBufferArena);
   if (!news) {
     cx->recoverFromOutOfMemory();
     return nullptr;
   }
 
   FillAndTerminate(news.get(), chars, length);
 
   return JSFlatString::new_<NoGC>(cx, std::move(news), length);
@@ -1840,28 +1840,30 @@ JSFlatString* NewStringCopyUTF8N(JSConte
   JS::SmallestEncoding encoding = JS::FindSmallestEncoding(utf8);
   if (encoding == JS::SmallestEncoding::ASCII) {
     return NewStringCopyN<allowGC>(cx, utf8.begin().get(), utf8.length());
   }
 
   size_t length;
   if (encoding == JS::SmallestEncoding::Latin1) {
     UniqueLatin1Chars latin1(
-        UTF8CharsToNewLatin1CharsZ(cx, utf8, &length, js::MallocArena).get());
+        UTF8CharsToNewLatin1CharsZ(cx, utf8, &length, js::StringBufferArena)
+            .get());
     if (!latin1) {
       return nullptr;
     }
 
     return NewString<allowGC>(cx, std::move(latin1), length);
   }
 
   MOZ_ASSERT(encoding == JS::SmallestEncoding::UTF16);
 
   UniqueTwoByteChars utf16(
-      UTF8CharsToNewTwoByteCharsZ(cx, utf8, &length, js::MallocArena).get());
+      UTF8CharsToNewTwoByteCharsZ(cx, utf8, &length, js::StringBufferArena)
+          .get());
   if (!utf16) {
     return nullptr;
   }
 
   return NewString<allowGC>(cx, std::move(utf16), length);
 }
 
 template JSFlatString* NewStringCopyUTF8N<CanGC>(JSContext* cx,
--- a/js/src/vm/StringType.h
+++ b/js/src/vm/StringType.h
@@ -393,16 +393,23 @@ class JSString : public js::gc::Cell {
 
  protected:
   template <typename CharT>
   MOZ_ALWAYS_INLINE void setNonInlineChars(const CharT* chars);
 
   MOZ_ALWAYS_INLINE
   uint32_t flags() const { return uint32_t(d.flags_); }
 
+  template <typename CharT>
+  static MOZ_ALWAYS_INLINE void checkStringCharsArena(const CharT* chars) {
+#ifdef MOZ_DEBUG
+    js::AssertJSStringBufferInCorrectArena(chars);
+#endif
+  }
+
  public:
   MOZ_ALWAYS_INLINE
   size_t length() const {
 #if JS_BITS_PER_WORD == 32
     return d.length_;
 #else
     return uint32_t(d.flags_ >> 32);
 #endif
@@ -1902,22 +1909,26 @@ MOZ_ALWAYS_INLINE bool JSInlineString::l
 template <>
 MOZ_ALWAYS_INLINE bool JSInlineString::lengthFits<char16_t>(size_t length) {
   // If it fits in a fat inline string, it fits in any inline string.
   return JSFatInlineString::lengthFits<char16_t>(length);
 }
 
 template <>
 MOZ_ALWAYS_INLINE void JSString::setNonInlineChars(const char16_t* chars) {
+  // Check that the new buffer is located in the StringBufferArena
+  checkStringCharsArena(chars);
   d.s.u2.nonInlineCharsTwoByte = chars;
 }
 
 template <>
 MOZ_ALWAYS_INLINE void JSString::setNonInlineChars(
     const JS::Latin1Char* chars) {
+  // Check that the new buffer is located in the StringBufferArena
+  checkStringCharsArena(chars);
   d.s.u2.nonInlineCharsLatin1 = chars;
 }
 
 MOZ_ALWAYS_INLINE const JS::Latin1Char* JSLinearString::rawLatin1Chars() const {
   MOZ_ASSERT(JSString::isLinear());
   MOZ_ASSERT(hasLatin1Chars());
   return isInline() ? d.inlineStorageLatin1 : d.s.u2.nonInlineCharsLatin1;
 }
--- a/js/src/vm/Time.cpp
+++ b/js/src/vm/Time.cpp
@@ -46,22 +46,19 @@ using mozilla::DebugOnly;
 // Forward declare the function
 static int64_t PRMJ_NowImpl();
 
 int64_t PRMJ_Now() {
   if (mozilla::TimeStamp::GetFuzzyfoxEnabled()) {
     return mozilla::TimeStamp::NowFuzzyTime();
   }
 
-  int64_t now = PRMJ_NowImpl();
   // We check the FuzzyFox clock in case it was recently disabled, to prevent
   // time from going backwards.
-  return mozilla::TimeStamp::NowFuzzyTime() > now
-             ? mozilla::TimeStamp::NowFuzzyTime()
-             : now;
+  return js::Max(PRMJ_NowImpl(), mozilla::TimeStamp::NowFuzzyTime());
 }
 
 #if defined(XP_UNIX)
 static int64_t PRMJ_NowImpl() {
   struct timeval tv;
 
 #  ifdef _SVID_GETTOD /* Defined only on Solaris, see Solaris <sys/types.h> */
   gettimeofday(&tv);
@@ -247,16 +244,17 @@ static int64_t PRMJ_NowImpl() {
     // cannot maintain the invariant that Date.now() never
     // decreases; the old implementation has this behavior as
     // well.
     needsCalibration = true;
   }
 }
 #endif
 
+#if !ENABLE_INTL_API || MOZ_SYSTEM_ICU
 #ifdef XP_WIN
 static void PRMJ_InvalidParameterHandler(const wchar_t* expression,
                                          const wchar_t* function,
                                          const wchar_t* file, unsigned int line,
                                          uintptr_t pReserved) {
   /* empty */
 }
 #endif
@@ -394,8 +392,9 @@ size_t PRMJ_FormatTime(char* buf, size_t
       memcpy(p, real_year, real_year_len);
       result = new_result;
       *(buf + result) = '\0';
     }
   }
 #endif
   return result;
 }
+#endif /* !ENABLE_INTL_API || MOZ_SYSTEM_ICU */
--- a/js/src/vm/Time.h
+++ b/js/src/vm/Time.h
@@ -8,31 +8,33 @@
 #define vm_Time_h
 
 #include "mozilla/RecordReplay.h"
 #include "mozilla/TimeStamp.h"
 
 #include <stddef.h>
 #include <stdint.h>
 
+#if !ENABLE_INTL_API || MOZ_SYSTEM_ICU
 /*
  * Broken down form of 64 bit time value.
  */
 struct PRMJTime {
   int32_t tm_usec; /* microseconds of second (0-999999) */
   int8_t tm_sec;   /* seconds of minute (0-59) */
   int8_t tm_min;   /* minutes of hour (0-59) */
   int8_t tm_hour;  /* hour of day (0-23) */
   int8_t tm_mday;  /* day of month (1-31) */
   int8_t tm_mon;   /* month of year (0-11) */
   int8_t tm_wday;  /* 0=sunday, 1=monday, ... */
   int32_t tm_year; /* absolute year, AD */
   int16_t tm_yday; /* day of year (0 to 365) */
   int8_t tm_isdst; /* non-zero if DST in effect */
 };
+#endif
 
 /* Some handy constants */
 #define PRMJ_USEC_PER_SEC 1000000L
 #define PRMJ_USEC_PER_MSEC 1000L
 
 /* Return the current local time in micro-seconds */
 extern int64_t PRMJ_Now();
 
@@ -45,20 +47,22 @@ inline void PRMJ_NowInit() {}
 
 /* Release the resources associated with PRMJ_Now; don't call PRMJ_Now again */
 #ifdef XP_WIN
 extern void PRMJ_NowShutdown();
 #else
 inline void PRMJ_NowShutdown() {}
 #endif
 
+#if !ENABLE_INTL_API || MOZ_SYSTEM_ICU
 /* Format a time value into a buffer. Same semantics as strftime() */
 extern size_t PRMJ_FormatTime(char* buf, size_t buflen, const char* fmt,
                               const PRMJTime* tm, int timeZoneYear,
                               int offsetInSeconds);
+#endif
 
 /**
  * Requesting the number of cycles from the CPU.
  *
  * `rdtsc`, or Read TimeStamp Cycle, is an instruction provided by
  * x86-compatible CPUs that lets processes request the number of
  * cycles spent by the CPU executing instructions since the CPU was
  * started. It may be used for performance monitoring, but you should
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -2757,17 +2757,18 @@ static bool Reject(JSContext* cx, const 
   // Ideally we'd report a JSMSG_WASM_COMPILE_ERROR here, but there's no easy
   // way to create an ErrorObject for an arbitrary error code with multiple
   // replacements.
   UniqueChars str(JS_smprintf("wasm validation error: %s", error.get()));
   if (!str) {
     return false;
   }
 
-  RootedString message(cx, NewLatin1StringZ(cx, std::move(str)));
+  size_t len = strlen(str.get());
+  RootedString message(cx, NewStringCopyN<CanGC>(cx, str.get(), len));
   if (!message) {
     return false;
   }
 
   RootedObject errorObj(
       cx, ErrorObject::create(cx, JSEXN_WASMCOMPILEERROR, stack, filename, 0,
                               line, 0, nullptr, message));
   if (!errorObj) {
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -2247,43 +2247,37 @@ nsIFrame* nsCSSFrameConstructor::Constru
   const nsStyleDisplay* display = computedStyle->StyleDisplay();
 
   // Ensure that our XBL bindings are installed.
   //
   // FIXME(emilio): Can we remove support for bindings on the root?
   if (display->mBinding) {
     // Get the XBL loader.
     nsresult rv;
-    bool resolveStyle;
 
     nsXBLService* xblService = nsXBLService::GetInstance();
     if (!xblService) {
       return nullptr;
     }
 
     RefPtr<nsXBLBinding> binding;
     rv = xblService->LoadBindings(aDocElement, display->mBinding->GetURI(),
                                   display->mBinding->ExtraData()->Principal(),
-                                  getter_AddRefs(binding), &resolveStyle);
+                                  getter_AddRefs(binding));
     if (NS_FAILED(rv) && rv != NS_ERROR_XBL_BLOCKED) {
       // Binding will load asynchronously.
       return nullptr;
     }
 
     if (binding) {
       // For backwards compat, keep firing the root's constructor
       // after all of its kids' constructors.  So tell the binding
       // manager about it right now.
       mDocument->BindingManager()->AddToAttachedQueue(binding);
     }
-
-    if (resolveStyle) {
-      computedStyle = mPresShell->StyleSet()->ResolveServoStyle(*aDocElement);
-      display = computedStyle->StyleDisplay();
-    }
   }
 
   // --------- IF SCROLLABLE WRAP IN SCROLLFRAME --------
 
   NS_ASSERTION(!display->IsScrollableOverflow() ||
                    state.mPresContext->IsPaginated() ||
                    propagatedScrollFrom == aDocElement,
                "Scrollbars should have been propagated to the viewport");
@@ -5299,133 +5293,117 @@ nsCSSFrameConstructor::FindElementTagDat
     case kNameSpaceID_XUL:
       return FindXULTagData(aElement, aStyle);
     default:
       return nullptr;
   }
 }
 
 nsCSSFrameConstructor::XBLBindingLoadInfo::XBLBindingLoadInfo(
-    already_AddRefed<ComputedStyle>&& aStyle,
     UniquePtr<PendingBinding> aPendingBinding)
-    : mStyle(std::move(aStyle)), mPendingBinding(std::move(aPendingBinding)) {
-  MOZ_ASSERT(mStyle);
-}
-
-nsCSSFrameConstructor::XBLBindingLoadInfo::XBLBindingLoadInfo(
-    nsIContent& aContent, ComputedStyle& aStyle)
-    : mStyle(&aStyle), mPendingBinding(nullptr) {}
+    : mPendingBinding(std::move(aPendingBinding)), mSuccess(true) {}
 
 nsCSSFrameConstructor::XBLBindingLoadInfo::XBLBindingLoadInfo() = default;
 
 nsCSSFrameConstructor::XBLBindingLoadInfo
 nsCSSFrameConstructor::LoadXBLBindingIfNeeded(nsIContent& aContent,
-                                              ComputedStyle& aStyle,
+                                              const ComputedStyle& aStyle,
                                               uint32_t aFlags) {
   if (!(aFlags & ITEM_ALLOW_XBL_BASE)) {
-    return {aContent, aStyle};
+    return XBLBindingLoadInfo(nullptr);
   }
   css::URLValue* binding = aStyle.StyleDisplay()->mBinding;
   if (!binding) {
-    return {aContent, aStyle};
+    return XBLBindingLoadInfo(nullptr);
   }
 
   nsXBLService* xblService = nsXBLService::GetInstance();
   if (!xblService) {
     return {};
   }
 
   auto newPendingBinding = MakeUnique<PendingBinding>();
 
-  bool resolveStyle;
-  nsresult rv = xblService->LoadBindings(
-      aContent.AsElement(), binding->GetURI(),
-      binding->ExtraData()->Principal(),
-      getter_AddRefs(newPendingBinding->mBinding), &resolveStyle);
+  nsresult rv =
+      xblService->LoadBindings(aContent.AsElement(), binding->GetURI(),
+                               binding->ExtraData()->Principal(),
+                               getter_AddRefs(newPendingBinding->mBinding));
   if (NS_FAILED(rv)) {
     if (rv == NS_ERROR_XBL_BLOCKED) {
-      return {aContent, aStyle};
+      return XBLBindingLoadInfo(nullptr);
     }
     return {};
   }
 
-  RefPtr<ComputedStyle> style =
-      resolveStyle
-          ? mPresShell->StyleSet()->ResolveServoStyle(*aContent.AsElement())
-          : do_AddRef(&aStyle);
-
-  return {style.forget(), std::move(newPendingBinding)};
+  return XBLBindingLoadInfo(std::move(newPendingBinding));
 }
 
 void nsCSSFrameConstructor::AddFrameConstructionItemsInternal(
     nsFrameConstructorState& aState, nsIContent* aContent,
     nsContainerFrame* aParentFrame, bool aSuppressWhiteSpaceOptimizations,
     ComputedStyle* aComputedStyle, uint32_t aFlags,
     FrameConstructionItemList& aItems) {
   MOZ_ASSERT(aContent->IsText() || aContent->IsElement(),
              "Shouldn't get anything else here!");
   MOZ_ASSERT(aContent->IsInComposedDoc());
   MOZ_ASSERT(!aContent->GetPrimaryFrame() || aState.mCreatingExtraFrames ||
              aContent->NodeInfo()->NameAtom() == nsGkAtoms::area);
 
   PendingBinding* pendingBinding = nullptr;
-  RefPtr<ComputedStyle> style;
   {
     XBLBindingLoadInfo xblInfo =
         LoadXBLBindingIfNeeded(*aContent, *aComputedStyle, aFlags);
-    if (!xblInfo.mStyle) {
+    if (!xblInfo.mSuccess) {
       return;
     }
 
     if (xblInfo.mPendingBinding && xblInfo.mPendingBinding->mBinding) {
       pendingBinding = xblInfo.mPendingBinding.get();
       aState.AddPendingBinding(std::move(xblInfo.mPendingBinding));
     }
-
-    style = xblInfo.mStyle.forget();
-    aComputedStyle = style.get();
   }
 
   const bool isGeneratedContent = !!(aFlags & ITEM_IS_GENERATED_CONTENT);
-  MOZ_ASSERT(!isGeneratedContent || style->IsPseudoElement(),
+  MOZ_ASSERT(!isGeneratedContent || aComputedStyle->IsPseudoElement(),
              "Generated content should be a pseudo-element");
 
   FrameConstructionItem* item = nullptr;
   auto cleanupGeneratedContent = mozilla::MakeScopeExit([&]() {
     if (isGeneratedContent && !item) {
       MOZ_ASSERT(!IsDisplayContents(aContent),
                  "This would need to change if we support display: contents "
                  "in generated content");
       aContent->UnbindFromTree();
     }
   });
 
-  const nsStyleDisplay& display = *style->StyleDisplay();
+  const nsStyleDisplay& display = *aComputedStyle->StyleDisplay();
 
   // Pre-check for display "none" - if we find that, don't create
   // any frame at all
   if (display.mDisplay == StyleDisplay::None) {
     return;
   }
 
   if (display.mDisplay == StyleDisplay::Contents) {
     CreateGeneratedContentItem(aState, aParentFrame, *aContent->AsElement(),
-                               *style, PseudoStyleType::before, aItems);
+                               *aComputedStyle, PseudoStyleType::before,
+                               aItems);
 
     FlattenedChildIterator iter(aContent);
     InsertionPoint insertion(aParentFrame, aContent);
     for (nsIContent* child = iter.GetNextChild(); child;
          child = iter.GetNextChild()) {
       AddFrameConstructionItems(aState, child, aSuppressWhiteSpaceOptimizations,
                                 insertion, aItems);
     }
     aItems.SetParentHasNoXBLChildren(!iter.XBLInvolved());
 
     CreateGeneratedContentItem(aState, aParentFrame, *aContent->AsElement(),
-                               *style, PseudoStyleType::after, aItems);
+                               *aComputedStyle, PseudoStyleType::after, aItems);
     return;
   }
 
   nsIContent* parent = aParentFrame ? aParentFrame->GetContent() : nullptr;
   if (ShouldSuppressFrameInSelect(parent, *aContent)) {
     return;
   }
 
@@ -5435,17 +5413,17 @@ void nsCSSFrameConstructor::AddFrameCons
   // ::before and ::after); we always want to create "internal" anonymous
   // content.
   auto* details = HTMLDetailsElement::FromNodeOrNull(parent);
   if (ShouldSuppressFrameInNonOpenDetails(details, aComputedStyle, *aContent)) {
     return;
   }
 
   const FrameConstructionData* data =
-      FindDataForContent(*aContent, *style, aParentFrame, aFlags);
+      FindDataForContent(*aContent, *aComputedStyle, aParentFrame, aFlags);
   if (!data || data->mBits & FCDATA_SUPPRESS_FRAME) {
     return;
   }
 
   bool isPopup = false;
 
 #ifdef MOZ_XUL
   if ((data->mBits & FCDATA_IS_POPUP) && (!aParentFrame ||  // Parent is inline
@@ -5477,25 +5455,26 @@ void nsCSSFrameConstructor::AddFrameCons
     AddPageBreakItem(aContent, aItems);
   }
 
   if (details && details->Open()) {
     auto* summary = HTMLSummaryElement::FromNode(aContent);
     if (summary && summary->IsMainSummary()) {
       // If details is open, the main summary needs to be rendered as if it is
       // the first child, so add the item to the front of the item list.
-      item =
-          aItems.PrependItem(this, data, aContent, pendingBinding,
-                             style.forget(), aSuppressWhiteSpaceOptimizations);
+      item = aItems.PrependItem(this, data, aContent, pendingBinding,
+                                do_AddRef(aComputedStyle),
+                                aSuppressWhiteSpaceOptimizations);
     }
   }
 
   if (!item) {
     item = aItems.AppendItem(this, data, aContent, pendingBinding,
-                             style.forget(), aSuppressWhiteSpaceOptimizations);
+                             do_AddRef(aComputedStyle),
+                             aSuppressWhiteSpaceOptimizations);
   }
   item->mIsText = !aContent->IsElement();
   item->mIsGeneratedContent = isGeneratedContent;
   item->mIsAnonymousContentCreatorContent =
       aFlags & ITEM_IS_ANONYMOUSCONTENTCREATOR_CONTENT;
   if (isGeneratedContent) {
     // We need to keep this alive until the frame takes ownership.
     // This corresponds to the Release in ConstructFramesFromItem.
--- a/layout/base/nsCSSFrameConstructor.h
+++ b/layout/base/nsCSSFrameConstructor.h
@@ -765,32 +765,26 @@ class nsCSSFrameConstructor final : publ
   static const PseudoParentData sPseudoParentData[eParentTypeCount];
 
   // The information that concerns the frame constructor after loading an XBL
   // binding.
   //
   // This is expected to just be used temporarily to aggregate the different
   // objects that LoadXBLBindingIfNeeded returns.
   struct MOZ_STACK_CLASS XBLBindingLoadInfo {
-    RefPtr<ComputedStyle> mStyle;
     mozilla::UniquePtr<PendingBinding> mPendingBinding;
-
-    // For the 'no binding loaded' case.
-    XBLBindingLoadInfo(nsIContent&, ComputedStyle&);
-
-    // For the case we actually load an XBL binding.
-    XBLBindingLoadInfo(already_AddRefed<ComputedStyle>&& aStyle,
-                       mozilla::UniquePtr<PendingBinding> aPendingBinding);
+    bool mSuccess = false;
 
     // For the error case.
     XBLBindingLoadInfo();
+    explicit XBLBindingLoadInfo(mozilla::UniquePtr<PendingBinding>);
   };
 
   // Returns null mStyle member to signal an error.
-  XBLBindingLoadInfo LoadXBLBindingIfNeeded(nsIContent&, ComputedStyle&,
+  XBLBindingLoadInfo LoadXBLBindingIfNeeded(nsIContent&, const ComputedStyle&,
                                             uint32_t aFlags);
 
   const FrameConstructionData* FindDataForContent(nsIContent&, ComputedStyle&,
                                                   nsIFrame* aParentFrame,
                                                   uint32_t aFlags);
 
   // aParentFrame might be null.  If it is, that means it was an inline frame.
   static const FrameConstructionData* FindTextData(const Text&,
--- a/layout/generic/nsLineLayout.cpp
+++ b/layout/generic/nsLineLayout.cpp
@@ -885,17 +885,16 @@ void nsLineLayout::ReflowFrame(nsIFrame*
   }
 
   pfd->mJustificationInfo = mJustificationInfo;
   mJustificationInfo = JustificationInfo();
 
   // See if the frame is a placeholderFrame and if it is process
   // the float. At the same time, check if the frame has any non-collapsed-away
   // content.
-  bool placedFloat = false;
   bool isEmpty;
   if (frameType == LayoutFrameType::None) {
     isEmpty = pfd->mFrame->IsEmpty();
   } else {
     if (LayoutFrameType::Placeholder == frameType) {
       isEmpty = true;
       pfd->mIsPlaceholder = true;
       pfd->mSkipWhenTrimmingWhitespace = true;
@@ -913,17 +912,17 @@ void nsLineLayout::ReflowFrame(nsIFrame*
             // of the letter frame to kill a continuation after we've stored it
             // in the line layout data structures. See bug 1490281 to fix the
             // underlying issue. When that's fixed this check should be removed.
             !outOfFlowFrame->IsLetterFrame() &&
             !GetOutermostLineLayout()->mBlockRI->mFlags.mCanHaveTextOverflow) {
           // We'll do this at the next break opportunity.
           RecordNoWrapFloat(outOfFlowFrame);
         } else {
-          placedFloat = TryToPlaceFloat(outOfFlowFrame);
+          TryToPlaceFloat(outOfFlowFrame);
         }
       }
     } else if (isText) {
       // Note non-empty text-frames for inline frame compatibility hackery
       pfd->mIsTextFrame = true;
       nsTextFrame* textFrame = static_cast<nsTextFrame*>(pfd->mFrame);
       isEmpty = !textFrame->HasNoncollapsedCharacters();
       if (!isEmpty) {
@@ -1059,21 +1058,22 @@ void nsLineLayout::ReflowFrame(nsIFrame*
       if (span) {
         // The frame we just finished reflowing is an inline
         // container.  It needs its child frames aligned in the block direction,
         // so do most of it now.
         VerticalAlignFrames(span);
       }
 
       if (!continuingTextRun && !psd->mNoWrap) {
-        if (!LineIsEmpty() || placedFloat) {
+        if (!LineIsEmpty()) {
           // record soft break opportunity after this content that can't be
           // part of a text run. This is not a text frame so we know
           // that offset INT32_MAX means "after the content".
-          if (NotifyOptionalBreakPosition(aFrame, INT32_MAX,
+          if (!aFrame->IsPlaceholderFrame() &&
+              NotifyOptionalBreakPosition(aFrame, INT32_MAX,
                                           optionalBreakAfterFits,
                                           gfxBreakPriority::eNormalBreak)) {
             // If this returns true then we are being told to actually break
             // here.
             aReflowStatus.SetInlineLineBreakAfter();
           }
         }
       }
--- a/layout/inspector/InspectorUtils.cpp
+++ b/layout/inspector/InspectorUtils.cpp
@@ -179,32 +179,16 @@ void InspectorUtils::GetCSSStyleRules(
 
   AutoTArray<ServoStyleRuleMap*, 1> maps;
   {
     ServoStyleSet* styleSet = presShell->StyleSet();
     ServoStyleRuleMap* map = styleSet->StyleRuleMap();
     maps.AppendElement(map);
   }
 
-  // Collect style rule maps for bindings.
-  for (nsIContent* bindingContent = &aElement; bindingContent;
-       bindingContent = bindingContent->GetBindingParent()) {
-    for (nsXBLBinding* binding = bindingContent->GetXBLBinding(); binding;
-         binding = binding->GetBaseBinding()) {
-      if (auto* map = binding->PrototypeBinding()->GetServoStyleRuleMap()) {
-        maps.AppendElement(map);
-      }
-    }
-    // Note that we intentionally don't cut off here, unlike when we
-    // do styling, because even if style rules from parent binding
-    // do not apply to the element directly in those cases, their
-    // rules may still show up in the list we get above due to the
-    // inheritance in cascading.
-  }
-
   // Now shadow DOM stuff...
   if (auto* shadow = aElement.GetShadowRoot()) {
     maps.AppendElement(&shadow->ServoStyleRuleMap());
   }
 
   for (auto* shadow = aElement.GetContainingShadow(); shadow;
        shadow = shadow->Host()->GetContainingShadow()) {
     maps.AppendElement(&shadow->ServoStyleRuleMap());
--- a/layout/inspector/ServoStyleRuleMap.cpp
+++ b/layout/inspector/ServoStyleRuleMap.cpp
@@ -11,39 +11,29 @@
 #include "mozilla/dom/CSSRuleBinding.h"
 #include "mozilla/dom/CSSStyleRule.h"
 #include "mozilla/dom/Document.h"
 #include "mozilla/dom/ShadowRoot.h"
 #include "mozilla/IntegerRange.h"
 #include "mozilla/ServoStyleSet.h"
 #include "mozilla/StyleSheetInlines.h"
 #include "nsStyleSheetService.h"
-#include "nsXBLPrototypeResources.h"
 
 using namespace mozilla::dom;
 
 namespace mozilla {
 
 void ServoStyleRuleMap::EnsureTable(ServoStyleSet& aStyleSet) {
   if (!IsEmpty()) {
     return;
   }
   aStyleSet.EnumerateStyleSheets(
       [&](StyleSheet& aSheet) { FillTableFromStyleSheet(aSheet); });
 }
 
-void ServoStyleRuleMap::EnsureTable(nsXBLPrototypeResources& aXBLResources) {
-  if (!IsEmpty() || !aXBLResources.GetServoStyles()) {
-    return;
-  }
-  for (auto index : IntegerRange(aXBLResources.SheetCount())) {
-    FillTableFromStyleSheet(*aXBLResources.StyleSheetAt(index));
-  }
-}
-
 void ServoStyleRuleMap::EnsureTable(ShadowRoot& aShadowRoot) {
   if (!IsEmpty()) {
     return;
   }
   for (auto index : IntegerRange(aShadowRoot.SheetCount())) {
     FillTableFromStyleSheet(*aShadowRoot.SheetAt(index));
   }
 }
--- a/layout/inspector/ServoStyleRuleMap.h
+++ b/layout/inspector/ServoStyleRuleMap.h
@@ -8,33 +8,31 @@
 #define mozilla_ServoStyleRuleMap_h
 
 #include "mozilla/dom/CSSStyleRule.h"
 #include "mozilla/StyleSheet.h"
 
 #include "nsDataHashtable.h"
 
 struct RawServoStyleRule;
-class nsXBLPrototypeResources;
 
 namespace mozilla {
 class ServoCSSRuleList;
 class ServoStyleSet;
 namespace css {
 class Rule;
 }  // namespace css
 namespace dom {
 class ShadowRoot;
 }
 class ServoStyleRuleMap {
  public:
   ServoStyleRuleMap() = default;
 
   void EnsureTable(ServoStyleSet&);
-  void EnsureTable(nsXBLPrototypeResources&);
   void EnsureTable(dom::ShadowRoot&);
 
   dom::CSSStyleRule* Lookup(const RawServoStyleRule* aRawRule) const {
     return mTable.Get(aRawRule);
   }
 
   void SheetAdded(StyleSheet&);
   void SheetRemoved(StyleSheet&);
--- a/layout/painting/FrameLayerBuilder.cpp
+++ b/layout/painting/FrameLayerBuilder.cpp
@@ -6466,18 +6466,23 @@ gfxSize FrameLayerBuilder::GetPaintedLay
 #ifdef MOZ_DUMP_PAINTING
 static void DebugPaintItem(DrawTarget& aDrawTarget, nsPresContext* aPresContext,
                            nsDisplayItem* aItem,
                            nsDisplayListBuilder* aBuilder) {
   bool snap;
   Rect bounds = NSRectToRect(aItem->GetBounds(aBuilder, &snap),
                              aPresContext->AppUnitsPerDevPixel());
 
-  RefPtr<DrawTarget> tempDT = aDrawTarget.CreateSimilarDrawTarget(
-      IntSize::Truncate(bounds.width, bounds.height), SurfaceFormat::B8G8R8A8);
+  const IntSize size = IntSize::Truncate(bounds.width, bounds.height);
+  if (size.IsEmpty()) {
+    return;
+  }
+
+  RefPtr<DrawTarget> tempDT =
+      aDrawTarget.CreateSimilarDrawTarget(size, SurfaceFormat::B8G8R8A8);
   RefPtr<gfxContext> context = gfxContext::CreateOrNull(tempDT);
   if (!context) {
     // Leave this as crash, it's in the debugging code, we want to know
     gfxDevCrash(LogReason::InvalidContext)
         << "DebugPaintItem context problem " << gfx::hexa(tempDT);
     return;
   }
   context->SetMatrix(Matrix::Translation(-bounds.x, -bounds.y));
deleted file mode 100644
--- a/layout/reftests/bugs/1375513-ref.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Reference for bug 1375513</title>
-    <style type="text/css">
-    input[type=range] {
-      width: 200px;
-      height: 20px;
-      margin: 0;
-      padding: 0;
-      background-color: blue;
-    }
-
-    input[type=range]::-moz-range-progress {
-      height: 10px;
-      background-color: lime;
-    }
-
-    input[type=range]::-moz-range-track,
-    input[type=range]::-moz-range-thumb {
-      visibility: hidden;
-    }
-    </style>
-  </head>
-  <body>
-    <input type="range">
-  </body>
-</html>
deleted file mode 100644
--- a/layout/reftests/bugs/1375513.css
+++ /dev/null
@@ -1,17 +0,0 @@
-input[type=range] {
-  width: 200px;
-  height: 20px;
-  margin: 0;
-  padding: 0;
-  background-color: blue;
-}
-
-input[type=range]::-moz-range-progress {
-  height: 10px;
-  background-color: lime;
-}
-
-input[type=range]::-moz-range-track,
-input[type=range]::-moz-range-thumb {
-  visibility: hidden;
-}
deleted file mode 100644
--- a/layout/reftests/bugs/1375513.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Test for bug 1375513</title>
-    <style type="text/css">
-    #input {
-      -moz-binding: url(1375513.xml#createInputRange);
-    }
-    </style>
-  </head>
-  <body>
-    <div id="input"></div>
-  </body>
-</html>
deleted file mode 100644
--- a/layout/reftests/bugs/1375513.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0"?>
-<bindings xmlns="http://www.mozilla.org/xbl"
-          xmlns:html="http://www.w3.org/1999/xhtml">
-  <binding id="createInputRange">
-    <resources>
-      <stylesheet src="1375513.css"/>
-    </resources>
-    <content><html:input type="range"/></content>
-  </binding>
-</bindings>
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -2049,17 +2049,16 @@ skip-if(isDebugBuild&&winWidget) == 1330
 == 1375315-6.html 1375315-6-ref.html
 == 1375315-7.html 1375315-7-ref.html
 == 1375315-8.html 1375315-8-ref.html
 == 1375315-9.html 1375315-9-ref.html
 == 1375315-10.html 1375315-10-ref.html
 == 1375315-11.html 1375315-11-ref.html
 == 1375315-12.html 1375315-12-ref.html
 == 1374062.html 1374062-ref.html
-== 1375513.html 1375513-ref.html
 == 1375674.html 1375674-ref.html
 == 1372041.html 1372041-ref.html
 == 1376092.html 1376092-ref.html
 fuzzy-if(Android,0-3,0-3) needs-focus == 1377447-1.html 1377447-1-ref.html
 needs-focus != 1377447-1.html 1377447-2.html
 == 1379041.html 1379041-ref.html
 == 1379696.html 1379696-ref.html
 == 1380224-1.html 1380224-1-ref.html
--- a/layout/reftests/xul/reftest.list
+++ b/layout/reftests/xul/reftest.list
@@ -57,18 +57,16 @@ fuzzy-if(skiaContent,0-1,0-60) fuzzy-if(
 == object-fit-scale-down-svg-002.xul object-fit-scale-down-svg-002-ref.html
 == object-fit-scale-down-svg-003.xul object-fit-scale-down-svg-003-ref.html
 == object-fit-scale-down-svg-004.xul object-fit-scale-down-svg-004-ref.html
 == object-fit-scale-down-svg-005.xul object-fit-scale-down-svg-005-ref.html
 == object-fit-scale-down-svg-006.xul object-fit-scale-down-svg-006-ref.html
 == object-position-png-001.xul object-position-png-001-ref.html
 == object-position-png-002.xul object-position-png-002-ref.html
 
-== root-binding-style.xul green-ref.xul
-
 == stack-sizing-1.xul stack-sizing-1-ref.xul
 == stack-sizing-2.xul stack-sizing-2-ref.xul
 
 # Tests for rendering SVG images in a XUL <treecell>:
 # XXXdholbert: These are marked as "random" right now, since they might not
 # render the images they trying to test in time for the reftest snapshot, per
 # bug 1218954.
 skip == treecell-image-svg-1a.xul treecell-image-svg-1-ref.xul # bug 1218954
deleted file mode 100644
--- a/layout/reftests/xul/root-binding-style-xbl.css
+++ /dev/null
@@ -1,5 +0,0 @@
-@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
-
-test {
-  background-color: green;
-}
deleted file mode 100644
--- a/layout/reftests/xul/root-binding-style.xul
+++ /dev/null
@@ -1,3 +0,0 @@
-<?xml version="1.0"?>
-<test xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" style="-moz-binding: url(xbl_bindings.xml#root_binding_style)">
-</test>
deleted file mode 100644
--- a/layout/reftests/xul/xbl_bindings.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0"?>
-<bindings xmlns="http://www.mozilla.org/xbl">
-  <binding id="root_binding_style">
-    <resources>
-      <stylesheet src="root-binding-style-xbl.css"/>
-    </resources>
-  </binding>
-</bindings>
--- a/layout/style/GeckoBindings.cpp
+++ b/layout/style/GeckoBindings.cpp
@@ -1952,25 +1952,16 @@ FontSizePrefs Gecko_GetBaseSize(nsAtom* 
   return sizes;
 }
 
 const Element* Gecko_GetBindingParent(const Element* aElement) {
   nsIContent* parent = aElement->GetBindingParent();
   return parent ? parent->AsElement() : nullptr;
 }
 
-const RawServoAuthorStyles* Gecko_XBLBinding_GetRawServoStyles(
-    const nsXBLBinding* aXBLBinding) {
-  return aXBLBinding->GetServoStyles();
-}
-
-bool Gecko_XBLBinding_InheritsStyle(const nsXBLBinding* aXBLBinding) {
-  return aXBLBinding->InheritsStyle();
-}
-
 static StaticRefPtr<UACacheReporter> gUACacheReporter;
 
 namespace mozilla {
 
 void InitializeServo() {
   URLExtraData::InitDummy();
   Servo_Initialize(URLExtraData::Dummy());
 
--- a/layout/style/GeckoBindings.h
+++ b/layout/style/GeckoBindings.h
@@ -690,20 +690,16 @@ mozilla::StyleGenericFontFamily Gecko_ns
     mozilla::StyleGenericFontFamily generic_family, nsAtom* language);
 
 mozilla::FontSizePrefs Gecko_GetBaseSize(nsAtom* lang);
 
 // XBL related functions.
 const mozilla::dom::Element* Gecko_GetBindingParent(
     const mozilla::dom::Element*);
 
-const RawServoAuthorStyles* Gecko_XBLBinding_GetRawServoStyles(
-    const nsXBLBinding*);
-bool Gecko_XBLBinding_InheritsStyle(const nsXBLBinding* aXBLBinding);
-
 struct GeckoFontMetrics {
   nscoord mChSize;  // -1.0 indicates not found
   nscoord mXSize;
 };
 
 GeckoFontMetrics Gecko_GetFontMetrics(const nsPresContext*, bool is_vertical,
                                       const nsStyleFont* font,
                                       nscoord font_size,
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -30,17 +30,16 @@
 #include "nsCSSFrameConstructor.h"
 #include "nsCSSPseudoElements.h"
 #include "nsDeviceContext.h"
 #include "nsHTMLStyleSheet.h"
 #include "nsIAnonymousContentCreator.h"
 #include "mozilla/dom/DocumentInlines.h"
 #include "nsMediaFeatures.h"
 #include "nsPrintfCString.h"
-#include "nsXBLPrototypeBinding.h"
 #include "gfxUserFontSet.h"
 #include "nsBindingManager.h"
 #include "nsWindowSizes.h"
 #include "GeckoProfiler.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
@@ -136,17 +135,17 @@ void ServoStyleSet::ShellDetachedFromDoc
   // Also GC the ruletree if it got big now that the DOM no longer has
   // references to styles around anymore.
   MaybeGCRuleTree();
 }
 
 void ServoStyleSet::RecordShadowStyleChange(ShadowRoot& aShadowRoot) {
   // TODO(emilio): We could keep track of the actual shadow roots that need
   // their styles recomputed.
-  SetStylistXBLStyleSheetsDirty();
+  SetStylistShadowDOMStyleSheetsDirty();
 
   // FIXME(emilio): This should be done using stylesheet invalidation instead.
   if (nsPresContext* pc = GetPresContext()) {
     pc->RestyleManager()->PostRestyleEvent(
         aShadowRoot.Host(), RestyleHint::RestyleSubtree(), nsChangeHint(0));
   }
 }
 
@@ -161,35 +160,26 @@ void ServoStyleSet::InvalidateStyleForDo
   }
 
   Element* root = mDocument->GetRootElement();
   if (!root) {
     return;
   }
 
   // TODO(emilio): It may be nicer to just invalidate stuff in a given subtree
-  // for XBL sheets / Shadow DOM. Consider just enumerating bound content
-  // instead and run invalidation individually, passing mRawSet for the UA /
-  // User sheets.
+  // for Shadow DOM. Consider just enumerating shadow roots instead and run
+  // invalidation individually, passing mRawSet for the UA / User sheets.
   AutoTArray<const RawServoAuthorStyles*, 20> nonDocumentStyles;
 
   EnumerateShadowRoots(*mDocument, [&](ShadowRoot& aShadowRoot) {
     if (auto* authorStyles = aShadowRoot.GetServoStyles()) {
       nonDocumentStyles.AppendElement(authorStyles);
     }
   });
 
-  mDocument->BindingManager()->EnumerateBoundContentProtoBindings(
-      [&](nsXBLPrototypeBinding* aProto) {
-        if (auto* authorStyles = aProto->GetServoStyles()) {
-          nonDocumentStyles.AppendElement(authorStyles);
-        }
-        return true;
-      });
-
   Servo_InvalidateStyleForDocStateChanges(
       root, mRawSet.get(), &nonDocumentStyles, aStatesChanged.ServoValue());
 }
 
 static const MediaFeatureChangeReason kMediaFeaturesAffectingDefaultStyle =
     // Zoom changes change the meaning of em units.
     MediaFeatureChangeReason::ZoomChange |
     // A resolution change changes the app-units-per-dev-pixels ratio, which
@@ -203,41 +193,32 @@ RestyleHint ServoStyleSet::MediumFeature
   AutoTArray<RawServoAuthorStyles*, 20> nonDocumentStyles;
 
   EnumerateShadowRoots(*mDocument, [&](ShadowRoot& aShadowRoot) {
     if (auto* authorStyles = aShadowRoot.GetServoStyles()) {
       nonDocumentStyles.AppendElement(authorStyles);
     }
   });
 
-  // FIXME(emilio): This is broken for XBL. See bug 1406875.
-  mDocument->BindingManager()->EnumerateBoundContentProtoBindings(
-      [&](nsXBLPrototypeBinding* aProto) {
-        if (auto* authorStyles = aProto->GetServoStyles()) {
-          nonDocumentStyles.AppendElement(authorStyles);
-        }
-        return true;
-      });
-
   bool mayAffectDefaultStyle =
       bool(aReason & kMediaFeaturesAffectingDefaultStyle);
 
   const MediumFeaturesChangedResult result =
       Servo_StyleSet_MediumFeaturesChanged(mRawSet.get(), &nonDocumentStyles,
                                            mayAffectDefaultStyle);
 
   const bool rulesChanged =
       result.mAffectsDocumentRules || result.mAffectsNonDocumentRules;
 
   if (result.mAffectsDocumentRules) {
     SetStylistStyleSheetsDirty();
   }
 
   if (result.mAffectsNonDocumentRules) {
-    SetStylistXBLStyleSheetsDirty();
+    SetStylistShadowDOMStyleSheetsDirty();
   }
 
   if (rulesChanged) {
     // TODO(emilio): This could be more granular.
     return RestyleHint::RestyleSubtree();
   }
 
   const bool viewportChanged =
@@ -657,24 +638,21 @@ size_t ServoStyleSet::SheetCount(Origin 
 
 StyleSheet* ServoStyleSet::SheetAt(Origin aOrigin, size_t aIndex) const {
   return const_cast<StyleSheet*>(
       Servo_StyleSet_GetSheetAt(mRawSet.get(), aOrigin, aIndex));
 }
 
 void ServoStyleSet::AppendAllNonDocumentAuthorSheets(
     nsTArray<StyleSheet*>& aArray) const {
-  if (mDocument) {
-    mDocument->BindingManager()->AppendAllSheets(aArray);
-    EnumerateShadowRoots(*mDocument, [&](ShadowRoot& aShadowRoot) {
-      for (auto index : IntegerRange(aShadowRoot.SheetCount())) {
-        aArray.AppendElement(aShadowRoot.SheetAt(index));
-      }
-    });
-  }
+  EnumerateShadowRoots(*mDocument, [&](ShadowRoot& aShadowRoot) {
+    for (auto index : IntegerRange(aShadowRoot.SheetCount())) {
+      aArray.AppendElement(aShadowRoot.SheetAt(index));
+    }
+  });
 }
 
 void ServoStyleSet::AddDocStyleSheet(StyleSheet* aSheet) {
   MOZ_ASSERT(aSheet->IsApplicable());
   MOZ_ASSERT(aSheet->RawContents(),
              "Raw sheet should be in place by this point.");
 
   RefPtr<StyleSheet> strong(aSheet);
@@ -888,18 +866,18 @@ void ServoStyleSet::SetStylistStyleSheet
   //
   // We don't allow to call getComputedStyle in elements without a pres shell
   // yet, so it is fine if there's no pres context here.
   if (nsPresContext* presContext = GetPresContext()) {
     presContext->RestyleManager()->IncrementUndisplayedRestyleGeneration();
   }
 }
 
-void ServoStyleSet::SetStylistXBLStyleSheetsDirty() {
-  mStylistState |= StylistState::XBLStyleSheetsDirty;
+void ServoStyleSet::SetStylistShadowDOMStyleSheetsDirty() {
+  mStylistState |= StylistState::ShadowDOMStyleSheetsDirty;
   if (nsPresContext* presContext = GetPresContext()) {
     presContext->RestyleManager()->IncrementUndisplayedRestyleGeneration();
   }
 }
 
 static OriginFlags ToOriginFlags(StyleOrigin aOrigin) {
   switch (aOrigin) {
     case StyleOrigin::UserAgent:
@@ -999,55 +977,40 @@ already_AddRefed<RawServoAnimationValue>
     Element* aElement, RawServoDeclarationBlock* aDeclarations,
     const mozilla::ComputedStyle* aStyle) {
   return Servo_AnimationValue_Compute(aElement, aDeclarations, aStyle,
                                       mRawSet.get())
       .Consume();
 }
 
 bool ServoStyleSet::EnsureUniqueInnerOnCSSSheets() {
-  using SheetOwner =
-      Variant<ServoStyleSet*, nsXBLPrototypeBinding*, ShadowRoot*>;
+  using SheetOwner = Variant<ServoStyleSet*, ShadowRoot*>;
 
   AutoTArray<Pair<StyleSheet*, SheetOwner>, 32> queue;
   EnumerateStyleSheets([&](StyleSheet& aSheet) {
     queue.AppendElement(MakePair(&aSheet, SheetOwner{this}));
   });
 
   EnumerateShadowRoots(*mDocument, [&](ShadowRoot& aShadowRoot) {
     for (auto index : IntegerRange(aShadowRoot.SheetCount())) {
       queue.AppendElement(
           MakePair(aShadowRoot.SheetAt(index), SheetOwner{&aShadowRoot}));
     }
   });
 
-  mDocument->BindingManager()->EnumerateBoundContentProtoBindings(
-      [&](nsXBLPrototypeBinding* aProto) {
-        AutoTArray<StyleSheet*, 3> sheets;
-        aProto->AppendStyleSheetsTo(sheets);
-        for (auto* sheet : sheets) {
-          queue.AppendElement(MakePair(sheet, SheetOwner{aProto}));
-        }
-        return true;
-      });
-
   bool anyNonDocStyleChanged = false;
   while (!queue.IsEmpty()) {
     uint32_t idx = queue.Length() - 1;
     auto* sheet = queue[idx].first();
     SheetOwner owner = queue[idx].second();
     queue.RemoveElementAt(idx);
 
-    if (!sheet->HasUniqueInner()) {
-      RawServoAuthorStyles* authorStyles = nullptr;
-      if (owner.is<ShadowRoot*>()) {
-        authorStyles = owner.as<ShadowRoot*>()->GetServoStyles();
-      } else if (owner.is<nsXBLPrototypeBinding*>()) {
-        authorStyles = owner.as<nsXBLPrototypeBinding*>()->GetServoStyles();
-      }
+    if (!sheet->HasUniqueInner() && owner.is<ShadowRoot*>()) {
+      RawServoAuthorStyles* authorStyles =
+          owner.as<ShadowRoot*>()->GetServoStyles();
 
       if (authorStyles) {
         Servo_AuthorStyles_ForceDirty(authorStyles);
         mNeedsRestyleAfterEnsureUniqueInner = true;
         anyNonDocStyleChanged = true;
       }
     }
 
@@ -1065,17 +1028,17 @@ bool ServoStyleSet::EnsureUniqueInnerOnC
     AutoTArray<StyleSheet*, 3> children;
     sheet->AppendAllChildSheets(children);
     for (auto* sheet : children) {
       queue.AppendElement(MakePair(sheet, owner));
     }
   }
 
   if (anyNonDocStyleChanged) {
-    SetStylistXBLStyleSheetsDirty();
+    SetStylistShadowDOMStyleSheetsDirty();
   }
 
   if (mNeedsRestyleAfterEnsureUniqueInner) {
     // TODO(emilio): We could make this faster if needed tracking the specific
     // origins and all that, but the only caller of this doesn't seem to really
     // care about perf.
     MarkOriginsDirty(OriginFlags::All);
   }
@@ -1095,17 +1058,17 @@ void ServoStyleSet::CompatibilityModeCha
   bool anyShadow = false;
   EnumerateShadowRoots(*mDocument, [&](ShadowRoot& aShadowRoot) {
     if (auto* authorStyles = aShadowRoot.GetServoStyles()) {
       anyShadow = true;
       Servo_AuthorStyles_ForceDirty(authorStyles);
     }
   });
   if (anyShadow) {
-    SetStylistXBLStyleSheetsDirty();
+    SetStylistShadowDOMStyleSheetsDirty();
   }
 }
 
 void ServoStyleSet::ClearNonInheritingComputedStyles() {
   for (RefPtr<ComputedStyle>& ptr : mNonInheritingComputedStyles) {
     ptr = nullptr;
   }
 }
@@ -1185,41 +1148,30 @@ already_AddRefed<ComputedStyle> ServoSty
                                                aDeclarations)
       .Consume();
 }
 
 void ServoStyleSet::UpdateStylist() {
   MOZ_ASSERT(StylistNeedsUpdate());
 
   if (mStylistState & StylistState::StyleSheetsDirty) {
-    // There's no need to compute invalidations and such for an XBL styleset,
-    // since they are loaded and unloaded synchronously, and they don't have to
-    // deal with dynamic content changes.
     Element* root = mDocument->GetRootElement();
     const ServoElementSnapshotTable* snapshots = nullptr;
     if (nsPresContext* pc = GetPresContext()) {
       snapshots = &pc->RestyleManager()->Snapshots();
     }
     Servo_StyleSet_FlushStyleSheets(mRawSet.get(), root, snapshots);
   }
 
-  if (MOZ_UNLIKELY(mStylistState & StylistState::XBLStyleSheetsDirty)) {
+  if (MOZ_UNLIKELY(mStylistState & StylistState::ShadowDOMStyleSheetsDirty)) {
     EnumerateShadowRoots(*mDocument, [&](ShadowRoot& aShadowRoot) {
       if (auto* authorStyles = aShadowRoot.GetServoStyles()) {
         Servo_AuthorStyles_Flush(authorStyles, mRawSet.get());
       }
     });
-
-    mDocument->BindingManager()->EnumerateBoundContentProtoBindings(
-        [&](nsXBLPrototypeBinding* aProto) {
-          if (auto* authorStyles = aProto->GetServoStyles()) {
-            Servo_AuthorStyles_Flush(authorStyles, mRawSet.get());
-          }
-          return true;
-        });
   }
 
   mStylistState = StylistState::NotDirty;
 }
 
 void ServoStyleSet::MaybeGCRuleTree() {
   MOZ_ASSERT(NS_IsMainThread());
   Servo_MaybeGCRuleTree(mRawSet.get());
--- a/layout/style/ServoStyleSet.h
+++ b/layout/style/ServoStyleSet.h
@@ -52,19 +52,19 @@ namespace mozilla {
 // update.
 enum class StylistState : uint8_t {
   // The stylist is not dirty, we should do nothing.
   NotDirty = 0,
 
   // The style sheets have changed, so we need to update the style data.
   StyleSheetsDirty = 1 << 0,
 
-  // Some of the style sheets of the bound elements in binding manager have
-  // changed, so we need to tell the binding manager to update style data.
-  XBLStyleSheetsDirty = 1 << 1,
+  // Some of the style sheets of the shadow trees in the document have
+  // changed.
+  ShadowDOMStyleSheetsDirty = 1 << 1,
 };
 
 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(StylistState)
 
 // Bitfield type to represent Servo stylesheet origins.
 enum class OriginFlags : uint8_t {
   UserAgent = 0x01,
   User = 0x02,
@@ -475,17 +475,17 @@ class ServoStyleSet {
    */
   void MarkOriginsDirty(OriginFlags aChangedOrigins);
 
   /**
    * Note that the stylist needs a style flush due to style sheet changes.
    */
   void SetStylistStyleSheetsDirty();
 
-  void SetStylistXBLStyleSheetsDirty();
+  void SetStylistShadowDOMStyleSheetsDirty();
 
   bool StylistNeedsUpdate() const {
     return mStylistState != StylistState::NotDirty;
   }
 
   /**
    * Update the stylist as needed to ensure style data is up-to-date.
    *
deleted file mode 100644
--- a/layout/style/test/media_queries_dynamic_xbl_binding.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0"?>
-<bindings xmlns="http://www.mozilla.org/xbl">
-  <binding id="binding">
-    <resources>
-      <stylesheet src="media_queries_dynamic_xbl_style.css" />
-    </resources>
-    <content>
-      <html:div xmlns:html="http://www.w3.org/1999/xhtml">
-        <children/>
-      </html:div>
-    </content>
-  </binding>
-</bindings>
deleted file mode 100644
--- a/layout/style/test/media_queries_dynamic_xbl_iframe.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<!DOCTYPE HTML>
-<style type="text/css">
-body { -moz-binding: url(media_queries_dynamic_xbl_binding.xml#binding); }
-</style>
-<p id="para">Hello</p>
deleted file mode 100644
--- a/layout/style/test/media_queries_dynamic_xbl_style.css
+++ /dev/null
@@ -1,6 +0,0 @@
-@media (orientation: portrait) {
-  div { color: purple; }
-}
-@media (orientation: landscape) {
-  div { color: blue; }
-}
--- a/layout/style/test/mochitest.ini
+++ b/layout/style/test/mochitest.ini
@@ -16,19 +16,16 @@ support-files =
   chrome/bug418986-2.js
   chrome/match.png
   chrome/mismatch.png
   descriptor_database.js
   !/dom/events/test/event_leak_utils.js
   empty.html
   file_computed_style_bfcache_display_none.html
   file_computed_style_bfcache_display_none2.html
-  media_queries_dynamic_xbl_binding.xml
-  media_queries_dynamic_xbl_iframe.html
-  media_queries_dynamic_xbl_style.css
   media_queries_iframe.html
   neverending_font_load.sjs
   neverending_stylesheet_load.sjs
   post-redirect-1.css
   post-redirect-2.css
   post-redirect-3.css
   property_database.js
   redirect.sjs
@@ -260,17 +257,16 @@ skip-if = toolkit == 'android'
 support-files = slow_broken_sheet.sjs slow_ok_sheet.sjs
 [test_logical_properties.html]
 [test_mask_image_CORS.html]
 [test_media_queries.html]
 # times out on verify, see bug 1461033.
 # debug-only failure; timed out #Android 4.3 aws only; bug 1030419
 skip-if = (verify || android_version == '18')
 [test_media_queries_dynamic.html]
-[test_media_queries_dynamic_xbl.html]
 [test_media_query_list.html]
 [test_media_query_serialization.html]
 [test_mq_any_hover_and_any_pointer.html]
 [test_mq_hover_and_pointer.html]
 [test_mq_prefers_reduced_motion_dynamic.html]
 run-if = (os == 'mac' || toolkit == 'android')
 [test_moz_device_pixel_ratio.html]
 [test_namespace_rule.html]
deleted file mode 100644
--- a/layout/style/test/test_media_queries_dynamic_xbl.html
+++ /dev/null
@@ -1,40 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=156716
--->
-<head>
-  <title>Test for Bug 156716</title>
-  <script src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body onload="run()">
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=156716">Mozilla Bug 156716</a>
-<iframe id="display" src="media_queries_dynamic_xbl_iframe.html"></iframe>
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-/** Test for Bug 156716 **/
-
-function run() {
-  var iframe = document.getElementById("display");
-
-  var subdoc = iframe.contentDocument;
-  var subwin = iframe.contentWindow;
-  var p = subdoc.getElementById("para");
-
-  iframe.setAttribute("style", "height: 300px; width: 100px");
-  is(subwin.getComputedStyle(p).color, "rgb(128, 0, 128)",
-     "should be purple when portait");
-  iframe.setAttribute("style", "height: 100px; width: 300px");
-  is(subwin.getComputedStyle(p).color, "rgb(0, 0, 255)",
-     "should be blue when landscape");
-  SimpleTest.finish();
-}
-SimpleTest.waitForExplicitFinish();
-
-</script>
-</pre>
-</body>
-</html>
-
--- a/layout/xul/nsMenuPopupFrame.cpp
+++ b/layout/xul/nsMenuPopupFrame.cpp
@@ -573,30 +573,29 @@ void nsMenuPopupFrame::LayoutPopup(nsBox
   if (openChanged) {
     mIsOpenChanged = false;
 
     // Make sure the current selection in a menulist is visible.
     if (IsMenuList() && mCurrentMenu) {
       EnsureMenuItemIsVisible(mCurrentMenu);
     }
 
-#ifndef MOZ_WIDGET_GTK
     // If the animate attribute is set to open, check for a transition and wait
     // for it to finish before firing the popupshown event.
-    if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
+    if (StaticPrefs::xul_panel_animations_enabled() &&
+        mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
                                            nsGkAtoms::animate, nsGkAtoms::open,
                                            eCaseMatters) &&
         AnimationUtils::HasCurrentTransitions(mContent->AsElement(),
                                               PseudoStyleType::NotPseudo)) {
       mPopupShownDispatcher = new nsXULPopupShownEvent(mContent, pc);
       mContent->AddSystemEventListener(NS_LITERAL_STRING("transitionend"),
                                        mPopupShownDispatcher, false, false);
       return;
     }
-#endif
 
     // If there are no transitions, fire the popupshown event right away.
     nsCOMPtr<nsIRunnable> event = new nsXULPopupShownEvent(GetContent(), pc);
     mContent->OwnerDoc()->Dispatch(TaskCategory::Other, event.forget());
   }
 
   if (needCallback && !mReflowCallbackData.mPosted) {
     pc->PresShell()->PostReflowCallback(this);
--- a/layout/xul/nsXULPopupManager.cpp
+++ b/layout/xul/nsXULPopupManager.cpp
@@ -1433,21 +1433,19 @@ void nsXULPopupManager::FirePopupHidingE
       // ePopupVisible. This probably isn't worth the hassle of handling.
</