Merge autoland to mozilla-central. a=merge
authorDorel Luca <dluca@mozilla.com>
Sat, 21 Sep 2019 00:50:16 +0300
changeset 494266 c221a75fbf2957b6254fedb96436b5efbf0fd147
parent 494193 89b9b3aa3aafb413060c61e381723c0788c07848 (current diff)
parent 494265 ea74ac35e7178aeaf73a29584ad22ea352004b9d (diff)
child 494347 de5a09d263ff557012c5872a9edfb276ca1e353f
child 494396 56e11fddf939f61bcfac9e179e13ff62a8ad5d82
push id36599
push userdluca@mozilla.com
push dateFri, 20 Sep 2019 21:52:25 +0000
treeherdermozilla-central@c221a75fbf29 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone71.0a1
first release with
nightly linux32
c221a75fbf29 / 71.0a1 / 20190920215225 / files
nightly linux64
c221a75fbf29 / 71.0a1 / 20190920215225 / files
nightly mac
c221a75fbf29 / 71.0a1 / 20190920215225 / files
nightly win32
c221a75fbf29 / 71.0a1 / 20190920215225 / files
nightly win64
c221a75fbf29 / 71.0a1 / 20190920215225 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to mozilla-central. a=merge
browser/app/winlauncher/DllBlocklistWin.cpp
browser/app/winlauncher/DllBlocklistWin.h
devtools/client/netmonitor/src/components/RequestBlockingPanel.js
mozglue/build/Authenticode.cpp
mozglue/build/Authenticode.h
mozglue/build/MozglueUtils.h
mozglue/build/UntrustedDllsHandler.cpp
mozglue/build/UntrustedDllsHandler.h
mozglue/build/WindowsDllBlocklist.cpp
mozglue/build/WindowsDllBlocklist.h
mozglue/build/WindowsDllBlocklistCommon.h
mozglue/build/WindowsDllBlocklistDefs.in
mozglue/build/WindowsDllServices.h
mozglue/build/gen_dll_blocklist_defs.py
toolkit/components/telemetry/docs/data/untrusted-modules-ping.rst
toolkit/components/telemetry/tests/unit/test_UntrustedModulesPing.js
toolkit/xre/ModuleEvaluator_windows.cpp
toolkit/xre/ModuleEvaluator_windows.h
toolkit/xre/ModuleVersionInfo_windows.cpp
toolkit/xre/ModuleVersionInfo_windows.h
--- a/accessible/tests/browser/states/browser.ini
+++ b/accessible/tests/browser/states/browser.ini
@@ -2,10 +2,14 @@
 support-files =
   head.js
   !/accessible/tests/browser/shared-head.js
   !/accessible/tests/mochitest/*.js
 
 [browser_test_link.js]
 skip-if = verify
 [browser_test_visibility.js]
+[browser_deck_has_out_of_process_iframe.js]
+support-files =
+  target.html
+  test_deck_has_out_of_process_iframe.xul
 [browser_offscreen_element_in_out_of_process_iframe.js]
 skip-if = (webrender && os == 'win') # bug 1580706
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/states/browser_deck_has_out_of_process_iframe.js
@@ -0,0 +1,120 @@
+/* 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";
+
+const DIRPATH = getRootDirectory(gTestPath).replace(
+  "chrome://mochitests/content/",
+  ""
+);
+const parentPATH = DIRPATH + "test_deck_has_out_of_process_iframe.xul";
+const iframePATH = DIRPATH + "target.html";
+
+// XXX: Using external files here since using data URL breaks something, e.g. it
+// makes querying the second iframe in a hidden deck failure for some reasons.
+const parentURL = `http://example.com/${parentPATH}`;
+const iframeURL = `http://example.org/${iframePATH}`;
+
+add_task(async function() {
+  const win = await BrowserTestUtils.openNewBrowserWindow({
+    fission: true,
+  });
+
+  try {
+    const browser = win.gBrowser.selectedTab.linkedBrowser;
+
+    BrowserTestUtils.loadURI(browser, parentURL);
+    await BrowserTestUtils.browserLoaded(browser, false, parentURL);
+
+    async function setupIFrame(id, url) {
+      const iframe = content.document.getElementById(id);
+
+      iframe.contentWindow.location = url;
+      await new Promise(resolve => {
+        iframe.addEventListener("load", resolve, { once: true });
+      });
+
+      return iframe.browsingContext;
+    }
+
+    async function spawnSelectDeck(index) {
+      async function selectDeck(i) {
+        const deck = content.document.getElementById("deck");
+
+        deck.setAttribute("selectedIndex", i);
+        await new Promise(resolve => {
+          content.window.addEventListener("MozAfterPaint", resolve, {
+            once: true,
+          });
+        });
+        return deck.selectedIndex;
+      }
+      await SpecialPowers.spawn(browser, [index], selectDeck);
+
+      await waitForIFrameUpdates();
+    }
+
+    const firstIFrame = await SpecialPowers.spawn(
+      browser,
+      ["first", iframeURL],
+      setupIFrame
+    );
+    const secondIFrame = await SpecialPowers.spawn(
+      browser,
+      ["second", iframeURL],
+      setupIFrame
+    );
+
+    await waitForIFrameUpdates();
+
+    await spawnTestStates(
+      firstIFrame,
+      "target",
+      0,
+      nsIAccessibleStates.STATE_OFFSCREEN
+    );
+    // Disable the check for the target element in the unselected pane of the
+    // deck, this should be fixed by bug 1578932.
+    // Note: As of now we can't use todo in the script transfered into the
+    // out-of-process.
+    //await spawnTestStates(
+    //  secondIFrame,
+    //  "target",
+    //  nsIAccessibleStates.STATE_OFFSCREEN,
+    //  nsIAccessibleStates.STATE_INVISIBLE
+    //);
+
+    // Select the second panel.
+    await spawnSelectDeck(1);
+    await spawnTestStates(
+      firstIFrame,
+      "target",
+      nsIAccessibleStates.STATE_OFFSCREEN,
+      nsIAccessibleStates.STATE_INVISIBLE
+    );
+    await spawnTestStates(
+      secondIFrame,
+      "target",
+      0,
+      nsIAccessibleStates.STATE_OFFSCREEN
+    );
+
+    // Select the first panel again.
+    await spawnSelectDeck(0);
+    await spawnTestStates(
+      firstIFrame,
+      "target",
+      0,
+      nsIAccessibleStates.STATE_OFFSCREEN
+    );
+    await spawnTestStates(
+      secondIFrame,
+      "target",
+      nsIAccessibleStates.STATE_OFFSCREEN,
+      nsIAccessibleStates.STATE_INVISIBLE
+    );
+  } finally {
+    await BrowserTestUtils.closeWindow(win);
+  }
+});
--- a/accessible/tests/browser/states/browser_offscreen_element_in_out_of_process_iframe.js
+++ b/accessible/tests/browser/states/browser_offscreen_element_in_out_of_process_iframe.js
@@ -56,35 +56,43 @@ add_task(async function() {
 
     // Setup an out-of-process iframe which is initially scrolled out.
     const iframe = await SpecialPowers.spawn(browser, [iframeURL], setup);
 
     await waitForIFrameUpdates();
     await spawnTestStates(
       iframe,
       "target",
-      nsIAccessibleStates.STATE_OFFSCREEN
+      nsIAccessibleStates.STATE_OFFSCREEN,
+      nsIAccessibleStates.STATE_INVISIBLE
     );
 
     // Scroll the iframe into view and the target element is also visible but
     // the visible area height is 11px.
     await scrollTo(0, 711);
     await spawnTestStates(
       iframe,
       "target",
-      nsIAccessibleStates.STATE_OFFSCREEN
+      nsIAccessibleStates.STATE_OFFSCREEN,
+      nsIAccessibleStates.STATE_INVISIBLE
     );
 
     // Scroll to a position where the visible height is 13px.
     await scrollTo(0, 713);
-    await spawnTestStates(iframe, "target", 0);
+    await spawnTestStates(
+      iframe,
+      "target",
+      0,
+      nsIAccessibleStates.STATE_OFFSCREEN
+    );
 
     // Scroll the iframe out again.
     await scrollTo(0, 0);
     await spawnTestStates(
       iframe,
       "target",
-      nsIAccessibleStates.STATE_OFFSCREEN
+      nsIAccessibleStates.STATE_OFFSCREEN,
+      nsIAccessibleStates.STATE_INVISIBLE
     );
   } finally {
     await BrowserTestUtils.closeWindow(win);
   }
 });
--- a/accessible/tests/browser/states/head.js
+++ b/accessible/tests/browser/states/head.js
@@ -27,27 +27,28 @@ async function waitForIFrameUpdates() {
   // calls.
   await new Promise(resolve => requestAnimationFrame(resolve));
   await new Promise(resolve => requestAnimationFrame(resolve));
 }
 
 // A utility function to test the state of |elementId| element in out-of-process
 // |browsingContext|.
 async function spawnTestStates(browsingContext, elementId, expectedStates) {
-  function testStates(id, expected) {
+  function testStates(id, expected, unexpected) {
     const acc = SpecialPowers.Cc[
       "@mozilla.org/accessibilityService;1"
     ].getService(SpecialPowers.Ci.nsIAccessibilityService);
     const target = content.document.getElementById(id);
     let state = {};
     acc.getAccessibleFor(target).getState(state, {});
     if (expected === 0) {
       Assert.equal(state.value, expected);
     } else {
       Assert.ok(state.value & expected);
     }
+    Assert.ok(!(state.value & unexpected));
   }
   await SpecialPowers.spawn(
     browsingContext,
     [elementId, expectedStates],
     testStates
   );
 }
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/states/target.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<div id="target">target</div>
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/states/test_deck_has_out_of_process_iframe.xul
@@ -0,0 +1,10 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        xmlns:html="http://www.w3.org/1999/xhtml"
+        title="XUL elements visibility states testing">
+<deck id="deck" selectedIndex="0">
+  <iframe id="first" flex="1"/>
+  <iframe id="second" flex="1"/>
+</deck>
+</window>
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -2092,17 +2092,17 @@ pref("devtools.performance.recording.obj
 pref("devtools.cache.disabled", false);
 
 // The default service workers UI setting
 pref("devtools.serviceWorkers.testing.enabled", false);
 
 // Enable the Network Monitor
 pref("devtools.netmonitor.enabled", true);
 
-// Enable Network Search in Nightly and DevEdition/Beta builds.
+// Enable Network Search in Nightly builds.
 #if defined(NIGHTLY_BUILD) || defined(MOZ_DEV_EDITION)
   pref("devtools.netmonitor.features.search", true);
 #else
   pref("devtools.netmonitor.features.search", false);
 #endif
 pref("devtools.netmonitor.features.requestBlocking", false);
 
 // Enable the Application panel
--- a/browser/base/content/browser-siteProtections.js
+++ b/browser/base/content/browser-siteProtections.js
@@ -1725,24 +1725,16 @@ var gProtectionsHandler = {
     // height of TP switch section, or it will change when toggling the switch,
     // which is not desirable for us. So, we need to use a different attribute
     // here.
     this._protectionsPopupTPSwitchSection.toggleAttribute(
       "short",
       !currentlyEnabled
     );
 
-    // Show the blue dot indicator if the protection is disabled. We need this
-    // in addition to the 'enabled' attribute of the TP switch section due to
-    // the blue dot won't be shown in the case that TP switch to off from on.
-    this._protectionsPopupTPSwitch.toggleAttribute(
-      "showdotindicator",
-      !currentlyEnabled
-    );
-
     // Give the button an accessible label for screen readers.
     if (currentlyEnabled) {
       this._protectionsPopupTPSwitch.setAttribute(
         "aria-label",
         gNavigatorBundle.getFormattedString("protections.disableAriaLabel", [
           host,
         ])
       );
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -74,17 +74,16 @@
   min-height: 0 !important;
   height: 0 !important;
   -moz-appearance: none !important;
 }
 %endif
 
 %ifdef XP_MACOSX
 #toolbar-menubar {
-  -moz-binding: none;
   visibility: collapse;
 }
 %endif
 
 panelmultiview {
   -moz-box-align: start;
 }
 
@@ -233,17 +232,17 @@ panelview[mainview] > .panel-header {
 /* Allow dropping a tab on buttons with associated drop actions. */
 #navigator-toolbox[movingtab] > #nav-bar > #nav-bar-customization-target > #personal-bookmarks,
 #navigator-toolbox[movingtab] > #nav-bar > #nav-bar-customization-target > #home-button,
 #navigator-toolbox[movingtab] > #nav-bar > #nav-bar-customization-target > #downloads-button,
 #navigator-toolbox[movingtab] > #nav-bar > #nav-bar-customization-target > #bookmarks-menu-button {
   pointer-events: auto;
 }
 
-toolbar[overflowable] > .customization-target:not([urlbar-breakout-extend]) {
+toolbar[overflowable][customizing] > .customization-target {
   overflow: hidden;
 }
 
 toolbar:not([overflowing]) > .overflow-button,
 toolbar[customizing] > .overflow-button {
   display: none;
 }
 
@@ -696,73 +695,61 @@ toolbar:not(#TabsToolbar) > #personal-bo
   display: none;
 }
 
 #nav-bar:not([keyNav=true]) #identity-box,
 #nav-bar:not([keyNav=true]) #tracking-protection-icon-container {
   -moz-user-focus: normal;
 }
 
-/* We leave 49ch plus whatever space the download button will need when it
+/* We leave 34ch plus whatever space the download button will need when it
  * appears. Normally this should be 16px for the icon, plus 2 * 2px padding
  * plus the toolbarbutton-inner-padding. We're adding 4px to ensure things
  * like rounding on hidpi don't accidentally result in the button going
  * into overflow.
  */
 #urlbar-container {
-  min-width: calc(49ch + 24px + 2 * var(--toolbarbutton-inner-padding));
+  min-width: calc(34ch + 24px + 2 * var(--toolbarbutton-inner-padding));
 }
 
 #nav-bar[downloadsbuttonshown] #urlbar-container {
-  min-width: 49ch;
+  min-width: 34ch;
 }
 
 #identity-icon-labels {
-  max-width: 17em;
-}
-@media (max-width: 700px) {
-  #urlbar-container {
-    min-width: calc(44ch + 24px + 2 * var(--toolbarbutton-inner-padding));
-  }
-  #nav-bar[downloadsbuttonshown] #urlbar-container {
-    min-width: 44ch;
-  }
-
-  #identity-icon-labels {
-    max-width: 60px;
-  }
+  max-width: 10em;
 }
 @media (max-width: 600px) {
   #urlbar-container {
-    min-width: calc(39ch + 24px + 2 * var(--toolbarbutton-inner-padding));
+    min-width: calc(29ch + 24px + 2 * var(--toolbarbutton-inner-padding));
   }
   #nav-bar[downloadsbuttonshown] #urlbar-container {
-    min-width: 39ch;
+    min-width: 29ch;
   }
   #identity-icon-labels {
     max-width: 50px;
   }
 }
 @media (max-width: 500px) {
   #urlbar-container {
-    min-width: calc(34ch + 24px + 2 * var(--toolbarbutton-inner-padding));
+    min-width: calc(22ch + 24px + 2 * var(--toolbarbutton-inner-padding));
   }
   #nav-bar[downloadsbuttonshown] #urlbar-container {
-    min-width: 34ch;
+    min-width: 22ch;
   }
   #identity-icon-labels {
     max-width: 40px;
   }
 }
 @media (max-width: 400px) {
   #urlbar-container {
-    min-width: calc(27ch + 24px + 2 * var(--toolbarbutton-inner-padding));
+    min-width: calc(10ch + 24px + 2 * var(--toolbarbutton-inner-padding));
   }
   #nav-bar[downloadsbuttonshown] #urlbar-container {
-    min-width: 27ch;
+    min-width: 10ch;
   }
   #identity-icon-labels {
     max-width: 30px;
   }
 }
 
 #identity-icon-country-label {
   direction: ltr;
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2873,17 +2873,17 @@ function focusAndSelectUrlBar() {
   }
 
   gURLBar.select();
 }
 
 function openLocation(event) {
   if (window.location.href == AppConstants.BROWSER_CHROME_URL) {
     focusAndSelectUrlBar();
-    if (gURLBar.openViewOnFocus && !gURLBar.view.isOpen) {
+    if (gURLBar.openViewOnFocusForCurrentTab && !gURLBar.view.isOpen) {
       gURLBar.startQuery({ event });
     }
     return;
   }
 
   // If there's an open browser window, redirect the command there.
   let win = getTopWin();
   if (win) {
--- a/browser/base/content/test/performance/head.js
+++ b/browser/base/content/test/performance/head.js
@@ -271,17 +271,17 @@ function forceImmediateToolbarOverflowHa
     overflowableToolbar._lazyResizeHandler &&
     overflowableToolbar._lazyResizeHandler.isArmed
   ) {
     overflowableToolbar._lazyResizeHandler.disarm();
     // Ensure the root frame is dirty before resize so that, if we're
     // in the middle of a reflow test, we record the reflows deterministically.
     let dwu = win.windowUtils;
     dwu.ensureDirtyRootFrame();
-    overflowableToolbar._onLazyResize();
+    overflowableToolbar._checkOverflow();
   }
 }
 
 async function prepareSettledWindow() {
   let win = await BrowserTestUtils.openNewBrowserWindow();
 
   await ensureNoPreloadedBrowser(win);
   forceImmediateToolbarOverflowHandling(win);
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -4720,39 +4720,40 @@ function XULWidgetSingleWrapper(aWidgetI
   Object.freeze(this);
 }
 
 const LAZY_RESIZE_INTERVAL_MS = 200;
 const OVERFLOW_PANEL_HIDE_DELAY_MS = 500;
 
 function OverflowableToolbar(aToolbarNode) {
   this._toolbar = aToolbarNode;
+  this._target = CustomizableUI.getCustomizationTarget(this._toolbar);
+  if (this._target.parentNode != this._toolbar) {
+    throw new Error(
+      "Customization target must be a direct child of an overflowable toolbar."
+    );
+  }
   this._collapsed = new Map();
   this._enabled = true;
-  this._toolbar.addEventListener("overflow", this);
-  this._toolbar.addEventListener("underflow", this);
 
   this._toolbar.setAttribute("overflowable", "true");
   let doc = this._toolbar.ownerDocument;
-  this._target = CustomizableUI.getCustomizationTarget(this._toolbar);
   this._list = doc.getElementById(this._toolbar.getAttribute("overflowtarget"));
   this._list._customizationTarget = this._list;
 
   let window = this._toolbar.ownerGlobal;
   if (window.gBrowserInit.delayedStartupFinished) {
     this.init();
   } else {
     Services.obs.addObserver(this, "browser-delayed-startup-finished");
   }
 }
 
 OverflowableToolbar.prototype = {
   initialized: false,
-  _forceOnOverflow: false,
-  _addedListener: false,
 
   observe(aSubject, aTopic, aData) {
     if (
       aTopic == "browser-delayed-startup-finished" &&
       aSubject == this._toolbar.ownerGlobal
     ) {
       Services.obs.removeObserver(this, "browser-delayed-startup-finished");
       this.init();
@@ -4774,31 +4775,23 @@ OverflowableToolbar.prototype = {
     this._chevron.addEventListener("dragend", this);
 
     let panelId = this._toolbar.getAttribute("overflowpanel");
     this._panel = doc.getElementById(panelId);
     this._panel.addEventListener("popuphiding", this);
     CustomizableUIInternal.addPanelCloseListeners(this._panel);
 
     CustomizableUI.addListener(this);
-    this._addedListener = true;
-
-    // The 'overflow' event may have been fired before init was called.
-    if (this.overflowedDuringConstruction) {
-      log.debug("Overflowed when constructed, running overflow handler now.");
-      this.onOverflow(this.overflowedDuringConstruction);
-      this.overflowedDuringConstruction = null;
-    }
+
+    this._checkOverflow();
 
     this.initialized = true;
   },
 
   uninit() {
-    this._toolbar.removeEventListener("overflow", this._toolbar);
-    this._toolbar.removeEventListener("underflow", this._toolbar);
     this._toolbar.removeAttribute("overflowable");
 
     if (!this.initialized) {
       Services.obs.removeObserver(this, "browser-delayed-startup-finished");
       return;
     }
 
     this._disable();
@@ -4808,38 +4801,21 @@ OverflowableToolbar.prototype = {
     window.gNavToolbox.removeEventListener("customizationstarting", this);
     window.gNavToolbox.removeEventListener("aftercustomization", this);
     this._chevron.removeEventListener("mousedown", this);
     this._chevron.removeEventListener("keypress", this);
     this._chevron.removeEventListener("dragover", this);
     this._chevron.removeEventListener("dragend", this);
     this._panel.removeEventListener("popuphiding", this);
     CustomizableUI.removeListener(this);
-    this._addedListener = false;
     CustomizableUIInternal.removePanelCloseListeners(this._panel);
   },
 
   handleEvent(aEvent) {
     switch (aEvent.type) {
-      case "overflow":
-        // Ignore vertical overflow and events from from nodes inside the toolbar.
-        if (aEvent.detail > 0 && aEvent.target == this._target) {
-          if (this.initialized) {
-            this.onOverflow(aEvent);
-          } else {
-            this.overflowedDuringConstruction = aEvent;
-          }
-        }
-        break;
-      case "underflow":
-        // Ignore vertical underflow and events from from nodes inside the toolbar.
-        if (aEvent.detail > 0 && aEvent.target == this._target) {
-          this.overflowedDuringConstruction = null;
-        }
-        break;
       case "aftercustomization":
         this._enable();
         break;
       case "mousedown":
         if (aEvent.button != 0) {
           break;
         }
         if (aEvent.target == this._chevron) {
@@ -4910,16 +4886,23 @@ OverflowableToolbar.prototype = {
           // handlers do.
           Services.tm.dispatchToMainThread(resolve);
         },
         { once: true }
       );
     });
   },
 
+  /**
+   * Exposes whether _onOverflow is currently running.
+   */
+  isHandlingOverflow() {
+    return !!this._onOverflowHandle;
+  },
+
   _onClickChevron(aEvent) {
     if (this._chevron.open) {
       this._chevron.open = false;
       PanelMultiView.hidePopup(this._panel);
     } else if (this._panel.state != "hiding" && !this._chevron.disabled) {
       this.show(aEvent);
     }
   },
@@ -4937,59 +4920,79 @@ OverflowableToolbar.prototype = {
     let contextMenuId = this._panel.getAttribute("context");
     if (contextMenuId) {
       let contextMenu = doc.getElementById(contextMenuId);
       gELS.removeSystemEventListener(contextMenu, "command", this, true);
     }
   },
 
   /**
-   * Avoid re-entrancy in the overflow handling by keeping track of invocations:
+   * Returns an array with two elements, the first one a boolean representing
+   * whether we're overflowing, the second one a number representing the
+   * maximum width items may occupy so we don't overflow.
    */
-  _lastOverflowCounter: 0,
+  async _getOverflowInfo() {
+    let win = this._target.ownerGlobal;
+    let totalAvailWidth;
+    let targetWidth;
+    await win.promiseDocumentFlushed(() => {
+      let style = win.getComputedStyle(this._toolbar);
+      totalAvailWidth =
+        this._toolbar.clientWidth -
+        parseFloat(style.borderLeftWidth) -
+        parseFloat(style.borderRightWidth) -
+        parseFloat(style.paddingLeft) -
+        parseFloat(style.paddingRight);
+      for (let child of this._toolbar.children) {
+        if (child.nodeName == "panel") {
+          // Ugh. PanelUI.showSubView puts customizationui-widget-panel
+          // directly into the toolbar. (bug 1158583)
+          continue;
+        }
+        style = win.getComputedStyle(child);
+        if (style.display == "none") {
+          continue;
+        }
+        totalAvailWidth -=
+          parseFloat(style.marginLeft) + parseFloat(style.marginRight);
+        if (child != this._target) {
+          totalAvailWidth -= child.clientWidth;
+        }
+      }
+      targetWidth = this._target.clientWidth;
+    });
+    log.debug(
+      `Getting overflow info: target width: ${targetWidth}; available width: ${totalAvailWidth}`
+    );
+    return [targetWidth > totalAvailWidth, totalAvailWidth];
+  },
 
   /**
    * Handle overflow in the toolbar by moving items to the overflow menu.
-   * @param {Event} aEvent
-   *        The overflow event that triggered handling overflow. May be omitted
-   *        in some cases (e.g. when we run this method after overflow handling
-   *        is re-enabled from customize mode, to ensure correct handling of
-   *        initial overflow).
    */
-  async onOverflow(aEvent) {
+  async _onOverflow() {
     if (!this._enabled) {
       return;
     }
 
-    log.debug(`Got overflow event`);
-    let child = this._target.lastElementChild;
-
-    let thisOverflowResponse = ++this._lastOverflowCounter;
-
     let win = this._target.ownerGlobal;
-    let [scrollLeftMin, scrollLeftMax] = await win.promiseDocumentFlushed(
-      () => {
-        return [this._target.scrollLeftMin, this._target.scrollLeftMax];
-      }
-    );
-    log.debug(
-      `Overflow event layout: scrollLeft min: ${scrollLeftMin}; max: ${scrollLeftMax}`
-    );
-    if (win.closed || this._lastOverflowCounter != thisOverflowResponse) {
-      log.debug(
-        `Stop responding to overflow because we're ${thisOverflowResponse} ` +
-          `and ${this._lastOverflowCounter} is the last one.`
-      );
+    let onOverflowHandle = {};
+    this._onOverflowHandle = onOverflowHandle;
+
+    let [isOverflowing] = await this._getOverflowInfo();
+
+    // Stop if the window has closed or if we re-enter while waiting for
+    // layout.
+    if (win.closed || this._onOverflowHandle != onOverflowHandle) {
+      log.debug("Window closed or another overflow handler started.");
       return;
     }
 
-    while (child && scrollLeftMin != scrollLeftMax) {
-      log.debug(
-        `Try to overflow ${child.id} given ${scrollLeftMin} != ${scrollLeftMax}`
-      );
+    let child = this._target.lastElementChild;
+    while (child && isOverflowing) {
       let prevChild = child.previousElementSibling;
 
       if (child.getAttribute("overflows") != "false") {
         this._collapsed.set(child.id, this._target.clientWidth);
         child.setAttribute("overflowedItem", true);
         child.setAttribute("cui-anchorid", this._chevron.id);
         CustomizableUIInternal.ensureButtonContextMenu(
           child,
@@ -4998,88 +5001,98 @@ OverflowableToolbar.prototype = {
         );
         CustomizableUIInternal.notifyListeners(
           "onWidgetOverflow",
           child,
           this._target
         );
 
         this._list.insertBefore(child, this._list.firstElementChild);
-        if (!this._addedListener) {
-          CustomizableUI.addListener(this);
-        }
         if (!CustomizableUI.isSpecialWidget(child.id)) {
           this._toolbar.setAttribute("overflowing", "true");
         }
       }
       child = prevChild;
-      [scrollLeftMin, scrollLeftMax] = await win.promiseDocumentFlushed(() => {
-        return [this._target.scrollLeftMin, this._target.scrollLeftMax];
-      });
-      // If the window has closed or if we re-enter because we were waiting
-      // for layout, stop.
-      if (win.closed || this._lastOverflowCounter != thisOverflowResponse) {
-        log.debug(`Window closed or another overflow handler started.`);
+      [isOverflowing] = await this._getOverflowInfo();
+      // Stop if the window has closed or if we re-enter while waiting for
+      // layout.
+      if (win.closed || this._onOverflowHandle != onOverflowHandle) {
+        log.debug("Window closed or another overflow handler started.");
         return;
       }
     }
 
     win.UpdateUrlbarSearchSplitterState();
-    // Reset the counter because we finished handling overflow.
-    this._lastOverflowCounter = 0;
+
+    this._onOverflowHandle = null;
   },
 
   _onResize(aEvent) {
     // Ignore bubbled-up resize events.
-    if (aEvent.target != aEvent.target.ownerGlobal.top) {
+    if (aEvent.target != aEvent.currentTarget) {
       return;
     }
     log.debug("Got resize event");
     if (!this._lazyResizeHandler) {
       this._lazyResizeHandler = new DeferredTask(
-        this._onLazyResize.bind(this),
+        this._checkOverflow.bind(this),
         LAZY_RESIZE_INTERVAL_MS,
         0
       );
     }
     this._lazyResizeHandler.arm();
   },
 
   /**
    * Try to move toolbar items back to the toolbar from the overflow menu.
    * @param {boolean} shouldMoveAllItems
    *        Whether we should move everything (e.g. because we're being disabled)
-   * @param {number} targetWidth
-   *        Optional; the width of the toolbar in which we can put things.
+   * @param {number} totalAvailWidth
+   *        Optional; the width of the area in which we can put things.
    *        Some consumers pass this to avoid reflows.
    *        While there are items in the list, this width won't change, and so
    *        we can avoid flushing layout by providing it and/or caching it.
    *        Note that if `shouldMoveAllItems` is true, we never need the width
    *        anyway.
    */
-  _moveItemsBackToTheirOrigin(shouldMoveAllItems, targetWidth) {
+  async _moveItemsBackToTheirOrigin(shouldMoveAllItems, totalAvailWidth) {
     log.debug(
       `Attempting to move ${shouldMoveAllItems ? "all" : "some"} items back`
     );
     let placements = gPlacements.get(this._toolbar.id);
     let win = this._target.ownerGlobal;
+    let moveItemsBackToTheirOriginHandle = {};
+    this._moveItemsBackToTheirOriginHandle = moveItemsBackToTheirOriginHandle;
+
     while (this._list.firstElementChild) {
       let child = this._list.firstElementChild;
       let minSize = this._collapsed.get(child.id);
       log.debug(`Considering moving ${child.id} back, minSize: ${minSize}`);
 
       if (!shouldMoveAllItems && minSize) {
-        if (!targetWidth) {
-          let dwu = win.windowUtils;
-          targetWidth = Math.floor(
-            dwu.getBoundsWithoutFlushing(this._target).width
+        if (!totalAvailWidth) {
+          [, totalAvailWidth] = await this._getOverflowInfo();
+
+          // If the window has closed or if we re-enter because we were waiting
+          // for layout, stop.
+          if (
+            win.closed ||
+            this._moveItemsBackToTheirOriginHandle !=
+              moveItemsBackToTheirOriginHandle
+          ) {
+            log.debug(
+              "Window closed or _moveItemsBackToTheirOrigin called again."
+            );
+            return;
+          }
+        }
+        if (totalAvailWidth <= minSize) {
+          log.debug(
+            `Need ${minSize} but width is ${totalAvailWidth} so bailing`
           );
-        }
-        if (targetWidth <= minSize) {
-          log.debug(`Need ${minSize} but width is ${targetWidth} so bailing`);
           break;
         }
       }
 
       log.debug(`Moving ${child.id} back`);
       this._collapsed.delete(child.id);
       let beforeNodeIndex = placements.indexOf(child.id) + 1;
       // If this is a skipintoolbarset item, meaning it doesn't occur in the placements list,
@@ -5117,64 +5130,57 @@ OverflowableToolbar.prototype = {
     }
 
     win.UpdateUrlbarSearchSplitterState();
 
     let collapsedWidgetIds = Array.from(this._collapsed.keys());
     if (collapsedWidgetIds.every(w => CustomizableUI.isSpecialWidget(w))) {
       this._toolbar.removeAttribute("overflowing");
     }
-    if (this._addedListener && !this._collapsed.size) {
-      CustomizableUI.removeListener(this);
-      this._addedListener = false;
-    }
+
+    this._moveItemsBackToTheirOriginHandle = null;
   },
 
-  async _onLazyResize() {
+  async _checkOverflow() {
     if (!this._enabled) {
       return;
     }
-    log.debug("Processing resize event");
+    log.debug("Checking overflow");
 
     let win = this._target.ownerGlobal;
-    let [min, max, targetWidth] = await win.promiseDocumentFlushed(() => {
-      return [
-        this._target.scrollLeftMin,
-        this._target.scrollLeftMax,
-        this._target.clientWidth,
-      ];
-    });
+    let [isOverflowing, totalAvailWidth] = await this._getOverflowInfo();
     if (win.closed) {
       return;
     }
-    log.debug(
-      `Got layout information after resize, scroll min: ${min}; max: ${max}`
-    );
-    if (min != max) {
-      this.onOverflow();
+
+    if (isOverflowing) {
+      this._onOverflow();
     } else {
-      this._moveItemsBackToTheirOrigin(false, targetWidth);
+      this._moveItemsBackToTheirOrigin(false, totalAvailWidth);
     }
   },
 
   _disable() {
-    this._enabled = false;
     this._moveItemsBackToTheirOrigin(true);
     if (this._lazyResizeHandler) {
       this._lazyResizeHandler.disarm();
     }
+    this._enabled = false;
   },
 
   _enable() {
     this._enabled = true;
-    this.onOverflow();
+    this._checkOverflow();
   },
 
   onWidgetBeforeDOMChange(aNode, aNextNode, aContainer) {
-    if (aContainer != this._target && aContainer != this._list) {
+    if (
+      !this._enabled ||
+      (aContainer != this._target && aContainer != this._list)
+    ) {
       return;
     }
     // When we (re)move an item, update all the items that come after it in the list
     // with the minsize *of the item before the to-be-removed node*. This way, we
     // ensure that we try to move items back as soon as that's possible.
     if (aNode.parentNode == this._list) {
       let updatedMinSize;
       if (aNode.previousElementSibling) {
@@ -5187,21 +5193,23 @@ OverflowableToolbar.prototype = {
       while (nextItem) {
         this._collapsed.set(nextItem.id, updatedMinSize);
         nextItem = nextItem.nextElementSibling;
       }
     }
   },
 
   onWidgetAfterDOMChange(aNode, aNextNode, aContainer) {
-    if (aContainer != this._target && aContainer != this._list) {
+    if (
+      !this._enabled ||
+      (aContainer != this._target && aContainer != this._list)
+    ) {
       return;
     }
 
-    let nowInBar = aNode.parentNode == aContainer;
     let nowOverflowed = aNode.parentNode == this._list;
     let wasOverflowed = this._collapsed.has(aNode.id);
 
     // If this wasn't overflowed before...
     if (!wasOverflowed) {
       // ... but it is now, then we added to the overflow panel.
       if (nowOverflowed) {
         // We could be the first item in the overflow panel if we're being inserted
@@ -5217,24 +5225,17 @@ OverflowableToolbar.prototype = {
         aNode.setAttribute("cui-anchorid", this._chevron.id);
         aNode.setAttribute("overflowedItem", true);
         CustomizableUIInternal.ensureButtonContextMenu(aNode, aContainer, true);
         CustomizableUIInternal.notifyListeners(
           "onWidgetOverflow",
           aNode,
           this._target
         );
-      } else if (!nowInBar) {
-        // If it is not overflowed and not in the toolbar, and was not overflowed
-        // either, it moved out of the toolbar. That means there's now space in there!
-        // Let's try to move stuff back:
-        this._moveItemsBackToTheirOrigin(true);
       }
-      // If it's in the toolbar now, then we don't care. An overflow event may
-      // fire afterwards; that's ok!
     } else if (!nowOverflowed) {
       // If it used to be overflowed...
       // ... and isn't anymore, let's remove our bookkeeping:
       this._collapsed.delete(aNode.id);
       aNode.removeAttribute("cui-anchorid");
       aNode.removeAttribute("overflowedItem");
       CustomizableUIInternal.ensureButtonContextMenu(aNode, aContainer);
       CustomizableUIInternal.notifyListeners(
@@ -5242,30 +5243,26 @@ OverflowableToolbar.prototype = {
         aNode,
         this._target
       );
 
       let collapsedWidgetIds = Array.from(this._collapsed.keys());
       if (collapsedWidgetIds.every(w => CustomizableUI.isSpecialWidget(w))) {
         this._toolbar.removeAttribute("overflowing");
       }
-      if (this._addedListener && !this._collapsed.size) {
-        CustomizableUI.removeListener(this);
-        this._addedListener = false;
-      }
     } else if (aNode.previousElementSibling) {
       // but if it still is, it must have changed places. Bookkeep:
       let prevId = aNode.previousElementSibling.id;
       let minSize = this._collapsed.get(prevId);
       this._collapsed.set(aNode.id, minSize);
-    } else {
-      // If it's now the first item in the overflow list,
-      // maybe we can return it:
-      this._moveItemsBackToTheirOrigin(false);
-    }
+    }
+
+    // We might overflow now if an item was added, or we may be able to move
+    // stuff back into the toolbar if an item was removed.
+    this._checkOverflow();
   },
 
   findOverflowedInsertionPoints(aNode) {
     let newNodeCanOverflow = aNode.getAttribute("overflows") != "false";
     let areaId = this._toolbar.id;
     let placements = gPlacements.get(areaId);
     let nodeIndex = placements.indexOf(aNode.id);
     let nodeBeforeNewNodeIsOverflown = false;
--- a/browser/components/customizableui/test/CustomizableUITestUtils.jsm
+++ b/browser/components/customizableui/test/CustomizableUITestUtils.jsm
@@ -125,26 +125,21 @@ class CustomizableUITestUtils {
     // asynchronously.
     //
     // We should first wait for the layout flush to make sure either the search
     // bar fits into the nav bar, or overflow event gets dispatched and the
     // overflow event handler is called.
     await this.window.promiseDocumentFlushed(() => {});
 
     // Check if the OverflowableToolbar is handling the overflow event.
-    // _lastOverflowCounter property is incremented synchronously at the top
-    // of the overflow event handler, and is set to 0 when it finishes.
     let navbar = this.window.document.getElementById(
       CustomizableUI.AREA_NAVBAR
     );
     await TestUtils.waitForCondition(() => {
-      // This test is using a private variable, that can be renamed or removed
-      // in the future.  Use === so that this won't silently skip if the value
-      // becomes undefined.
-      return navbar.overflowable._lastOverflowCounter === 0;
+      return !navbar.overflowable.isHandlingOverflow();
     });
 
     let searchbar = this.window.document.getElementById("searchbar");
     if (!searchbar) {
       throw new Error("The search bar should exist.");
     }
 
     // If the search bar overflows, it's placed inside the overflow panel.
--- a/browser/components/search/extensions/engines.json
+++ b/browser/components/search/extensions/engines.json
@@ -1,39 +1,42 @@
 {
   "data": [
     {
       "engineName": "Google",
       "orderHint": 1000,
       "webExtensionId": "google@search.mozilla.org",
       "webExtensionVersion": "1.0",
       "searchUrlGetExtraCodes": "client=firefox-b-d",
+      "telemetryId": "google-b-d",
       "appliesTo": [{
         "included": { "everywhere": true },
         "default": "yes"
       }, {
         "included": {
           "regions": ["ru", "tr", "by", "kz"],
           "locales": {
             "matches": ["ru", "tr", "be", "kk"],
             "startsWith": ["en"]
-          }
+          },
+          "telemetryId": "google"
         },
         "default": "no"
       }, {
         "included": {
           "regions": ["cn"],
           "locales": {
             "matches": ["zh-CN"]
           }
         },
         "default": "no"
       }, {
         "included": { "regions": ["us"] },
-        "searchUrlGetExtraCodes": "client=firefox-b-1-d"
+        "searchUrlGetExtraCodes": "client=firefox-b-1-d",
+        "telemetryId": "google-b-1-d"
       }],
       "aliases": ["@google"]
     },
     {
       "engineName": "Bing",
       "webExtensionId": "bing@search.mozilla.org",
       "webExtensionVersion": "1.0",
       "appliesTo": [{
@@ -57,32 +60,34 @@
         "included": { "regions": ["default"] }
       }],
       "aliases": ["@bing"]
     },
     {
       "engineName": "\u767E\u5EA6",
       "webExtensionId": "baidu@search.mozilla.org",
       "webExtensionVersion": "1.0",
+      "telemetryId": "baidu",
       "appliesTo": [{
         "included": { "locales": { "matches": ["zh-CN"] } }
       }, {
         "included": {
           "regions": ["cn"],
           "locales": { "matches": ["zh-CN"] }
         },
         "default": "yes"
       }],
       "aliases": ["@\u767E\u5EA6", "@baidu"]
     },
     {
       "engineName": "Amazon.com",
       "orderHint": 500,
       "webExtensionId": "amazondotcom@search.mozilla.org",
       "webExtensionVersion": "1.1",
+      "telemetryId": "amazondotcom",
       "appliesTo": [{
         "included": { "regions": ["default"] }
       }, {
         "included": {
           "locales": {
             "matches": [
               "ach", "af", "ar", "az", "bg", "cak", "en-US", "eo",
               "es-AR", "fa", "gn", "hy-AM", "ia", "is", "ka", "km",
@@ -101,64 +106,68 @@
               "lt", "mk", "ms", "my", "ro", "si", "th", "tl",
               "trs", "uz"
             ]
           }
         },
         "webExtensionId": "amazon@search.mozilla.org",
         "webExtensionVersion": "1.1",
         "webExtensionLocales": ["au"],
+        "telemetryId": "amazon-au",
         "engineName": "Amazon.com.au"
       }, {
         "included": {
           "regions": ["ca"],
           "locales": {
             "matches": [
               "ach", "af", "ar", "az", "bg", "cak", "en-US", "eo",
               "es-AR", "fa", "gn", "hy-AM", "ia", "is", "ka", "km",
               "lt", "mk", "ms", "my", "ro", "si", "th", "tl",
               "trs", "uz"
             ]
           }
         },
         "webExtensionId": "amazon@search.mozilla.org",
         "webExtensionVersion": "1.1",
         "webExtensionLocales": ["ca"],
+        "telemetryId": "amazon-ca",
         "engineName": "Amazon.ca"
       }, {
         "included": {
           "regions": ["fr"],
           "locales": {
             "matches": [
               "ach", "af", "ar", "az", "bg", "cak", "en-US", "eo",
               "es-AR", "fa", "gn", "hy-AM", "ia", "is", "ka", "km",
               "lt", "mk", "ms", "my", "ro", "si", "th", "tl",
               "trs", "uz"
             ]
           }
         },
         "webExtensionId": "amazon@search.mozilla.org",
         "webExtensionVersion": "1.1",
         "webExtensionLocales": ["france"],
+        "telemetryId": "amazon-france",
         "engineName": "Amazon.fr"
       }, {
         "included": {
           "regions": ["gb"],
           "locales": {
             "matches": [
               "ach", "af", "ar", "az", "bg", "cak", "en-US", "eo",
               "es-AR", "fa", "gn", "hy-AM", "ia", "is", "ka", "km",
               "lt", "mk", "ms", "my", "ro", "si", "th", "tl",
               "trs", "uz"
             ]
           }
         },
         "webExtensionId": "amazon@search.mozilla.org",
         "webExtensionVersion": "1.1",
         "webExtensionLocales": ["en-GB"],
+        "telemetryId": "amazon-en-GB",
         "engineName": "Amazon.co.uk"
       }],
       "aliases": ["amazon"]
     },
     {
       "engineName": "Amazon",
       "orderHint": 500,
       "webExtensionId": "amazon@search.mozilla.org",
@@ -166,77 +175,87 @@
       "appliesTo": [{
         "included": {
           "locales": { "matches": [
             "as", "bn", "bn-IN", "kn", "gu-IN", "mai", "ml", "mr",
             "or", "pa-IN", "ta", "te", "ur"
           ]}
         },
         "webExtensionLocales": ["in"],
+        "telemetryId": "amazon-in",
         "engineName": "Amazon.in"
       }, {
         "included": {
           "locales": { "matches": ["br", "ff", "fr", "son", "wo"] }
         },
         "webExtensionLocales": ["france"],
+        "telemetryId": "amazon-france",
         "engineName": "Amazon.fr"
       }, {
         "included": {
           "regions": ["ca"],
           "locales": { "matches": ["br", "ff", "fr", "son", "wo"] }
         },
         "webExtensionLocales": ["ca"],
+        "telemetryId": "amazon-ca",
         "engineName": "Amazon.ca"
       }, {
         "included": { "locales": { "matches": ["en-CA"] } },
         "webExtensionLocales": ["ca"],
+        "telemetryId": "amazon-ca",
         "engineName": "Amazon.ca"
       }, {
         "included": { "locales": { "matches": ["ja-JP-mac", "ja"] } },
         "webExtensionLocales": ["jp"],
+        "telemetryId": "amazon-jp",
         "engineName": "Amazon.jp"
       }, {
         "included": { "locales": { "matches": ["it", "lij"] } },
         "webExtensionLocales": ["it"],
+        "telemetryId": "amazon-it",
         "engineName": "Amazon.it"
       }, {
         "included": { "locales": { "matches": ["de", "dsb", "hsb"] } },
         "webExtensionLocales": ["de"],
+        "telemetryId": "amazon-de",
         "engineName": "Amazon.de"
       }, {
         "included": {
           "locales": {
             "matches": [
               "cy", "da", "el", "en-GB", "eu", "ga-IE", "gd", "gl", "hr",
               "nb-NO", "nn-NO", "pt-PT", "sq", "sr"
             ]
           }
         },
         "webExtensionLocales": ["en-GB"],
+        "telemetryId": "amazon-en-GB",
         "engineName": "Amazon.co.uk"
       }, {
         "included": {
           "regions": ["au"],
           "locales": {
             "matches": [
               "cy", "da", "el", "en-GB", "eu", "ga-IE", "gd", "gl", "hr",
               "nb-NO", "nn-NO", "pt-PT", "sq", "sr"
             ]
           }
         },
         "webExtensionLocales": ["au"],
+        "telemetryId": "amazon-au",
         "engineName": "Amazon.au"
       }],
       "aliases": ["amazon"]
     },
     {
       "engineName": "\u4E9A\u9A6C\u900A",
       "orderHint": 500,
       "webExtensionId": "amazondotcn@search.mozilla.org",
       "webExtensionVersion": "1.0",
+      "telemetryId": "amazondotcn",
       "appliesTo": [{
         "included": { "locales": { "matches": ["zh-CN"] } }
       }],
       "aliases": ["amazon"]
     },
     {
       "engineName": "eBay",
       "orderHint": 500,
@@ -247,114 +266,135 @@
       }, {
         "included": { "locales": { "matches": ["en-US"] } },
         "excluded": { "regions": ["ru", "tr", "by", "kz"] }
       }, {
         "included": {
           "locales": { "matches": ["en-US"] },
           "regions": ["au"]
         },
-        "webExtensionLocales": ["au"]
+        "webExtensionLocales": ["au"],
+        "telemetryId": "ebay-au"
       }, {
         "included": {
           "locales": { "matches": ["en-US"] },
           "regions": ["ie"]
         },
-        "webExtensionLocales": ["ie"]
+        "webExtensionLocales": ["ie"],
+        "telemetryId": "ebay-ie"
       }, {
         "included": {
           "locales": { "matches": ["en-US"] },
           "regions": ["be"]
         },
-        "webExtensionLocales": ["be"]
+        "webExtensionLocales": ["be"],
+        "telemetryId": "ebay-be"
       }, {
         "included": { "locales": {
           "matches": ["an", "ast", "ca", "ca-valencia", "es-ES", "eu", "gl"]
         }},
-        "webExtensionLocales": ["es"]
+        "webExtensionLocales": ["es"],
+        "telemetryId": "ebay-es"
       }, {
         "included": { "locales": { "matches": ["br", "fr", "wo"] }},
-        "webExtensionLocales": ["fr"]
+        "webExtensionLocales": ["fr"],
+        "telemetryId": "ebay-fr"
       }, {
         "included": {
           "locales": { "matches": ["br", "fr", "wo"] },
           "regions": ["be"]
         },
-        "webExtensionLocales": ["be"]
+        "webExtensionLocales": ["be"],
+        "telemetryId": "ebay-be"
       }, {
         "included": {
           "locales": { "matches": ["br", "fr", "wo"] },
           "regions": ["ca"]
         },
-        "webExtensionLocales": ["ca"]
+        "webExtensionLocales": ["ca"],
+        "telemetryId": "ebay-ca"
       }, {
         "included": {
           "locales": { "matches": ["br", "fr", "wo"] },
           "regions": ["ch"]
         },
-        "webExtensionLocales": ["ch"]
+        "webExtensionLocales": ["ch"],
+        "telemetryId": "ebay-ch"
       }, {
         "included": { "locales": { "matches": ["cy", "en-GB", "gd"] }},
-        "webExtensionLocales": ["uk"]
+        "webExtensionLocales": ["uk"],
+        "telemetryId": "ebay-uk"
       }, {
         "included": {
           "locales": { "matches": ["cy", "en-GB", "gd"] },
           "regions": ["au"]
         },
-        "webExtensionLocales": ["au"]
+        "webExtensionLocales": ["au"],
+        "telemetryId": "ebay-au"
       }, {
         "included": {
           "locales": { "matches": ["cy", "en-GB", "gd"] },
           "regions": ["ie"]
         },
-        "webExtensionLocales": ["ie"]
+        "webExtensionLocales": ["ie"],
+        "telemetryId": "ebay-ie"
       }, {
         "included": { "locales": { "matches": ["de", "dsb", "hsb"] }},
-        "webExtensionLocales": ["de"]
+        "webExtensionLocales": ["de"],
+        "telemetryId": "ebay-de"
       }, {
         "included": {
           "locales": { "matches": ["de", "dsb", "hsb"] },
           "regions": ["at"]
         },
-        "webExtensionLocales": ["at"]
+        "webExtensionLocales": ["at"],
+        "telemetryId": "ebay-at"
       }, {
         "included": {
           "locales": { "matches": ["de", "dsb", "hsb"] },
           "regions": ["ch"]
         },
-        "webExtensionLocales": ["ch"]
+        "webExtensionLocales": ["ch"],
+        "telemetryId": "ebay-ch"
       }, {
         "included": { "locales": { "matches": ["en-CA"] }},
-        "webExtensionLocales": ["ca"]
+        "webExtensionLocales": ["ca"],
+        "telemetryId": "ebay-ca"
       }, {
         "included": { "locales": { "matches": ["fy-NL", "nl"] }},
-        "webExtensionLocales": ["nl"]
+        "webExtensionLocales": ["nl"],
+        "telemetryId": "ebay-nl"
       }, {
         "included": {
           "locales": { "matches": ["fy-NL", "nl"] },
           "regions": ["be"]
         },
-        "webExtensionLocales": ["be"]
+        "webExtensionLocales": ["be"],
+        "telemetryId": "ebay-be"
       }, {
         "included": { "locales": { "matches": ["ga-IE"] }},
-        "webExtensionLocales": ["ie"]
+        "webExtensionLocales": ["ie"],
+        "telemetryId": "ebay-ie"
       }, {
         "included": { "locales": { "matches": ["it", "lij"] }},
-        "webExtensionLocales": ["it"]
+        "webExtensionLocales": ["it"],
+        "telemetryId": "ebay-it"
       }, {
         "included": { "locales": { "matches": ["rm"] }},
-        "webExtensionLocales": ["ch"]
+        "webExtensionLocales": ["ch"],
+        "telemetryId": "ebay-ch"
       }],
       "aliases": ["@ebay"]
     },
     {
       "engineName": "DuckDuckGo",
       "orderHint": 500,
       "webExtensionId": "ddg@search.mozilla.org",
       "webExtensionVersion": "1.0",
+      "telemetryId": "ddg",
       "appliesTo": [{
         "included": { "everywhere": true }
       }],
       "aliases": ["@duckduckgo", "@ddg"]
     },
     {
       "engineName": "Yandex",
       "webExtensionId": "yandex@search.mozilla.org",
@@ -363,35 +403,41 @@
         "default": "yes",
         "included": {
           "regions": ["ru", "tr", "by", "kz"],
           "locales": {
             "matches": ["ru", "tr", "be", "kk"],
             "startsWith": ["en"]
           }
         },
-        "webExtensionLocales": ["en"]
+        "webExtensionLocales": ["en"],
+        "telemetryId": "yandex-en"
       }, {
         "included": { "locales": { "matches": ["az"] }},
-        "webExtensionLocales": ["az"]
+        "webExtensionLocales": ["az"],
+        "telemetryId": "yandex-az"
       }, {
         "included": { "locales": { "matches": ["be"] }},
         "webExtensionLocales": ["by"],
-        "engineName": "\u044F\u043D\u0434\u0435\u043A\u0441"
+        "engineName": "\u044F\u043D\u0434\u0435\u043A\u0441",
+        "telemetryId": "yandex-by"
       }, {
         "included": { "locales": { "matches": ["kk"] }},
         "webExtensionLocales": ["kk"],
-        "engineName": "\u044F\u043D\u0434\u0435\u043A\u0441"
+        "engineName": "\u044F\u043D\u0434\u0435\u043A\u0441",
+        "telemetryId": "yandex-kk"
       }, {
         "included": { "locales": { "matches": ["ru"] }},
         "webExtensionLocales": ["ru"],
-        "engineName": "\u044F\u043D\u0434\u0435\u043A\u0441"
+        "engineName": "\u044F\u043D\u0434\u0435\u043A\u0441",
+        "telemetryId": "yandex-ru"
       }, {
         "included": { "locales": { "matches": ["tr"] }},
-        "webExtensionLocales": ["tr"]
+        "webExtensionLocales": ["tr"],
+        "telemetryId": "yandex-tr"
       }],
       "aliases": ["@\u044F\u043D\u0434\u0435\u043A\u0441", "@yandex"]
     },
     {
       "engineName": "allaannonser-sv-SE",
       "webExtensionId": "allaannonser-sv-SE@search.mozilla.org",
       "webExtensionVersion": "1.2",
       "appliesTo": [{ "included": { "locales": { "matches": ["sv-SE"]}}}]
--- a/browser/components/uitour/UITour.jsm
+++ b/browser/components/uitour/UITour.jsm
@@ -151,16 +151,17 @@ var UITour = {
         allowAdd: true,
         query: "#panic-button",
         widgetName: "panic-button",
       },
     ],
     ["help", { query: "#appMenu-help-button" }],
     ["home", { query: "#home-button" }],
     ["library", { query: "#appMenu-library-button" }],
+    ["logins", { query: "#appMenu-logins-button" }],
     [
       "pocket",
       {
         allowAdd: true,
         query: aDocument => {
           // The pocket's urlbar page action button is pre-defined in the DOM.
           // It would be hidden if toggled off from the urlbar.
           let node = aDocument.getElementById("pocket-button-box");
--- a/browser/components/uitour/test/browser_UITour_availableTargets.js
+++ b/browser/components/uitour/test/browser_UITour_availableTargets.js
@@ -15,16 +15,17 @@ function getExpectedTargets() {
     "addons",
     "appMenu",
     "backForward",
     "customize",
     "devtools",
     "help",
     "home",
     "library",
+    "logins",
     "pageActionButton",
     "pageAction-bookmark",
     "pageAction-copyURL",
     "pageAction-emailLink",
     "pageAction-sendToDevice",
     ...(hasPocket ? ["pocket"] : []),
     "privateWindow",
     ...(hasQuit ? ["quit"] : []),
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -913,23 +913,19 @@ class UrlbarInput {
   get untrimmedValue() {
     return this._untrimmedValue;
   }
 
   set value(val) {
     return this._setValue(val, true);
   }
 
-  get openViewOnFocus() {
-    return this._openViewOnFocus;
-  }
-
   get openViewOnFocusForCurrentTab() {
     return (
-      this.openViewOnFocus &&
+      this._openViewOnFocus &&
       !["about:newtab", "about:home"].includes(
         this.window.gBrowser.currentURI.spec
       ) &&
       !this.isPrivate
     );
   }
 
   async updateLayoutBreakout() {
@@ -947,38 +943,28 @@ class UrlbarInput {
       !(
         (this.focused && !this.textbox.classList.contains("hidden-focus")) ||
         this.view.isOpen
       )
     ) {
       return;
     }
     this.setAttribute("breakout-extend", "true");
-
-    let customizationTarget = this.textbox.closest(".customization-target");
-    if (customizationTarget) {
-      customizationTarget.setAttribute("urlbar-breakout-extend", "true");
-    }
   }
 
   endLayoutExtend(force) {
     if (
       !this.hasAttribute("breakout-extend") ||
       (!force &&
         (this.view.isOpen ||
           (this.focused && !this.textbox.classList.contains("hidden-focus"))))
     ) {
       return;
     }
     this.removeAttribute("breakout-extend");
-
-    let customizationTarget = this.textbox.closest(".customization-target");
-    if (customizationTarget) {
-      customizationTarget.removeAttribute("urlbar-breakout-extend");
-    }
   }
 
   setPageProxyState(state) {
     this.setAttribute("pageproxystate", state);
     this._inputContainer.setAttribute("pageproxystate", state);
     this._identityBox.setAttribute("pageproxystate", state);
   }
 
--- a/browser/components/urlbar/tests/browser/browser_openViewOnFocus.js
+++ b/browser/components/urlbar/tests/browser/browser_openViewOnFocus.js
@@ -11,46 +11,60 @@ async function checkOpensOnFocus(win = w
   // Even with openViewOnFocus = true, the view should not open when the input
   // is focused programmatically.
   win.gURLBar.blur();
   win.gURLBar.focus();
   Assert.ok(!win.gURLBar.view.isOpen, "check urlbar panel is not open");
   Assert.ok(win.gURLBar.dropmarker.hidden, "The dropmarker should be hidden");
   win.gURLBar.blur();
   Assert.ok(win.gURLBar.dropmarker.hidden, "The dropmarker should be hidden");
+
   // Check the keyboard shortcut.
   await UrlbarTestUtils.promisePopupOpen(win, () => {
     win.document.getElementById("Browser:OpenLocation").doCommand();
   });
-  win.gURLBar.blur();
+  await UrlbarTestUtils.promisePopupClose(win, () => {
+    win.gURLBar.blur();
+  });
+
   // Focus with the mouse.
   await UrlbarTestUtils.promisePopupOpen(win, () => {
     EventUtils.synthesizeMouseAtCenter(win.gURLBar.inputField, {});
   });
-  win.gURLBar.blur();
+  await UrlbarTestUtils.promisePopupClose(win, () => {
+    win.gURLBar.blur();
+  });
 }
 
-function checkDoesNotOpenOnFocus(win = window) {
+async function checkDoesNotOpenOnFocus(win = window) {
   Assert.ok(
     !win.gURLBar.openViewOnFocusForCurrentTab,
     "openViewOnFocusForCurrentTab should be false"
   );
   // The view should not open when the input is focused programmatically.
   win.gURLBar.blur();
   win.gURLBar.focus();
   Assert.ok(!win.gURLBar.view.isOpen, "check urlbar panel is not open");
   Assert.ok(win.gURLBar.dropmarker.hidden, "The dropmarker should be hidden");
   win.gURLBar.blur();
   Assert.ok(win.gURLBar.dropmarker.hidden, "The dropmarker should be hidden");
+
   // Check the keyboard shortcut.
   win.document.getElementById("Browser:OpenLocation").doCommand();
+  // Because the panel opening may not be immediate, we must wait a bit.
+  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+  await new Promise(resolve => setTimeout(resolve, 500));
   Assert.ok(!win.gURLBar.view.isOpen, "check urlbar panel is not open");
   win.gURLBar.blur();
+
   // Focus with the mouse.
   EventUtils.synthesizeMouseAtCenter(win.gURLBar.inputField, {});
+  // Because the panel opening may not be immediate, we must wait a bit.
+  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+  await new Promise(resolve => setTimeout(resolve, 500));
   Assert.ok(!win.gURLBar.view.isOpen, "check urlbar panel is not open");
   win.gURLBar.blur();
 }
 
 add_task(async function setUp() {
   await SpecialPowers.pushPrefEnv({
     set: [["browser.urlbar.openViewOnFocus", true]],
   });
@@ -75,43 +89,48 @@ add_task(async function test() {
 
 add_task(async function newtabAndHome() {
   for (let url of ["about:newtab", "about:home"]) {
     // withNewTab randomly hangs on these pages when waitForLoad = true (the
     // default), so pass false.
     await BrowserTestUtils.withNewTab(
       { gBrowser, url, waitForLoad: false },
       async browser => {
+        // We don't wait for load, but we must ensure to be on the expected url.
+        await TestUtils.waitForCondition(
+          () => window.gBrowser.currentURI.spec == url,
+          "Ensure we're on the expected page"
+        );
         // openViewOnFocus should be disabled for these pages even though the
         // pref is true.
-        checkDoesNotOpenOnFocus();
+        await checkDoesNotOpenOnFocus();
         // Open a new tab where openViewOnFocus should be enabled.
         await BrowserTestUtils.withNewTab(
           { gBrowser, url: "http://example.com/" },
           async otherBrowser => {
             // openViewOnFocus should be enabled.
             await checkOpensOnFocus();
             // Switch back to about:newtab/home.  openViewOnFocus should be
             // disabled.
             await BrowserTestUtils.switchTab(
               gBrowser,
               gBrowser.getTabForBrowser(browser)
             );
-            checkDoesNotOpenOnFocus();
+            await checkDoesNotOpenOnFocus();
             // Switch back to example.com.  openViewOnFocus should be enabled.
             await BrowserTestUtils.switchTab(
               gBrowser,
               gBrowser.getTabForBrowser(otherBrowser)
             );
             await checkOpensOnFocus();
           }
         );
         // After example.com closes, about:newtab/home should be selected again,
         // and openViewOnFocus should be disabled.
-        checkDoesNotOpenOnFocus();
+        await checkDoesNotOpenOnFocus();
         // Load example.com in the same tab.  openViewOnFocus should become
         // enabled.
         await BrowserTestUtils.loadURI(
           gBrowser.selectedBrowser,
           "http://example.com/"
         );
         await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
         await checkOpensOnFocus();
@@ -119,11 +138,11 @@ add_task(async function newtabAndHome() 
     );
   }
 });
 
 add_task(async function privateWindow() {
   let privateWin = await BrowserTestUtils.openNewBrowserWindow({
     private: true,
   });
-  checkDoesNotOpenOnFocus(privateWin);
+  await checkDoesNotOpenOnFocus(privateWin);
   await BrowserTestUtils.closeWindow(privateWin);
 });
--- a/browser/themes/shared/controlcenter/panel.inc.css
+++ b/browser/themes/shared/controlcenter/panel.inc.css
@@ -1069,34 +1069,15 @@ description#identity-popup-content-verif
 #protections-popup-trackers-blocked-counter-description {
   color: #737373;
 }
 
 :root[lwt-popup-brighttext] #protections-popup-trackers-blocked-counter-description {
   color: #f9f9fa;
 }
 
-#protections-popup-tp-switch:not([enabled])[showdotindicator]::after {
-  background: #00B3F4;
-  border: 2px rgba(0,144,237,0.5) solid;
-  content: " ";
-  border-radius: 6px;
-  position: absolute;
-  margin: auto;
-  top: 0;
-  bottom: 12px;
-  left: 21px;
-  height: 6px;
-  width: 6px;
-}
-
-#protections-popup-tp-switch:not([enabled])[showdotindicator]:-moz-locale-dir(rtl)::after {
-  left: unset;
-  right: 21px;
-}
-
 .protections-popup-description {
   border-bottom: 1px solid var(--panel-separator-color);
 }
 
 .protections-popup-description > description {
   margin: 10px 24px;
 }
--- a/browser/themes/shared/identity-block/identity-block.inc.css
+++ b/browser/themes/shared/identity-block/identity-block.inc.css
@@ -263,26 +263,16 @@
   transform: translateX(-256px);
   width: 272px;
   background-size: auto;
   height: 16px;
   min-height: 16px;
   -moz-context-properties: fill, fill-opacity;
 }
 
-#tracking-protection-icon-box[hasException]::before {
-  background: url(chrome://browser/skin/badge-blue.svg);
-  content: "";
-  position: absolute;
-  bottom: 15px;
-  right: 0;
-  height: 14px;
-  width: 14px;
-}
-
 #tracking-protection-icon-box[hasException]:-moz-locale-dir(rtl)::before {
   left: 0;
   right: unset;
   /* This is needed in order to put the dot in front of the shield icon in rtl
      mode */
   z-index: 1;
 }
 
--- a/browser/themes/windows/controlcenter/panel.css
+++ b/browser/themes/windows/controlcenter/panel.css
@@ -5,9 +5,10 @@
 %include ../../shared/controlcenter/panel.inc.css
 
 .tracking-protection-button > .button-box > .button-icon {
   margin-inline-end: 5px;
 }
 
 .protections-popup-tp-switch:-moz-focusring {
   outline: 1px dotted;
+  outline-offset: unset;
 }
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/components/ObjectInspectorItem.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/components/ObjectInspectorItem.js
@@ -305,17 +305,17 @@ class ObjectInspectorItem extends Compon
 
     if (!item || !item.contents || !item.contents.watchpoint) {
       return;
     }
 
     const watchpoint = item.contents.watchpoint;
     return dom.button({
       className: `remove-${watchpoint}-watchpoint`,
-      title: L10N.getStr("watchpoints.removeWatchpoint"),
+      title: L10N.getStr("watchpoints.removeWatchpointTooltip"),
       onClick: () => removeWatchpoint(item),
     });
   }
 
   render() {
     const { arrow } = this.props;
 
     const { label, value } = this.getLabelAndValue();
--- a/devtools/client/debugger/src/components/SecondaryPanes/Scopes.js
+++ b/devtools/client/debugger/src/components/SecondaryPanes/Scopes.js
@@ -123,49 +123,56 @@ class Scopes extends PureComponent<Props
       !item.parent ||
       !item.parent.contents ||
       !item.contents.configurable
     ) {
       return;
     }
 
     if (!item.contents || item.contents.watchpoint) {
+      const removeWatchpointLabel = L10N.getStr("watchpoints.removeWatchpoint");
+
       const removeWatchpointItem = {
         id: "node-menu-remove-watchpoint",
-        // NOTE: we're going to update the UI to add a "break on..."
-        // sub menu. At that point we'll translate the strings. bug 1580591
-        label: "Remove watchpoint",
+        label: removeWatchpointLabel,
         disabled: false,
         click: () => removeWatchpoint(item),
       };
 
       const menuItems = [removeWatchpointItem];
       return showMenu(event, menuItems);
     }
 
-    // NOTE: we're going to update the UI to add a "break on..."
-    // sub menu. At that point we'll translate the strings. bug 1580591
-    const addSetWatchpointLabel = "Pause on set";
-    const addGetWatchpointLabel = "Pause on get";
+    const addSetWatchpointLabel = L10N.getStr("watchpoints.setWatchpoint");
+    const addGetWatchpointLabel = L10N.getStr("watchpoints.getWatchpoint");
+    const watchpointsSubmenuLabel = L10N.getStr("watchpoints.submenu");
 
-    const addSetWatchpoint = {
+    const addSetWatchpointItem = {
       id: "node-menu-add-set-watchpoint",
       label: addSetWatchpointLabel,
       disabled: false,
       click: () => addWatchpoint(item, "set"),
     };
 
-    const addGetWatchpoint = {
+    const addGetWatchpointItem = {
       id: "node-menu-add-get-watchpoint",
       label: addGetWatchpointLabel,
       disabled: false,
       click: () => addWatchpoint(item, "get"),
     };
 
-    const menuItems = [addGetWatchpoint, addSetWatchpoint];
+    const watchpointsSubmenuItem = {
+      id: "node-menu-watchpoints",
+      label: watchpointsSubmenuLabel,
+      disabled: false,
+      click: () => addWatchpoint(item, "set"),
+      submenu: [addSetWatchpointItem, addGetWatchpointItem],
+    };
+
+    const menuItems = [watchpointsSubmenuItem];
     showMenu(event, menuItems);
   };
 
   renderScopesList() {
     const {
       cx,
       isLoading,
       openLink,
--- a/devtools/client/debugger/test/mochitest/browser_dbg-watchpoints.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg-watchpoints.js
@@ -11,17 +11,18 @@ add_task(async function() {
   await navigate(dbg, "doc-watchpoints.html", "doc-watchpoints.html");
   await selectSource(dbg, "doc-watchpoints.html");
   await waitForPaused(dbg);
 
   info(`Add a get watchpoint at b`);
   await toggleScopeNode(dbg, 3);
   const addedWatchpoint = waitForDispatch(dbg, "SET_WATCHPOINT");
   await rightClickScopeNode(dbg, 5);
-  selectContextMenuItem(dbg, selectors.addGetWatchpoint);
+  selectContextMenuItem(dbg, selectors.watchpointsSubmenu);
+  document.querySelector(selectors.addGetWatchpoint).click();
   await addedWatchpoint;
 
   info(`Resume and wait to pause at the access to b on line 12`);
   resume(dbg);
   await waitForPaused(dbg);
   await waitForState(dbg, () => dbg.selectors.getSelectedInlinePreviews());
   assertPausedAtSourceAndLine(
     dbg,
--- a/devtools/client/debugger/test/mochitest/helpers.js
+++ b/devtools/client/debugger/test/mochitest/helpers.js
@@ -1297,16 +1297,17 @@ const selectors = {
   projectSerchExpandedResults: ".project-text-search .result",
   threadsPaneItems: ".threads-pane .thread",
   threadsPaneItem: i => `.threads-pane .thread:nth-child(${i})`,
   threadsPaneItemPause: i => `${selectors.threadsPaneItem(i)} .pause-badge`,
   CodeMirrorLines: ".CodeMirror-lines",
   inlinePreviewLables: ".CodeMirror-linewidget .inline-preview-label",
   inlinePreviewValues: ".CodeMirror-linewidget .inline-preview-value",
   inlinePreviewOpenInspector: ".inline-preview-value button.open-inspector",
+  watchpointsSubmenu: "#node-menu-watchpoints",
   addGetWatchpoint: "#node-menu-add-get-watchpoint",
   addSetWatchpoint: "#node-menu-add-set-watchpoint",
   removeWatchpoint: "#node-menu-remove-watchpoint",
   logEventsCheckbox: ".events-header input",
 };
 
 function getSelector(elementName, ...args) {
   let selector = selectors[elementName];
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -227,16 +227,17 @@ devtools.jar:
     skin/images/select-arrow.svg (themes/images/select-arrow.svg)
     skin/images/settings.svg (themes/images/settings.svg)
     skin/images/lock.svg (themes/images/lock.svg)
 
     # Netmonitor
     content/netmonitor/src/assets/styles/httpi.css (netmonitor/src/assets/styles/httpi.css)
     content/netmonitor/src/assets/styles/netmonitor.css (netmonitor/src/assets/styles/netmonitor.css)
     content/netmonitor/src/assets/styles/NetworkActionBar.css (netmonitor/src/assets/styles/NetworkActionBar.css)
+    content/netmonitor/src/assets/styles/RequestBlockingPanel.css (netmonitor/src/assets/styles/RequestBlockingPanel.css)
     content/netmonitor/src/assets/styles/NetworkDetailsPanel.css (netmonitor/src/assets/styles/NetworkDetailsPanel.css)
     content/netmonitor/src/assets/styles/CustomRequestPanel.css (netmonitor/src/assets/styles/CustomRequestPanel.css)
     content/netmonitor/src/assets/styles/RequestList.css (netmonitor/src/assets/styles/RequestList.css)
     content/netmonitor/src/assets/styles/StatisticsPanel.css (netmonitor/src/assets/styles/StatisticsPanel.css)
     content/netmonitor/src/assets/styles/StatusBar.css (netmonitor/src/assets/styles/StatusBar.css)
     content/netmonitor/src/assets/styles/Toolbar.css (netmonitor/src/assets/styles/Toolbar.css)
     content/netmonitor/src/assets/styles/variables.css (netmonitor/src/assets/styles/variables.css)
     content/netmonitor/src/assets/icons/play.svg (netmonitor/src/assets/icons/play.svg)
--- a/devtools/client/locales/en-US/debugger.properties
+++ b/devtools/client/locales/en-US/debugger.properties
@@ -478,20 +478,35 @@ xhrBreakpoints.label=Add XHR breakpoint
 
 # LOCALIZATION NOTE (xhrBreakpoints.item.label): message displayed when reaching a breakpoint for XHR requests. %S is replaced by the path provided as condition for the breakpoint.
 xhrBreakpoints.item.label=URL contains “%S”
 
 # LOCALIZATION NOTE (pauseOnAnyXHR): The pause on any XHR checkbox description
 # when the debugger will pause on any XHR requests.
 pauseOnAnyXHR=Pause on any URL
 
+# LOCALIZATION NOTE (watchpoints.submenu): This is the text for the watchpoints sub-menu.
+watchpoints.submenu=Break on…
+
+# LOCALIZATION NOTE (watchpoints.getWatchpoint): This is the text that appears in the
+# watchpoints sub-menu to add a "get" watchpoint on an object property.
+watchpoints.getWatchpoint=Property get
+
+# LOCALIZATION NOTE (watchpoints.setWatchpoint): This is the text that appears in the
+# watchpoints submenu to add a "set" watchpoint on an object property.
+watchpoints.setWatchpoint=Property set
+
 # LOCALIZATION NOTE (watchpoints.removeWatchpoint): This is the text that appears in the
 # context menu to delete a watchpoint on an object property.
 watchpoints.removeWatchpoint=Remove watchpoint
 
+# LOCALIZATION NOTE (watchpoints.removeWatchpointTooltip): This is the text that appears in the
+# tooltip to delete a watchpoint on an object property.
+watchpoints.removeWatchpointTooltip=Remove watchpoint
+
 # LOCALIZATION NOTE (sourceTabs.closeTab): Editor source tab context menu item
 # for closing the selected tab below the mouse.
 sourceTabs.closeTab=Close tab
 sourceTabs.closeTab.accesskey=c
 sourceTabs.closeTab.key=CmdOrCtrl+W
 
 # LOCALIZATION NOTE (sourceTabs.closeOtherTabs): Editor source tab context menu item
 # for closing the other tabs.
@@ -777,17 +792,17 @@ whyPaused.xhr=Paused on XMLHttpRequest
 # LOCALIZATION NOTE (whyPaused.promiseRejection): The text that is displayed
 # in a info block explaining how the debugger is currently paused on a
 # promise rejection
 whyPaused.promiseRejection=Paused on promise rejection
 
 # LOCALIZATION NOTE (whyPaused.getWatchpoint): The text that is displayed
 # in a info block explaining how the debugger is currently paused at a
 # watchpoint on an object property
-whyPaused.getWatchpoint=Paused on property access
+whyPaused.getWatchpoint=Paused on property get
 
 # LOCALIZATION NOTE (whyPaused.setWatchpoint): The text that is displayed
 # in an info block explaining how the debugger is currently paused at a
 # watchpoint on an object property
 whyPaused.setWatchpoint=Paused on property set
 
 # LOCALIZATION NOTE (whyPaused.assert): The text that is displayed
 # in a info block explaining how the debugger is currently paused on an
--- a/devtools/client/locales/en-US/netmonitor.properties
+++ b/devtools/client/locales/en-US/netmonitor.properties
@@ -732,16 +732,32 @@ netmonitor.search.status.labels.fileCoun
 # LOCALIZATION NOTE (netmonitor.search.status.labels.error): This is the label
 # displayed in the search results status bar when status is set to error.
 netmonitor.search.status.labels.error=Search error.
 
 # LOCALIZATION NOTE (netmonitor.actionbar.requestBlocking): This is the label displayed
 # in the action bar's request blocking tab
 netmonitor.actionbar.requestBlocking=Request Blocking
 
+# LOCALIZATION NOTE (netmonitor.actionbar.enableBlocking): This is the label displayed
+# in request blocking tab to represent if requests blocking should be enabled
+netmonitor.actionbar.enableBlocking=Enable Request Blocking
+
+# LOCALIZATION NOTE (netmonitor.actionbar.blockSearchPlaceholder): This is the
+# placeholder text for the request addition form
+netmonitor.actionbar.blockSearchPlaceholder=Block resource when URL contains
+
+# LOCALIZATION NOTE (netmonitor.actionbar.removeBlockedUrl): This is the
+# tooltip shown over the remove button for blocked URL item
+netmonitor.actionbar.removeBlockedUrl=Remove pattern
+
+# LOCALIZATION NOTE (netmonitor.actionbar.addBlockedUrl): This is the
+# tooltip shown over the Add Blocked URL button
+netmonitor.actionbar.addBlockedUrl=Add pattern
+
 # LOCALIZATION NOTE (netmonitor.actionbar.search): This is the label displayed
 # in the action bar's search tab
 netmonitor.actionbar.search=Search
 
 # LOCALIZATION NOTE (messagesTruncated): This is the text displayed
 # in the messages panel when the number of messages is over the
 # truncation limit.
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
--- a/devtools/client/netmonitor/src/actions/index.js
+++ b/devtools/client/netmonitor/src/actions/index.js
@@ -8,21 +8,23 @@ const batching = require("./batching");
 const filters = require("./filters");
 const requests = require("./requests");
 const selection = require("./selection");
 const sort = require("./sort");
 const timingMarkers = require("./timing-markers");
 const ui = require("./ui");
 const webSockets = require("./web-sockets");
 const search = require("./search");
+const requestBlocking = require("./request-blocking");
 
 Object.assign(
   exports,
   batching,
   filters,
   requests,
   search,
   selection,
   sort,
   timingMarkers,
   ui,
-  webSockets
+  webSockets,
+  requestBlocking
 );
--- a/devtools/client/netmonitor/src/actions/moz.build
+++ b/devtools/client/netmonitor/src/actions/moz.build
@@ -1,16 +1,17 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
     'batching.js',
     'filters.js',
     'index.js',
+    'request-blocking.js',
     'requests.js',
     'search.js',
     'selection.js',
     'sort.js',
     'timing-markers.js',
     'ui.js',
     'web-sockets.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/actions/request-blocking.js
@@ -0,0 +1,75 @@
+/* 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";
+
+const {
+  ADD_BLOCKED_URL,
+  TOGGLE_BLOCKING_ENABLED,
+  TOGGLE_BLOCKED_URL,
+  UPDATE_BLOCKED_URL,
+  REMOVE_BLOCKED_URL,
+} = require("../constants");
+
+/**
+ * Toggle blocking enabled
+ */
+function toggleBlockingEnabled(isChecked) {
+  /*
+    TODO:  Send a signal to server to toggle blocking
+  */
+  return {
+    type: TOGGLE_BLOCKING_ENABLED,
+    enabled: isChecked,
+  };
+}
+
+function removeBlockedUrl(url) {
+  /*
+    TODO:  Sync blocked URL list
+  */
+  return {
+    type: REMOVE_BLOCKED_URL,
+    url,
+  };
+}
+
+function addBlockedUrl(url) {
+  /*
+    TODO:  Sync blocked URL list
+  */
+  return {
+    type: ADD_BLOCKED_URL,
+    url,
+  };
+}
+
+function toggleBlockedUrl(url) {
+  /*
+    TODO:  Sync blocked URL list
+  */
+  return {
+    type: TOGGLE_BLOCKED_URL,
+    url,
+  };
+}
+
+function updateBlockedUrl(oldUrl, newUrl) {
+  /*
+    TODO:  Sync blocked URL list
+  */
+  return {
+    type: UPDATE_BLOCKED_URL,
+    oldUrl,
+    newUrl,
+  };
+}
+
+module.exports = {
+  addBlockedUrl,
+  toggleBlockingEnabled,
+  toggleBlockedUrl,
+  removeBlockedUrl,
+  updateBlockedUrl,
+};
--- a/devtools/client/netmonitor/src/actions/ui.js
+++ b/devtools/client/netmonitor/src/actions/ui.js
@@ -8,16 +8,17 @@ const {
   ACTIVITY_TYPE,
   OPEN_NETWORK_DETAILS,
   RESIZE_NETWORK_DETAILS,
   ENABLE_PERSISTENT_LOGS,
   DISABLE_BROWSER_CACHE,
   OPEN_STATISTICS,
   RESET_COLUMNS,
   SELECT_DETAILS_PANEL_TAB,
+  SELECT_ACTION_BAR_TAB,
   TOGGLE_COLUMN,
   WATERFALL_RESIZE,
   SET_COLUMNS_WIDTH,
 } = require("../constants");
 
 const { getDisplayedRequests } = require("../selectors/index");
 
 const DEVTOOLS_DISABLE_CACHE_PREF = "devtools.cache.disabled";
@@ -132,16 +133,28 @@ function resizeWaterfall(width) {
 function selectDetailsPanelTab(id) {
   return {
     type: SELECT_DETAILS_PANEL_TAB,
     id,
   };
 }
 
 /**
+ * Change the selected tab for network action bar.
+ *
+ * @param {string} id - tab id to be selected
+ */
+function selectActionBarTab(id) {
+  return {
+    type: SELECT_ACTION_BAR_TAB,
+    id,
+  };
+}
+
+/**
  * Toggles a column
  *
  * @param {string} column - The column that is going to be toggled
  */
 function toggleColumn(column) {
   return {
     type: TOGGLE_COLUMN,
     column,
@@ -196,15 +209,16 @@ module.exports = {
   openNetworkDetails,
   resizeNetworkDetails,
   enablePersistentLogs,
   disableBrowserCache,
   openStatistics,
   resetColumns,
   resizeWaterfall,
   selectDetailsPanelTab,
+  selectActionBarTab,
   toggleColumn,
   setColumnsWidth,
   toggleNetworkDetails,
   togglePersistentLogs,
   toggleBrowserCache,
   toggleStatistics,
 };
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/assets/styles/RequestBlockingPanel.css
@@ -0,0 +1,91 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* The "Enable Blocking Requests" bar */
+.network-monitor .request-blocking-enable-bar {
+  background: var(--theme-tab-toolbar-background);
+  padding: 2px 10px;
+  display: flex;
+  overflow: hidden;
+  white-space: nowrap;
+  border-bottom: 1px solid var(--theme-splitter-color);
+}
+
+.network-monitor .request-blocking-enable-form {
+  flex-grow: 1;
+}
+
+.network-monitor .request-blocking-enable-form label {
+  display: inline-block;
+}
+
+.network-monitor .request-blocking-enable-bar .request-blocking-label {
+  -moz-user-select: none;
+}
+
+.network-monitor .request-blocking-enable-bar button::before {
+  background-image: url("chrome://devtools/skin/images/close.svg");
+  transform: rotate(45deg);
+}
+
+.network-monitor .request-blocking-label {
+  cursor: default;
+}
+
+/* Blocked request list */
+.network-monitor .request-blocking-list {
+  margin: 0;
+  padding: 0;
+  border-bottom: 1px solid var(--theme-splitter-color);
+}
+
+.network-monitor .request-blocking-list li {
+  padding: 2px 10px;
+  display: flex;
+  overflow: hidden;
+  white-space: nowrap;
+}
+
+.network-monitor .request-blocking-list li.request-blocking-edit-item {
+  padding: 1px 0;
+}
+
+.network-monitor .request-blocking-list label {
+  width: calc(100% - 26px);
+  overflow: hidden;
+  text-overflow: ellipsis;
+  font-family: var(--monospace-font-family);
+}
+
+.network-monitor .request-blocking-enable-form label,
+.network-monitor .request-blocking-list label {
+  margin-top: 2px;
+}
+
+.network-monitor .request-blocking-list button {
+  visibility: hidden;
+}
+
+.network-monitor .request-blocking-list button::before {
+  background-image: url("chrome://devtools/skin/images/close.svg");
+}
+
+.network-monitor .request-blocking-list li:hover button,
+.network-monitor .request-blocking-list li:focus-within button {
+  visibility: visible;
+}
+
+/* Addition form */
+.request-blocking-edit-item form {
+  width: 100%;
+}
+
+.request-blocking-add-form input,
+.request-blocking-edit-item input {
+  width: calc(100% - 1px);
+  padding-top: 4px;
+  padding-bottom: 4px;
+  background-image: none;
+  padding-inline-start: 29px;
+}
--- a/devtools/client/netmonitor/src/assets/styles/netmonitor.css
+++ b/devtools/client/netmonitor/src/assets/styles/netmonitor.css
@@ -14,16 +14,17 @@
 @import "resource://devtools/client/shared/components/MdnLink.css";
 
 /* Network panel components & styles */
 @import "chrome://devtools/content/netmonitor/src/assets/styles/variables.css";
 @import "chrome://devtools/content/netmonitor/src/assets/styles/Toolbar.css";
 @import "chrome://devtools/content/netmonitor/src/assets/styles/StatusBar.css";
 @import "chrome://devtools/content/netmonitor/src/assets/styles/RequestList.css";
 @import "chrome://devtools/content/netmonitor/src/assets/styles/NetworkActionBar.css";
+@import "chrome://devtools/content/netmonitor/src/assets/styles/RequestBlockingPanel.css";
 @import "chrome://devtools/content/netmonitor/src/assets/styles/NetworkDetailsPanel.css";
 @import "chrome://devtools/content/netmonitor/src/assets/styles/StatisticsPanel.css";
 @import "chrome://devtools/content/netmonitor/src/assets/styles/CustomRequestPanel.css";
 @import "chrome://devtools/content/netmonitor/src/assets/styles/StatusCode.css";
 @import "chrome://devtools/content/netmonitor/src/assets/styles/websockets.css";
 @import "chrome://devtools/content/netmonitor/src/assets/styles/search.css";
 
 /* General */
--- a/devtools/client/netmonitor/src/components/NetworkActionBar.js
+++ b/devtools/client/netmonitor/src/components/NetworkActionBar.js
@@ -4,58 +4,71 @@
 
 "use strict";
 
 const Services = require("Services");
 const {
   Component,
   createFactory,
 } = require("devtools/client/shared/vendor/react");
+const {
+  connect,
+} = require("devtools/client/shared/redux/visibility-handler-connect");
 const { div } = require("devtools/client/shared/vendor/react-dom-factories");
 const { L10N } = require("../utils/l10n");
+const Actions = require("../actions/index");
 
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 
 const Tabbar = createFactory(
   require("devtools/client/shared/components/tabs/TabBar")
 );
 const TabPanel = createFactory(
   require("devtools/client/shared/components/tabs/Tabs").TabPanel
 );
 
 loader.lazyGetter(this, "SearchPanel", function() {
   return createFactory(require("./search/SearchPanel"));
 });
 
 loader.lazyGetter(this, "RequestBlockingPanel", function() {
-  return createFactory(require("./RequestBlockingPanel"));
+  return createFactory(require("./request-blocking/RequestBlockingPanel"));
 });
 
 class NetworkActionBar extends Component {
   static get propTypes() {
     return {
       connector: PropTypes.object.isRequired,
+      selectedActionBarTabId: PropTypes.string,
+      selectActionBarTab: PropTypes.func.isRequired,
     };
   }
 
   render() {
-    const { connector } = this.props;
+    const {
+      connector,
+      selectedActionBarTabId,
+      selectActionBarTab,
+    } = this.props;
 
     // The request blocking and search panels are available behind a pref
     const showBlockingPanel = Services.prefs.getBoolPref(
       "devtools.netmonitor.features.requestBlocking"
     );
     const showSearchPanel = Services.prefs.getBoolPref(
       "devtools.netmonitor.features.search"
     );
 
     return div(
       { className: "network-action-bar" },
       Tabbar(
-        {},
+        {
+          activeTabId: selectedActionBarTabId,
+          onSelect: id => selectActionBarTab(id),
+        },
         showSearchPanel &&
           TabPanel(
             {
               id: "network-action-bar-search",
               title: L10N.getStr("netmonitor.actionbar.search"),
               className: "network-action-bar-search",
             },
             SearchPanel({ connector })
@@ -69,9 +82,16 @@ class NetworkActionBar extends Component
             },
             RequestBlockingPanel()
           )
       )
     );
   }
 }
 
-module.exports = NetworkActionBar;
+module.exports = connect(
+  state => ({
+    selectedActionBarTabId: state.ui.selectedActionBarTabId,
+  }),
+  dispatch => ({
+    selectActionBarTab: id => dispatch(Actions.selectActionBarTab(id)),
+  })
+)(NetworkActionBar);
--- a/devtools/client/netmonitor/src/components/moz.build
+++ b/devtools/client/netmonitor/src/components/moz.build
@@ -1,13 +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/.
 
 DIRS += [
+    'request-blocking',
     'search',
     'websockets',
 ]
 
 DevToolsModules(
     'App.js',
     'CachePanel.js',
     'CookiesPanel.js',
@@ -16,17 +17,16 @@ DevToolsModules(
     'HeadersPanel.js',
     'HtmlPreview.js',
     'JSONPreview.js',
     'MonitorPanel.js',
     'NetworkActionBar.js',
     'NetworkDetailsPanel.js',
     'ParamsPanel.js',
     'PropertiesView.js',
-    'RequestBlockingPanel.js',
     'RequestList.js',
     'RequestListColumnCause.js',
     'RequestListColumnContentSize.js',
     'RequestListColumnCookies.js',
     'RequestListColumnDomain.js',
     'RequestListColumnFile.js',
     'RequestListColumnMethod.js',
     'RequestListColumnProtocol.js',
rename from devtools/client/netmonitor/src/components/RequestBlockingPanel.js
rename to devtools/client/netmonitor/src/components/request-blocking/RequestBlockingPanel.js
--- a/devtools/client/netmonitor/src/components/RequestBlockingPanel.js
+++ b/devtools/client/netmonitor/src/components/request-blocking/RequestBlockingPanel.js
@@ -1,15 +1,219 @@
 /* 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";
 
 const { Component } = require("devtools/client/shared/vendor/react");
+const {
+  button,
+  div,
+  form,
+  input,
+  label,
+  li,
+  span,
+  ul,
+} = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const {
+  connect,
+} = require("devtools/client/shared/redux/visibility-handler-connect");
+const Actions = require("../../actions/index");
+const { L10N } = require("../../utils/l10n");
 
-class NetworkActionBar extends Component {
+const ENABLE_BLOCKING_LABEL = L10N.getStr(
+  "netmonitor.actionbar.enableBlocking"
+);
+const ADD_BUTTON_TOOLTIP = L10N.getStr("netmonitor.actionbar.addBlockedUrl");
+const ADD_URL_PLACEHOLDER = L10N.getStr(
+  "netmonitor.actionbar.blockSearchPlaceholder"
+);
+const REMOVE_URL_TOOLTIP = L10N.getStr("netmonitor.actionbar.removeBlockedUrl");
+
+class RequestBlockingPanel extends Component {
+  static get propTypes() {
+    return {
+      blockedUrls: PropTypes.array.isRequired,
+      addBlockedUrl: PropTypes.func.isRequired,
+      removeBlockedUrl: PropTypes.func.isRequired,
+      toggleBlockingEnabled: PropTypes.func.isRequired,
+      toggleBlockedUrl: PropTypes.func.isRequired,
+      updateBlockedUrl: PropTypes.func.isRequired,
+    };
+  }
+
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      editingUrl: null,
+    };
+  }
+
+  componentDidUpdate(prevProps) {
+    if (this.state.editingUrl) {
+      this.refs.editInput.focus();
+      this.refs.editInput.select();
+    }
+  }
+
+  renderEnableBar() {
+    return div(
+      { className: "request-blocking-enable-bar" },
+      div(
+        { className: "request-blocking-enable-form" },
+        label(
+          { className: "devtools-checkbox-label" },
+          input({
+            type: "checkbox",
+            className: "devtools-checkbox",
+            ref: "enabledCheckbox",
+            onChange: () =>
+              this.props.toggleBlockingEnabled(
+                this.refs.enabledCheckbox.checked
+              ),
+          }),
+          span({ className: "request-blocking-label" }, ENABLE_BLOCKING_LABEL)
+        )
+      ),
+      button({
+        className: "devtools-button",
+        title: ADD_BUTTON_TOOLTIP,
+        onClick: () => this.refs.addInput.focus(),
+      })
+    );
+  }
+
+  renderItemContent({ url, enabled }) {
+    const { toggleBlockedUrl, removeBlockedUrl } = this.props;
+
+    return li(
+      { key: url },
+      label(
+        {
+          className: "devtools-checkbox-label",
+          onDoubleClick: () => this.setState({ editingUrl: url }),
+        },
+        input({
+          type: "checkbox",
+          className: "devtools-checkbox",
+          checked: enabled,
+          onChange: () => toggleBlockedUrl(url),
+        }),
+        span(
+          {
+            className: "request-blocking-label",
+            title: url,
+          },
+          url
+        )
+      ),
+      button({
+        className: "devtools-button",
+        title: REMOVE_URL_TOOLTIP,
+        onClick: () => removeBlockedUrl(url),
+      })
+    );
+  }
+
+  renderEditForm(url) {
+    const { updateBlockedUrl, removeBlockedUrl } = this.props;
+    return li(
+      { key: url, className: "request-blocking-edit-item" },
+      form(
+        {
+          onSubmit: e => {
+            const { editInput } = this.refs;
+            const newValue = editInput.value;
+            e.preventDefault();
+
+            if (url != newValue) {
+              if (editInput.value.trim() === "") {
+                removeBlockedUrl(url, newValue);
+              } else {
+                updateBlockedUrl(url, newValue);
+              }
+            }
+            this.setState({ editingUrl: null });
+          },
+        },
+        input({
+          type: "text",
+          defaultValue: url,
+          ref: "editInput",
+          className: "devtools-searchinput",
+          placeholder: ADD_URL_PLACEHOLDER,
+          onBlur: () => this.setState({ editingUrl: null }),
+        }),
+
+        input({ type: "submit", style: { display: "none" } })
+      )
+    );
+  }
+
+  renderBlockedList() {
+    const { blockedUrls } = this.props;
+
+    if (blockedUrls.length === 0) {
+      return null;
+    }
+
+    const listItems = blockedUrls.map(item =>
+      this.state.editingUrl === item.url
+        ? this.renderEditForm(item.url)
+        : this.renderItemContent(item)
+    );
+
+    return ul({ className: "request-blocking-list" }, ...listItems);
+  }
+
+  renderAddForm() {
+    const { addBlockedUrl } = this.props;
+    return div(
+      { className: "request-blocking-add-form" },
+      form(
+        {
+          onSubmit: e => {
+            const { addInput } = this.refs;
+            e.preventDefault();
+            addBlockedUrl(addInput.value);
+            addInput.value = "";
+            addInput.focus();
+          },
+        },
+        input({
+          type: "text",
+          ref: "addInput",
+          className: "devtools-searchinput",
+          placeholder: ADD_URL_PLACEHOLDER,
+        }),
+        input({ type: "submit", style: { display: "none" } })
+      )
+    );
+  }
+
   render() {
-    return "";
+    return div(
+      { className: "request-blocking-panel" },
+      this.renderEnableBar(),
+      this.renderBlockedList(),
+      this.renderAddForm()
+    );
   }
 }
 
-module.exports = NetworkActionBar;
+module.exports = connect(
+  state => ({
+    blockedUrls: state.requestBlocking.blockedUrls,
+  }),
+  dispatch => ({
+    toggleBlockingEnabled: checked =>
+      dispatch(Actions.toggleBlockingEnabled(checked)),
+    addBlockedUrl: url => dispatch(Actions.addBlockedUrl(url)),
+    removeBlockedUrl: url => dispatch(Actions.removeBlockedUrl(url)),
+    toggleBlockedUrl: url => dispatch(Actions.toggleBlockedUrl(url)),
+    updateBlockedUrl: (oldUrl, newUrl) =>
+      dispatch(Actions.updateBlockedUrl(oldUrl, newUrl)),
+  })
+)(RequestBlockingPanel);
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/components/request-blocking/moz.build
@@ -0,0 +1,7 @@
+# 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/.
+
+DevToolsModules(
+    'RequestBlockingPanel.js',
+)
--- a/devtools/client/netmonitor/src/constants.js
+++ b/devtools/client/netmonitor/src/constants.js
@@ -2,16 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const actionTypes = {
   ADD_REQUEST: "ADD_REQUEST",
   ADD_TIMING_MARKER: "ADD_TIMING_MARKER",
+  ADD_BLOCKED_URL: "ADD_BLOCKED_URL",
   BATCH_ACTIONS: "BATCH_ACTIONS",
   BATCH_ENABLE: "BATCH_ENABLE",
   BATCH_FLUSH: "BATCH_FLUSH",
   BLOCK_SELECTED_REQUEST_DONE: "BLOCK_SELECTED_REQUEST_DONE",
   CLEAR_REQUESTS: "CLEAR_REQUESTS",
   CLEAR_TIMING_MARKERS: "CLEAR_TIMING_MARKERS",
   CLONE_REQUEST: "CLONE_REQUEST",
   CLONE_SELECTED_REQUEST: "CLONE_SELECTED_REQUEST",
@@ -21,19 +22,24 @@ const actionTypes = {
   RIGHT_CLICK_REQUEST: "RIGHT_CLICK_REQUEST",
   ENABLE_PERSISTENT_LOGS: "ENABLE_PERSISTENT_LOGS",
   DISABLE_BROWSER_CACHE: "DISABLE_BROWSER_CACHE",
   OPEN_STATISTICS: "OPEN_STATISTICS",
   REMOVE_SELECTED_CUSTOM_REQUEST: "REMOVE_SELECTED_CUSTOM_REQUEST",
   RESET_COLUMNS: "RESET_COLUMNS",
   SELECT_REQUEST: "SELECT_REQUEST",
   SELECT_DETAILS_PANEL_TAB: "SELECT_DETAILS_PANEL_TAB",
+  SELECT_ACTION_BAR_TAB: "SELECT_ACTION_BAR_TAB",
   SEND_CUSTOM_REQUEST: "SEND_CUSTOM_REQUEST",
   SET_REQUEST_FILTER_TEXT: "SET_REQUEST_FILTER_TEXT",
   SORT_BY: "SORT_BY",
+  TOGGLE_BLOCKING_ENABLED: "TOGGLE_BLOCKING_ENABLED",
+  REMOVE_BLOCKED_URL: "REMOVE_BLOCKED_URL",
+  TOGGLE_BLOCKED_URL: "TOGGLE_BLOCKED_URL",
+  UPDATE_BLOCKED_URL: "UPDATE_BLOCKED_URL",
   TOGGLE_COLUMN: "TOGGLE_COLUMN",
   TOGGLE_RECORDING: "TOGGLE_RECORDING",
   TOGGLE_REQUEST_FILTER_TYPE: "TOGGLE_REQUEST_FILTER_TYPE",
   UNBLOCK_SELECTED_REQUEST_DONE: "UNBLOCK_SELECTED_REQUEST_DONE",
   UPDATE_REQUEST: "UPDATE_REQUEST",
   WATERFALL_RESIZE: "WATERFALL_RESIZE",
   SET_COLUMNS_WIDTH: "SET_COLUMNS_WIDTH",
   WS_ADD_FRAME: "WS_ADD_FRAME",
--- a/devtools/client/netmonitor/src/reducers/index.js
+++ b/devtools/client/netmonitor/src/reducers/index.js
@@ -1,27 +1,29 @@
 /* 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";
 
 const { combineReducers } = require("devtools/client/shared/vendor/redux");
 const batchingReducer = require("./batching");
+const requestBlockingReducer = require("./request-blocking");
 const { requestsReducer } = require("./requests");
 const { search } = require("./search");
 const { sortReducer } = require("./sort");
 const { filters } = require("./filters");
 const { timingMarkers } = require("./timing-markers");
 const { ui } = require("./ui");
 const { webSockets } = require("./web-sockets");
 const networkThrottling = require("devtools/client/shared/components/throttling/reducer");
 
 module.exports = batchingReducer(
   combineReducers({
+    requestBlocking: requestBlockingReducer,
     requests: requestsReducer,
     search,
     sort: sortReducer,
     webSockets,
     filters,
     timingMarkers,
     ui,
     networkThrottling,
--- a/devtools/client/netmonitor/src/reducers/moz.build
+++ b/devtools/client/netmonitor/src/reducers/moz.build
@@ -1,15 +1,16 @@
 # 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/.
 
 DevToolsModules(
     'batching.js',
     'filters.js',
     'index.js',
+    'request-blocking.js',
     'requests.js',
     'search.js',
     'sort.js',
     'timing-markers.js',
     'ui.js',
     'web-sockets.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/reducers/request-blocking.js
@@ -0,0 +1,100 @@
+/* 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";
+
+const {
+  ADD_BLOCKED_URL,
+  TOGGLE_BLOCKED_URL,
+  UPDATE_BLOCKED_URL,
+  REMOVE_BLOCKED_URL,
+  TOGGLE_BLOCKING_ENABLED,
+} = require("../constants");
+
+function RequestBlocking() {
+  return {
+    blockedUrls: [],
+    blockingEnabled: true,
+  };
+}
+
+function requestBlockingReducer(state = RequestBlocking(), action) {
+  switch (action.type) {
+    case ADD_BLOCKED_URL:
+      return addBlockedUrl(state, action);
+    case REMOVE_BLOCKED_URL:
+      return removeBlockedUrl(state, action);
+    case UPDATE_BLOCKED_URL:
+      return updateBlockedUrl(state, action);
+    case TOGGLE_BLOCKED_URL:
+      return toggleBlockedUrl(state, action);
+    case TOGGLE_BLOCKING_ENABLED:
+      return toggleBlockingEnabled(state, action);
+    default:
+      return state;
+  }
+}
+
+function toggleBlockingEnabled(state, action) {
+  return {
+    ...state,
+    blockingEnabled: action.enabled,
+  };
+}
+
+function addBlockedUrl(state, action) {
+  // The user can paste in a list of URLS so we need to cleanse the input
+  // Pasting a list turns new lines into spaces
+  const uniqueUrls = [...new Set(action.url.split(" "))].map(url => url.trim());
+  const newUrls = uniqueUrls
+    // Ensure the URL isn't already blocked
+    .filter(url => url && !state.blockedUrls.some(item => item.url === url))
+    // Add new URLs as enabled by default
+    .map(url => ({ url, enabled: true }));
+
+  const blockedUrls = [...state.blockedUrls, ...newUrls];
+  return {
+    ...state,
+    blockedUrls,
+  };
+}
+
+function removeBlockedUrl(state, action) {
+  return {
+    ...state,
+    blockedUrls: state.blockedUrls.filter(item => item.url != action.url),
+  };
+}
+
+function updateBlockedUrl(state, action) {
+  const { oldUrl, newUrl } = action;
+
+  const blockedUrls = state.blockedUrls.map(item => {
+    if (item.url === oldUrl) {
+      return { ...item, url: newUrl };
+    }
+    return item;
+  });
+
+  return {
+    ...state,
+    blockedUrls,
+  };
+}
+
+function toggleBlockedUrl(state, action) {
+  const blockedUrls = state.blockedUrls.map(item => {
+    if (item.url === action.url) {
+      return { ...item, enabled: !item.enabled };
+    }
+    return item;
+  });
+
+  return {
+    ...state,
+    blockedUrls,
+  };
+}
+
+module.exports = requestBlockingReducer;
--- a/devtools/client/netmonitor/src/reducers/ui.js
+++ b/devtools/client/netmonitor/src/reducers/ui.js
@@ -11,16 +11,17 @@ const {
   RESIZE_NETWORK_DETAILS,
   ENABLE_PERSISTENT_LOGS,
   DISABLE_BROWSER_CACHE,
   OPEN_STATISTICS,
   REMOVE_SELECTED_CUSTOM_REQUEST,
   RESET_COLUMNS,
   RESPONSE_HEADERS,
   SELECT_DETAILS_PANEL_TAB,
+  SELECT_ACTION_BAR_TAB,
   SEND_CUSTOM_REQUEST,
   SELECT_REQUEST,
   TOGGLE_COLUMN,
   WATERFALL_RESIZE,
   PANELS,
   MIN_COLUMN_WIDTH,
   SET_COLUMNS_WIDTH,
 } = require("../constants");
@@ -76,16 +77,17 @@ function UI(initialState = {}) {
     networkDetailsWidth: null,
     networkDetailsHeight: null,
     persistentLogsEnabled: Services.prefs.getBoolPref(
       "devtools.netmonitor.persistlog"
     ),
     browserCacheDisabled: Services.prefs.getBoolPref("devtools.cache.disabled"),
     statisticsOpen: false,
     waterfallWidth: null,
+    selectedActionBarTabId: null,
     ...initialState,
   };
 }
 
 function resetColumns(state) {
   return {
     ...state,
     columns: Columns(),
@@ -138,16 +140,23 @@ function openStatistics(state, action) {
 
 function setDetailsPanelTab(state, action) {
   return {
     ...state,
     detailsPanelSelectedTab: action.id,
   };
 }
 
+function setActionBarTab(state, action) {
+  return {
+    ...state,
+    selectedActionBarTabId: action.id,
+  };
+}
+
 function toggleColumn(state, action) {
   const { column } = action;
 
   if (!state.columns.hasOwnProperty(column)) {
     return state;
   }
 
   return {
@@ -200,16 +209,18 @@ function ui(state = UI(), action) {
     case RESET_COLUMNS:
       return resetColumns(state);
     case REMOVE_SELECTED_CUSTOM_REQUEST:
       return openNetworkDetails(state, { open: true });
     case SEND_CUSTOM_REQUEST:
       return openNetworkDetails(state, { open: false });
     case SELECT_DETAILS_PANEL_TAB:
       return setDetailsPanelTab(state, action);
+    case SELECT_ACTION_BAR_TAB:
+      return setActionBarTab(state, action);
     case SELECT_REQUEST:
       return openNetworkDetails(state, { open: true });
     case TOGGLE_COLUMN:
       return toggleColumn(state, action);
     case WATERFALL_RESIZE:
       return resizeWaterfall(state, action);
     case SET_COLUMNS_WIDTH:
       return setColumnsWidth(state, action);
new file mode 100644
--- /dev/null
+++ b/devtools/client/performance-new/README.md
@@ -0,0 +1,21 @@
+# Performance New
+
+This folder contains the code for the new performance panel that is a simplified recorder that works to record a performance profile, and inject it into profiler.firefox.com. This tool is not in charge of any of the analysis, only the recording.
+
+## Overall Architecture
+
+The performance panel is split into two different modes. There is the DevTools panel mode, and the browser menu bar "popup" mode. The UI is implemented for both in `devtools/client/performance-new`, and many codepaths are shared. Both use the same React/Redux setup, but then each are configured with slightly different workflows. These are documented below.
+
+### DevTools Panel
+
+This panel works similarly to the other DevTools panels. The `devtools/client/performance-new/initializer.js` is in charge of initializing the page specifically for the DevTools workflow. This script creates a `PerfActor` that is then used for talking to the Gecko Profiler component on the debuggee. This path is specifically optimized for targeting Firefox running on Android phones. This workflow can also target the current browser, but the overhead from DevTools can skew the results, making the performance profile less accurate.
+
+### Profiler Popup
+
+The popup can be enabled by going to `Tools -> Web Developer -> Enable Profiler Toolbar Icon". This then runs the initializer in`devtools/client/performance-new/popup/initializer.js`, and configures the UI to work in a configuration that is optimized for profiling the current browser. The Gecko Profiler is turned on (using the ActorReadyGeckoProfilerInterface), and is queried directly–bypassing the DevTools' remote debugging protocol.
+
+## Injecting profiles into [profiler.firefox.com]
+
+After a profile has been collected, it needs to be sent to [profiler.firefox.com] for analysis. This is done by using browser APIs to open a new tab, and then injecting the profile into the page through a frame script. See `frame-script.js` for implementation details. Both the DevTools Panel and the Popup use this frame script.
+
+[profiler.firefox.com]: https://profiler.firefox.com
--- a/devtools/client/performance-new/initializer.js
+++ b/devtools/client/performance-new/initializer.js
@@ -22,16 +22,22 @@ const actions = require("devtools/client
 const { Provider } = require("devtools/client/shared/vendor/react-redux");
 const {
   receiveProfile,
   getRecordingPreferencesFromDebuggee,
   setRecordingPreferencesOnDebuggee,
 } = require("devtools/client/performance-new/browser");
 
 /**
+ * This file initializes the DevTools Panel UI. It is in charge of initializing
+ * the DevTools specific environment, and then passing those requirements into
+ * the UI.
+ */
+
+/**
  * Initialize the panel by creating a redux store, and render the root component.
  *
  * @param perfFront - The Perf actor's front. Used to start and stop recordings.
  * @param preferenceFront - Used to get the recording preferences from the device.
  */
 async function gInit(perfFront, preferenceFront) {
   const store = createStore(reducers);
 
--- a/devtools/client/performance-new/panel.js
+++ b/devtools/client/performance-new/panel.js
@@ -1,15 +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/. */
 "use strict";
 
 loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
 
+/**
+ * This file contains the PerformancePanel, which uses a common API for DevTools to
+ * start and load everything. This will call `gInit` from the initializer.js file,
+ * which does the important initialization for the panel. This code is more concerned
+ * with wiring this panel into the rest of DevTools and fetching the Actor's fronts.
+ */
+
 class PerformancePanel {
   constructor(iframeWindow, toolbox) {
     this.panelWin = iframeWindow;
     this.toolbox = toolbox;
 
     EventEmitter.decorate(this);
   }
 
--- a/devtools/client/performance-new/popup/initializer.js
+++ b/devtools/client/performance-new/popup/initializer.js
@@ -1,15 +1,23 @@
 /* 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";
 
 /**
+ * This file initializes the profiler popup UI. It is in charge of initializing
+ * the browser specific environment, and then passing those requirements into
+ * the UI. The popup is enabled by toggle the following in the browser menu:
+ *
+ * Tools -> Web Developer -> Enable Profiler Toolbar Icon
+ */
+
+/**
  * Initialize the require function through a BrowserLoader. This loader ensures
  * that the popup can use require and has access to the window object.
  */
 const { BrowserLoader } = ChromeUtils.import(
   "resource://devtools/client/shared/browser-loader.js"
 );
 const { require } = BrowserLoader({
   baseURI: "resource://devtools/client/performance-new/popup/",
new file mode 100644
--- /dev/null
+++ b/devtools/client/performance-new/store/README.md
@@ -0,0 +1,3 @@
+# Performance New Store
+
+This folder contains the Redux store logic for both the DevTools Panel and the Profiler Popup.
--- a/devtools/client/preferences/debugger.js
+++ b/devtools/client/preferences/debugger.js
@@ -78,12 +78,11 @@ pref("devtools.debugger.features.async-s
 pref("devtools.debugger.features.skip-pausing", true);
 pref("devtools.debugger.features.autocomplete-expressions", false);
 pref("devtools.debugger.features.map-expression-bindings", true);
 pref("devtools.debugger.features.xhr-breakpoints", true);
 pref("devtools.debugger.features.original-blackbox", true);
 pref("devtools.debugger.features.event-listeners-breakpoints", true);
 pref("devtools.debugger.features.dom-mutation-breakpoints", true);
 pref("devtools.debugger.features.log-points", true);
-pref("devtools.debugger.features.overlay-step-buttons", false);
 pref("devtools.debugger.features.overlay-step-buttons", true);
 pref("devtools.debugger.features.inline-preview", true);
 pref("devtools.debugger.features.watchpoints", false);
--- a/devtools/client/shared/components/menu/MenuList.js
+++ b/devtools/client/shared/components/menu/MenuList.js
@@ -128,30 +128,31 @@ class MenuList extends PureComponent {
     const attr = {
       role: "menu",
       ref: this.setWrapperRef,
       onKeyDown: this.onKeyDown,
       onMouseOver: this.onMouseOverOrFocus,
       onMouseOut: this.onMouseOutOrBlur,
       onFocus: this.onMouseOverOrFocus,
       onBlur: this.onMouseOutOrBlur,
+      className: "menu-standard-padding",
     };
 
     if (this.props.id) {
       attr.id = this.props.id;
     }
 
     // Add padding for checkbox image if necessary.
     let hasCheckbox = false;
     Children.forEach(this.props.children, child => {
       if (typeof child.props.checked !== "undefined") {
         hasCheckbox = true;
       }
     });
     if (hasCheckbox) {
-      attr.className = "checkbox-container";
+      attr.className = "checkbox-container menu-standard-padding";
     }
 
     return div(attr, this.props.children);
   }
 }
 
 module.exports = MenuList;
--- a/devtools/client/shared/components/reps/reps.js
+++ b/devtools/client/shared/components/reps/reps.js
@@ -8310,17 +8310,17 @@ class ObjectInspectorItem extends Compon
 
     if (!item || !item.contents || !item.contents.watchpoint) {
       return;
     }
 
     const watchpoint = item.contents.watchpoint;
     return dom.button({
       className: `remove-${watchpoint}-watchpoint`,
-      title: L10N.getStr("watchpoints.removeWatchpoint"),
+      title: L10N.getStr("watchpoints.removeWatchpointTooltip"),
       onClick: () => removeWatchpoint(item)
     });
   }
 
   render() {
     const {
       arrow
     } = this.props;
--- a/devtools/client/shared/widgets/tooltip/inactive-css-tooltip-helper.js
+++ b/devtools/client/shared/widgets/tooltip/inactive-css-tooltip-helper.js
@@ -39,17 +39,17 @@ class InactiveCssTooltipHelper {
     // then insert it into the tooltip. This is needed in order for the tooltip
     // to size to the contents properly and for tests.
     await doc.l10n.translateFragment(fragment);
     doc.l10n.pauseObserving();
     tooltip.panel.appendChild(fragment);
     doc.l10n.resumeObserving();
 
     // Size the content.
-    tooltip.setContentSize({ width: 275, height: Infinity });
+    tooltip.setContentSize({ width: 267, height: Infinity });
   }
 
   /**
    * Get the template that the Fluent string will be merged with. This template
    * looks something like this but there is a variable amount of properties in the
    * fix section:
    *
    * <div class="devtools-tooltip-inactive-css">
--- a/devtools/client/themes/tooltips.css
+++ b/devtools/client/themes/tooltips.css
@@ -74,19 +74,19 @@ strong {
   color: var(--theme-body-color);
   padding: 2px;
 }
 
 /* Tooltip: Inactive CSS tooltip */
 
 .devtools-tooltip-inactive-css {
   color: var(--theme-arrowpanel-color);
-  padding: 7px 14px 9px;
+  margin: 0;
+  padding: 10px 14px 12px 14px;
   font-size: 12px;
-  margin: 0;
 }
 
 .devtools-tooltip-inactive-css,
 .devtools-tooltip-inactive-css strong {
   user-select: text;
   -moz-user-focus: normal;
 }
 
@@ -263,19 +263,19 @@ strong {
 .tooltip-container[type="arrow"]:not(.tooltip-container-xul) > .tooltip-panel,
 .tooltip-container[type="arrow"]:not(.tooltip-container-xul) > .tooltip-arrow::before {
   border: 1px solid var(--theme-arrowpanel-border-color);
 }
 
 /* Tooltip : doorhanger style */
 
 .tooltip-container[type="doorhanger"] > .tooltip-panel {
-  padding: 2px 0;
   color: var(--theme-arrowpanel-color);
-  margin: 4px;
+  margin: 0;
+  padding: 0;
   max-width: 320px;
 }
 
 .tooltip-container[type="doorhanger"] > .tooltip-panel,
 .tooltip-container[type="doorhanger"] > .tooltip-arrow::before {
   background: var(--theme-arrowpanel-background);
   border: 1px solid var(--theme-arrowpanel-border-color);
   border-radius: var(--theme-arrowpanel-border-radius);
@@ -344,34 +344,19 @@ strong {
    * But we also want to shift it in so that the box-shadow
    * is not clipped when we clip the parent so we add
    * a suitable margin for that.
    */
   --overhang: calc((var(--arrow-width) - var(--square-side)) / 2);
   margin-left: calc(var(--overhang) + var(--shadow-margin));
 }
 
-.tooltip-container[type="doorhanger"].tooltip-top > .tooltip-panel {
-  /*
-   * Drop the margin between the doorhanger and the arrow and add extra
-   * padding.
-   */
-  margin-bottom: 0;
-  padding-top: 6px;
-  padding-bottom: 2px;
-}
-
-.tooltip-container[type="doorhanger"].tooltip-bottom > .tooltip-panel {
-  /*
-   * Drop the margin between the doorhanger and the arrow and add extra
-   * padding.
-   */
-  margin-top: 0;
-  padding-top: 6px;
-  padding-bottom: 6px;
+.tooltip-container[type="doorhanger"] .menu-standard-padding {
+  margin: 0;
+  padding: 6px 0;
 }
 
 .tooltip-container[type="doorhanger"].tooltip-top > .tooltip-arrow {
   /* Overlap the arrow with the 1px border of the doorhanger */
   margin-top: -1px;
 }
 
 .tooltip-container[type="doorhanger"].tooltip-bottom > .tooltip-arrow {
--- a/devtools/client/webconsole/test/browser/browser.ini
+++ b/devtools/client/webconsole/test/browser/browser.ini
@@ -501,8 +501,9 @@ skip-if = (os == "win" && bits == 32) &&
 [browser_webconsole_warning_group_multiples.js]
 [browser_webconsole_warning_groups_outside_console_group.js]
 [browser_webconsole_warning_groups_toggle.js]
 [browser_webconsole_warning_groups.js]
 [browser_webconsole_websocket.js]
 [browser_webconsole_worker_error.js]
 [browser_webconsole_worker_evaluate.js]
 [browser_webconsole_worklet_error.js]
+skip-if = release_or_beta # bug 1582611
--- a/docshell/test/navigation/browser.ini
+++ b/docshell/test/navigation/browser.ini
@@ -4,10 +4,9 @@ support-files =
   bug343515_pg2.html
   bug343515_pg3.html
   bug343515_pg3_1.html
   bug343515_pg3_1_1.html
   bug343515_pg3_2.html
 
 [browser_bug343515.js]
 [browser_test-content-chromeflags.js]
-fail-if = fission
 tags = openwindow
--- a/docshell/test/navigation/browser_test-content-chromeflags.js
+++ b/docshell/test/navigation/browser_test-content-chromeflags.js
@@ -1,11 +1,14 @@
 const TEST_PAGE = `data:text/html,<html><body><a href="about:blank" target="_blank">Test</a></body></html>`;
-const CHROME_ALL = Ci.nsIWebBrowserChrome.CHROME_ALL;
-const CHROME_REMOTE_WINDOW = Ci.nsIWebBrowserChrome.CHROME_REMOTE_WINDOW;
+const {
+  CHROME_ALL,
+  CHROME_REMOTE_WINDOW,
+  CHROME_FISSION_WINDOW,
+} = Ci.nsIWebBrowserChrome;
 
 /**
  * Tests that when we open new browser windows from content they
  * get the full browser chrome.
  */
 add_task(async function() {
   // Make sure that the window.open call will open a new
   // window instead of a new tab.
@@ -27,20 +30,28 @@ add_task(async function() {
       let openedPromise = BrowserTestUtils.waitForNewWindow();
       BrowserTestUtils.synthesizeMouse("a", 0, 0, {}, browser);
       let win = await openedPromise;
 
       let chromeFlags = win.docShell.treeOwner
         .QueryInterface(Ci.nsIInterfaceRequestor)
         .getInterface(Ci.nsIXULWindow).chromeFlags;
 
-      // In the multi-process case, the new window will have the
+      let expected = CHROME_ALL;
+
+      // In the multi-process tab case, the new window will have the
       // CHROME_REMOTE_WINDOW flag set.
-      const EXPECTED = gMultiProcessBrowser
-        ? CHROME_ALL | CHROME_REMOTE_WINDOW
-        : CHROME_ALL;
+      if (gMultiProcessBrowser) {
+        expected |= CHROME_REMOTE_WINDOW;
+      }
 
-      is(chromeFlags, EXPECTED, "Window should have opened with all chrome");
+      // In the multi-process subframe case, the new window will have the
+      // CHROME_FISSION_WINDOW flag set.
+      if (gFissionBrowser) {
+        expected |= CHROME_FISSION_WINDOW;
+      }
 
-      BrowserTestUtils.closeWindow(win);
+      is(chromeFlags, expected, "Window should have opened with all chrome");
+
+      await BrowserTestUtils.closeWindow(win);
     }
   );
 });
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -10576,16 +10576,20 @@ void Document::Destroy() {
   if (mOriginalDocument) {
     mOriginalDocument->mLatestStaticClone = nullptr;
   }
 
   // Shut down our external resource map.  We might not need this for
   // leak-fixing if we fix nsDocumentViewer to do cycle-collection, but
   // tearing down all those frame trees right now is the right thing to do.
   mExternalResourceMap.Shutdown();
+
+  // Manually break cycles via promise's global object pointer.
+  mReadyForIdle = nullptr;
+  mOrientationPendingPromise = nullptr;
 }
 
 void Document::RemovedFromDocShell() {
   mEditingState = EditingState::eOff;
 
   if (mRemovedFromDocShell) return;
 
   mRemovedFromDocShell = true;
@@ -12610,16 +12614,21 @@ already_AddRefed<nsIURI> Document::GetMo
   if (!uri) {
     return nullptr;
   }
 
   return uri.forget();
 }
 
 Promise* Document::GetDocumentReadyForIdle(ErrorResult& aRv) {
+  if (mIsGoingAway) {
+    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+    return nullptr;
+  }
+
   if (!mReadyForIdle) {
     nsIGlobalObject* global = GetScopeObject();
     if (!global) {
       aRv.Throw(NS_ERROR_NOT_AVAILABLE);
       return nullptr;
     }
 
     mReadyForIdle = Promise::Create(global, aRv);
@@ -13810,18 +13819,27 @@ bool Document::ApplyFullscreen(UniquePtr
   aRequest->MayResolvePromise();
   return true;
 }
 
 bool Document::FullscreenEnabled(CallerType aCallerType) {
   return !GetFullscreenError(this, aCallerType);
 }
 
-void Document::SetOrientationPendingPromise(Promise* aPromise) {
+void Document::ClearOrientationPendingPromise() {
+  mOrientationPendingPromise = nullptr;
+}
+
+bool Document::SetOrientationPendingPromise(Promise* aPromise) {
+  if (mIsGoingAway) {
+    return false;
+  }
+
   mOrientationPendingPromise = aPromise;
+  return true;
 }
 
 static void DispatchPointerLockChange(Document* aTarget) {
   if (!aTarget) {
     return;
   }
 
   RefPtr<AsyncEventDispatcher> asyncDispatcher =
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -2258,17 +2258,18 @@ class Document : public nsINode,
     mCurrentOrientationType = aType;
     mCurrentOrientationAngle = aAngle;
   }
 
   uint16_t CurrentOrientationAngle() const { return mCurrentOrientationAngle; }
   OrientationType CurrentOrientationType() const {
     return mCurrentOrientationType;
   }
-  void SetOrientationPendingPromise(Promise* aPromise);
+  void ClearOrientationPendingPromise();
+  bool SetOrientationPendingPromise(Promise* aPromise);
   Promise* GetOrientationPendingPromise() const {
     return mOrientationPendingPromise;
   }
 
   void SetRDMPaneOrientation(OrientationType aType, uint16_t aAngle) {
     if (mInRDMPane) {
       SetCurrentOrientation(aType, aAngle);
     }
--- a/dom/base/ScreenOrientation.cpp
+++ b/dom/base/ScreenOrientation.cpp
@@ -165,46 +165,46 @@ ScreenOrientation::LockOrientationTask::
     // lock orientation, thus we don't need to do anything. Old promise
     // should be been rejected.
     return NS_OK;
   }
 
   if (mDocument->Hidden()) {
     // Active orientation lock is not the document's orientation lock.
     mPromise->MaybeResolveWithUndefined();
-    mDocument->SetOrientationPendingPromise(nullptr);
+    mDocument->ClearOrientationPendingPromise();
     return NS_OK;
   }
 
   if (mOrientationLock == hal::eScreenOrientation_None) {
     mScreenOrientation->UnlockDeviceOrientation();
     mPromise->MaybeResolveWithUndefined();
-    mDocument->SetOrientationPendingPromise(nullptr);
+    mDocument->ClearOrientationPendingPromise();
     return NS_OK;
   }
 
   ErrorResult rv;
   bool result = mScreenOrientation->LockDeviceOrientation(mOrientationLock,
                                                           mIsFullscreen, rv);
   if (NS_WARN_IF(rv.Failed())) {
     return rv.StealNSResult();
   }
 
   if (NS_WARN_IF(!result)) {
     mPromise->MaybeReject(NS_ERROR_UNEXPECTED);
-    mDocument->SetOrientationPendingPromise(nullptr);
+    mDocument->ClearOrientationPendingPromise();
     return NS_OK;
   }
 
   if (OrientationLockContains(mDocument->CurrentOrientationType()) ||
       (mOrientationLock == hal::eScreenOrientation_Default &&
        mDocument->CurrentOrientationAngle() == 0)) {
     // Orientation lock will not cause an orientation change.
     mPromise->MaybeResolveWithUndefined();
-    mDocument->SetOrientationPendingPromise(nullptr);
+    mDocument->ClearOrientationPendingPromise();
   }
 
   return NS_OK;
 }
 
 already_AddRefed<Promise> ScreenOrientation::Lock(
     OrientationLockType aOrientation, ErrorResult& aRv) {
   hal::ScreenOrientation orientation = hal::eScreenOrientation_None;
@@ -251,17 +251,17 @@ already_AddRefed<Promise> ScreenOrientat
 static inline void AbortOrientationPromises(nsIDocShell* aDocShell) {
   MOZ_ASSERT(aDocShell);
 
   Document* doc = aDocShell->GetDocument();
   if (doc) {
     Promise* promise = doc->GetOrientationPendingPromise();
     if (promise) {
       promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
-      doc->SetOrientationPendingPromise(nullptr);
+      doc->ClearOrientationPendingPromise();
     }
   }
 
   int32_t childCount;
   aDocShell->GetInProcessChildCount(&childCount);
   for (int32_t i = 0; i < childCount; i++) {
     nsCOMPtr<nsIDocShellTreeItem> child;
     if (NS_SUCCEEDED(
@@ -320,17 +320,20 @@ already_AddRefed<Promise> ScreenOrientat
   if (!rootShell) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   rootShell->SetOrientationLock(aOrientation);
   AbortOrientationPromises(rootShell);
 
-  doc->SetOrientationPendingPromise(p);
+  if (!doc->SetOrientationPendingPromise(p)) {
+    p->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+    return p.forget();
+  }
 
   nsCOMPtr<nsIRunnable> lockOrientationTask = new LockOrientationTask(
       this, p, aOrientation, doc, perm == FULLSCREEN_LOCK_ALLOWED);
   aRv = NS_DispatchToMainThread(lockOrientationTask);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
@@ -548,17 +551,17 @@ ScreenOrientation::DispatchChangeEventAn
       "dom::ScreenOrientation::DispatchChangeEvent", [self, doc]() {
         DebugOnly<nsresult> rv =
             self->DispatchTrustedEvent(NS_LITERAL_STRING("change"));
         NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "DispatchTrustedEvent failed");
         if (doc) {
           Promise* pendingPromise = doc->GetOrientationPendingPromise();
           if (pendingPromise) {
             pendingPromise->MaybeResolveWithUndefined();
-            doc->SetOrientationPendingPromise(nullptr);
+            doc->ClearOrientationPendingPromise();
           }
         }
       });
 }
 
 JSObject* ScreenOrientation::WrapObject(JSContext* aCx,
                                         JS::Handle<JSObject*> aGivenProto) {
   return ScreenOrientation_Binding::Wrap(aCx, this, aGivenProto);
--- a/dom/base/test/test_bug466080.html
+++ b/dom/base/test/test_bug466080.html
@@ -1,34 +1,35 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <title>Test bug 466080</title>
   <script src="/tests/SimpleTest/SimpleTest.js"></script>        
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
-<body onload="onWindowLoad()">
+<body>
 <iframe id="frame1"
-        src="https://requireclientcert.example.com/tests/dom/base/test/bug466080.sjs"
-        onload="document.iframeWasLoaded = true">
+        src="https://requireclientcert.example.com/tests/dom/base/test/bug466080.sjs">
  
  This iframe should load the resource via the src-attribute from
  a secure server which requires a client-cert. Doing this is
  supposed to work, but further below in the test we try to load
  the resource from the same url using a XHR, which should not work.
  
  TODO : What if we change 'src' from JS? Would/should it load?
 
 </iframe>
 
 <script class="testbody" type="text/javascript">
 
-document.iframeWasLoaded = false;
+"use strict";
 
-var alltests = [
+onWindowLoad();
+
+let alltests = [
 
 // load resource from a relative url - this should work
   { url:"bug466080.sjs",
     status_check:"==200",
     error:"XHR from relative URL"},
 
 // TODO - load the resource from a relative url via https..?
 
@@ -75,21 +76,22 @@ var alltests = [
   { url:"https://requireclientcert.example.com/tests/dom/base/test/bug466080.sjs",
     status_check:"!=200",
     error:"XHR PREFLIGHT from cross-site secure server requiring certificate",
     withCredentials:"true",
     method:"XMETHOD"},
     
 ];
 
-function onWindowLoad() {
+async function onWindowLoad() {
     // First, check that resource was loaded into the iframe
     // This check in fact depends on bug #444165... :)
-    ok(document.iframeWasLoaded, "Loading resource via src-attribute");
-
+    await new Promise(resolve => {
+        document.getElementById("frame1").onload = () => { resolve(); };
+    });
 
     function runTest(test) {
 
         var xhr =  new XMLHttpRequest();
 
         var method = "GET";
         if (test.method != null) { method = test.method; }
         xhr.open(method, test.url);
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -10829,17 +10829,17 @@ class ClassMethod(ClassItem):
         decorators = []
         if self.canRunScript:
             decorators.append('MOZ_CAN_RUN_SCRIPT')
         if self.inline:
             decorators.append('inline')
         if declaring:
             if self.static:
                 decorators.append('static')
-            if self.virtual:
+            if self.virtual and not self.override:
                 decorators.append('virtual')
         if decorators:
             return ' '.join(decorators) + ' '
         return ''
 
     def getBody(self):
         # Override me or pass a string to constructor
         assert self.body is not None
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -2171,27 +2171,27 @@ int MediaManager::AddDeviceChangeCallbac
     MOZ_RELEASE_ASSERT(manager);  // Must exist while media thread is alive
     // this is needed in case persistent permission is given but no gUM()
     // or enumeration() has created the real backend yet
     manager->GetBackend();
     if (fakeDeviceChangeEventOn)
       manager->GetBackend()->SetFakeDeviceChangeEvents();
   }));
 
-  return DeviceChangeCallback::AddDeviceChangeCallback(aCallback);
+  return DeviceChangeNotifier::AddDeviceChangeCallback(aCallback);
 }
 
 void MediaManager::OnDeviceChange() {
   NS_DispatchToMainThread(NS_NewRunnableFunction(
       "MediaManager::OnDeviceChange", [self = RefPtr<MediaManager>(this)]() {
         MOZ_ASSERT(NS_IsMainThread());
         if (sHasShutdown) {
           return;
         }
-        self->DeviceChangeCallback::OnDeviceChange();
+        self->NotifyDeviceChange();
 
         // On some Windows machine, if we call EnumerateRawDevices immediately
         // after receiving devicechange event, sometimes we would get outdated
         // devices list.
         PR_Sleep(PR_MillisecondsToInterval(200));
         auto devices = MakeRefPtr<MediaDeviceSetRefCnt>();
         self->EnumerateRawDevices(
                 0, MediaSourceEnum::Camera, MediaSourceEnum::Microphone,
@@ -3405,17 +3405,17 @@ void MediaManager::RemoveMediaDevicesCal
   MutexAutoLock lock(mCallbackMutex);
   for (DeviceChangeCallback* observer : mDeviceChangeCallbackList) {
     MediaDevices* mediadevices = static_cast<MediaDevices*>(observer);
     MOZ_ASSERT(mediadevices);
     if (mediadevices) {
       nsPIDOMWindowInner* window = mediadevices->GetOwner();
       MOZ_ASSERT(window);
       if (window && window->WindowID() == aWindowID) {
-        DeviceChangeCallback::RemoveDeviceChangeCallbackLocked(observer);
+        RemoveDeviceChangeCallbackLocked(observer);
         return;
       }
     }
   }
 }
 
 void MediaManager::AddWindowID(uint64_t aWindowId,
                                RefPtr<GetUserMediaWindowListener> aListener) {
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -127,16 +127,17 @@ class MediaDevice : public nsIMediaDevic
 };
 
 typedef nsRefPtrHashtable<nsUint64HashKey, GetUserMediaWindowListener>
     WindowTable;
 typedef MozPromise<RefPtr<AudioDeviceInfo>, nsresult, true> SinkInfoPromise;
 
 class MediaManager final : public nsIMediaManagerService,
                            public nsIObserver,
+                           public DeviceChangeNotifier,
                            public DeviceChangeCallback {
   friend SourceListener;
 
  public:
   static already_AddRefed<MediaManager> GetInstance();
 
   // NOTE: never Dispatch(....,NS_DISPATCH_SYNC) to the MediaManager
   // thread from the MainThread, as we NS_DISPATCH_SYNC to MainThread
--- a/dom/media/systemservices/CamerasChild.cpp
+++ b/dom/media/systemservices/CamerasChild.cpp
@@ -47,17 +47,17 @@ class FakeOnDeviceChangeEventRunnable : 
       : Runnable("camera::FakeOnDeviceChangeEventRunnable"),
         mCounter(counter) {}
 
   NS_IMETHOD Run() override {
     OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex());
 
     CamerasChild* child = CamerasSingleton::Child();
     if (child) {
-      child->OnDeviceChange();
+      child->NotifyDeviceChange();
 
       if (mCounter++ < FAKE_ONDEVICECHANGE_EVENT_REPEAT_COUNT) {
         RefPtr<FakeOnDeviceChangeEventRunnable> evt =
             new FakeOnDeviceChangeEventRunnable(mCounter);
         CamerasSingleton::FakeDeviceChangeEventThread()->DelayedDispatch(
             evt.forget(), FAKE_ONDEVICECHANGE_EVENT_PERIOD_IN_MS);
       }
     }
@@ -149,17 +149,17 @@ int CamerasChild::AddDeviceChangeCallbac
 
   // In order to detect the event, we need to init the camera engine.
   // Currently EnsureInitialized(aCapEngine) is only called when one of
   // CamerasaParent api, e.g., RecvNumberOfCaptureDevices(), is called.
 
   // So here we setup camera engine via EnsureInitialized(aCapEngine)
 
   EnsureInitialized(CameraEngine);
-  return DeviceChangeCallback::AddDeviceChangeCallback(aCallback);
+  return DeviceChangeNotifier::AddDeviceChangeCallback(aCallback);
 }
 
 mozilla::ipc::IPCResult CamerasChild::RecvReplyFailure(void) {
   LOG(("%s", __PRETTY_FUNCTION__));
   MonitorAutoLock monitor(mReplyMonitor);
   mReceivedReply = true;
   mReplySuccess = false;
   monitor.Notify();
@@ -575,17 +575,17 @@ mozilla::ipc::IPCResult CamerasChild::Re
   } else {
     LOG(("DeliverFrame called with dead callback"));
   }
   SendReleaseFrame(std::move(shmem));
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult CamerasChild::RecvDeviceChange() {
-  this->OnDeviceChange();
+  this->NotifyDeviceChange();
   return IPC_OK();
 }
 
 int CamerasChild::SetFakeDeviceChangeEvents() {
   CamerasSingleton::Mutex().AssertCurrentThreadOwns();
 
   if (!CamerasSingleton::FakeDeviceChangeEventThread()) {
     nsresult rv = NS_NewNamedThread(
--- a/dom/media/systemservices/CamerasChild.h
+++ b/dom/media/systemservices/CamerasChild.h
@@ -138,17 +138,17 @@ int GetChildAndCall(MEM_FUN&& f, ARGS&&.
   CamerasChild* child = GetCamerasChild();
   if (child) {
     return (child->*f)(std::forward<ARGS>(args)...);
   } else {
     return -1;
   }
 }
 
-class CamerasChild final : public PCamerasChild, public DeviceChangeCallback {
+class CamerasChild final : public PCamerasChild, public DeviceChangeNotifier {
   friend class mozilla::ipc::BackgroundChildImpl;
   template <class T>
   friend class mozilla::camera::LockAndDispatch;
 
  public:
   // We are owned by the PBackground thread only. CamerasSingleton
   // takes a non-owning reference.
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CamerasChild)
--- a/dom/media/systemservices/DeviceChangeCallback.h
+++ b/dom/media/systemservices/DeviceChangeCallback.h
@@ -3,55 +3,62 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_DeviceChangeCallback_h
 #define mozilla_DeviceChangeCallback_h
 
 #include "mozilla/Mutex.h"
+#include "nsTArray.h"
 
 namespace mozilla {
 
 class DeviceChangeCallback {
  public:
-  virtual void OnDeviceChange() {
+  virtual ~DeviceChangeCallback() = default;
+  virtual void OnDeviceChange() = 0;
+};
+
+class DeviceChangeNotifier {
+ public:
+  DeviceChangeNotifier()
+      : mCallbackMutex("mozilla::DeviceChangeCallback::mCallbackMutex") {
+  }
+
+  void NotifyDeviceChange() {
     MutexAutoLock lock(mCallbackMutex);
     for (DeviceChangeCallback* observer : mDeviceChangeCallbackList) {
       observer->OnDeviceChange();
     }
   }
 
   virtual int AddDeviceChangeCallback(DeviceChangeCallback* aCallback) {
     MutexAutoLock lock(mCallbackMutex);
     if (mDeviceChangeCallbackList.IndexOf(aCallback) ==
         mDeviceChangeCallbackList.NoIndex)
       mDeviceChangeCallbackList.AppendElement(aCallback);
     return 0;
   }
 
-  virtual int RemoveDeviceChangeCallback(DeviceChangeCallback* aCallback) {
+  int RemoveDeviceChangeCallback(DeviceChangeCallback* aCallback) {
     MutexAutoLock lock(mCallbackMutex);
     return RemoveDeviceChangeCallbackLocked(aCallback);
   }
 
-  virtual int RemoveDeviceChangeCallbackLocked(
+  int RemoveDeviceChangeCallbackLocked(
       DeviceChangeCallback* aCallback) {
     mCallbackMutex.AssertCurrentThreadOwns();
     if (mDeviceChangeCallbackList.IndexOf(aCallback) !=
         mDeviceChangeCallbackList.NoIndex)
       mDeviceChangeCallbackList.RemoveElement(aCallback);
     return 0;
   }
 
-  DeviceChangeCallback()
-      : mCallbackMutex("mozilla::media::DeviceChangeCallback::mCallbackMutex") {
-  }
-
-  virtual ~DeviceChangeCallback() {}
+  virtual ~DeviceChangeNotifier() = default;
 
  protected:
   nsTArray<DeviceChangeCallback*> mDeviceChangeCallbackList;
   Mutex mCallbackMutex;
 };
 
 }  // namespace mozilla
 
--- a/dom/media/webrtc/CubebDeviceEnumerator.cpp
+++ b/dom/media/webrtc/CubebDeviceEnumerator.cpp
@@ -1,32 +1,31 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "CubebDeviceEnumerator.h"
 #include "mozilla/StaticPtr.h"
+#include "nsThreadUtils.h"
 
 namespace mozilla {
 
 using namespace CubebUtils;
 
 /* static */
 StaticRefPtr<CubebDeviceEnumerator> CubebDeviceEnumerator::sInstance;
 
 /* static */
-already_AddRefed<CubebDeviceEnumerator> CubebDeviceEnumerator::GetInstance() {
+CubebDeviceEnumerator* CubebDeviceEnumerator::GetInstance() {
   if (!sInstance) {
     sInstance = new CubebDeviceEnumerator();
   }
-  RefPtr<CubebDeviceEnumerator> instance = sInstance.get();
-  MOZ_ASSERT(instance);
-  return instance.forget();
+  return sInstance.get();
 }
 
 CubebDeviceEnumerator::CubebDeviceEnumerator()
     : mMutex("CubebDeviceListMutex"),
       mManualInputInvalidation(false),
       mManualOutputInvalidation(false) {
   int rv = cubeb_register_device_collection_changed(
       GetCubebContext(), CUBEB_DEVICE_TYPE_OUTPUT,
@@ -300,19 +299,26 @@ void CubebDeviceEnumerator::InputAudioDe
 
 void CubebDeviceEnumerator::OutputAudioDeviceListChanged_s(cubeb* aContext,
                                                            void* aUser) {
   CubebDeviceEnumerator* self = reinterpret_cast<CubebDeviceEnumerator*>(aUser);
   self->AudioDeviceListChanged(CubebDeviceEnumerator::Side::OUTPUT);
 }
 
 void CubebDeviceEnumerator::AudioDeviceListChanged(Side aSide) {
-  MutexAutoLock lock(mMutex);
+  {
+    MutexAutoLock lock(mMutex);
+    if (aSide == Side::INPUT) {
+      mInputDevices.Clear();
+    } else {
+      MOZ_ASSERT(aSide == Side::OUTPUT);
+      mOutputDevices.Clear();
+    }
+  }
 
-  if (aSide == Side::INPUT) {
-    mInputDevices.Clear();
-  } else {
-    MOZ_ASSERT(aSide == Side::OUTPUT);
-    mOutputDevices.Clear();
-  }
+  NS_DispatchToMainThread(NS_NewRunnableFunction(
+      "CubebDeviceEnumerator::AudioDeviceListChanged",
+      [self = RefPtr<CubebDeviceEnumerator>(this)]() {
+        self->NotifyDeviceChange();
+      }));
 }
 
 }  // namespace mozilla
--- a/dom/media/webrtc/CubebDeviceEnumerator.h
+++ b/dom/media/webrtc/CubebDeviceEnumerator.h
@@ -3,27 +3,28 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef CUBEBDEVICEENUMERATOR_H_
 #define CUBEBDEVICEENUMERATOR_H_
 
 #include "AudioDeviceInfo.h"
 #include "CubebUtils.h"
 #include "cubeb/cubeb.h"
+#include "mozilla/media/DeviceChangeCallback.h"
 #include "mozilla/Mutex.h"
 #include "nsTArray.h"
 
 namespace mozilla {
 // This class implements a cache for accessing the audio device list.
 // It can be accessed on any thread.
-class CubebDeviceEnumerator final {
+class CubebDeviceEnumerator final : public DeviceChangeNotifier {
  public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CubebDeviceEnumerator)
 
-  static already_AddRefed<CubebDeviceEnumerator> GetInstance();
+  static CubebDeviceEnumerator* GetInstance();
   static void Shutdown();
   // This method returns a list of all the input audio devices
   // (sources) available on this machine.
   // This method is safe to call from all threads.
   void EnumerateAudioInputDevices(
       nsTArray<RefPtr<AudioDeviceInfo>>& aOutDevices);
   // Similar for the audio audio devices (sinks). Also thread safe.
   void EnumerateAudioOutputDevices(
--- a/dom/media/webrtc/MediaEngine.h
+++ b/dom/media/webrtc/MediaEngine.h
@@ -25,17 +25,18 @@ class MediaEngineSource;
 
 enum MediaSinkEnum {
   Speaker,
   Other,
 };
 
 enum { kVideoTrack = 1, kAudioTrack = 2, kTrackCount };
 
-class MediaEngine : public DeviceChangeCallback {
+class MediaEngine : public DeviceChangeNotifier,
+                    public DeviceChangeCallback {
  public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaEngine)
   NS_DECL_OWNINGTHREAD
 
   void AssertIsOnOwningThread() const { NS_ASSERT_OWNINGTHREAD(MediaEngine); }
 
   /**
    * Populate an array of sources of the requested type in the nsTArray.
@@ -44,15 +45,17 @@ class MediaEngine : public DeviceChangeC
   virtual void EnumerateDevices(uint64_t aWindowId, dom::MediaSourceEnum,
                                 MediaSinkEnum,
                                 nsTArray<RefPtr<MediaDevice>>*) = 0;
 
   virtual void Shutdown() = 0;
 
   virtual void SetFakeDeviceChangeEvents() {}
 
+  void OnDeviceChange() override { NotifyDeviceChange(); }
+
  protected:
   virtual ~MediaEngine() = default;
 };
 
 }  // namespace mozilla
 
 #endif /* MEDIAENGINE_H_ */
--- a/dom/media/webrtc/MediaEngineWebRTC.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTC.cpp
@@ -22,29 +22,34 @@
 #include "prenv.h"
 
 static mozilla::LazyLogModule sGetUserMediaLog("GetUserMedia");
 #undef LOG
 #define LOG(args) MOZ_LOG(sGetUserMediaLog, mozilla::LogLevel::Debug, args)
 
 namespace mozilla {
 
+CubebDeviceEnumerator* GetEnumerator() {
+  return CubebDeviceEnumerator::GetInstance();
+}
+
 MediaEngineWebRTC::MediaEngineWebRTC(MediaEnginePrefs& aPrefs)
     : mMutex("mozilla::MediaEngineWebRTC"),
       mDelayAgnostic(aPrefs.mDelayAgnostic),
       mExtendedFilter(aPrefs.mExtendedFilter),
       mHasTabVideoSource(false) {
   nsCOMPtr<nsIComponentRegistrar> compMgr;
   NS_GetComponentRegistrar(getter_AddRefs(compMgr));
   if (compMgr) {
     compMgr->IsContractIDRegistered(NS_TABSOURCESERVICE_CONTRACTID,
                                     &mHasTabVideoSource);
   }
 
   camera::GetChildAndCall(&camera::CamerasChild::AddDeviceChangeCallback, this);
+  GetEnumerator()->AddDeviceChangeCallback(this);
 }
 
 void MediaEngineWebRTC::SetFakeDeviceChangeEvents() {
   camera::GetChildAndCall(&camera::CamerasChild::SetFakeDeviceChangeEvents);
 }
 
 void MediaEngineWebRTC::EnumerateVideoDevices(
     uint64_t aWindowId, camera::CaptureEngine aCapEngine,
@@ -144,20 +149,18 @@ void MediaEngineWebRTC::EnumerateVideoDe
         tabVideoSource->GetGroupId(), NS_LITERAL_STRING("")));
   }
 }
 
 void MediaEngineWebRTC::EnumerateMicrophoneDevices(
     uint64_t aWindowId, nsTArray<RefPtr<MediaDevice>>* aDevices) {
   mMutex.AssertCurrentThreadOwns();
 
-  mEnumerator = CubebDeviceEnumerator::GetInstance();
-
   nsTArray<RefPtr<AudioDeviceInfo>> devices;
-  mEnumerator->EnumerateAudioInputDevices(devices);
+  GetEnumerator()->EnumerateAudioInputDevices(devices);
 
   DebugOnly<bool> foundPreferredDevice = false;
 
   for (uint32_t i = 0; i < devices.Length(); i++) {
 #ifndef ANDROID
     MOZ_ASSERT(devices[i]->DeviceID());
 #endif
     LOG(("Cubeb device %u: type 0x%x, state 0x%x, name %s, id %p", i,
@@ -195,21 +198,18 @@ void MediaEngineWebRTC::EnumerateMicroph
         aDevices->AppendElement(device);
       }
     }
   }
 }
 
 void MediaEngineWebRTC::EnumerateSpeakerDevices(
     uint64_t aWindowId, nsTArray<RefPtr<MediaDevice>>* aDevices) {
-  if (!mEnumerator) {
-    mEnumerator = CubebDeviceEnumerator::GetInstance();
-  }
   nsTArray<RefPtr<AudioDeviceInfo>> devices;
-  mEnumerator->EnumerateAudioOutputDevices(devices);
+  GetEnumerator()->EnumerateAudioOutputDevices(devices);
 
 #ifndef XP_WIN
   DebugOnly<bool> preferredDeviceFound = false;
 #endif
   for (auto& device : devices) {
     if (device->State() == CUBEB_DEVICE_STATE_ENABLED) {
       MOZ_ASSERT(device->Type() == CUBEB_DEVICE_TYPE_OUTPUT);
       nsString uuid(device->Name());
@@ -265,32 +265,31 @@ void MediaEngineWebRTC::EnumerateDevices
   } else if (aMediaSource == dom::MediaSourceEnum::AudioCapture) {
     RefPtr<MediaEngineWebRTCAudioCaptureSource> audioCaptureSource =
         new MediaEngineWebRTCAudioCaptureSource(nullptr);
     aDevices->AppendElement(MakeRefPtr<MediaDevice>(
         audioCaptureSource, audioCaptureSource->GetName(),
         NS_ConvertUTF8toUTF16(audioCaptureSource->GetUUID()),
         audioCaptureSource->GetGroupId(), NS_LITERAL_STRING("")));
   } else if (aMediaSource == dom::MediaSourceEnum::Microphone) {
-    MOZ_ASSERT(aMediaSource == dom::MediaSourceEnum::Microphone);
     EnumerateMicrophoneDevices(aWindowId, aDevices);
   }
 
   if (aMediaSink == MediaSinkEnum::Speaker) {
     EnumerateSpeakerDevices(aWindowId, aDevices);
   }
 }
 
 void MediaEngineWebRTC::Shutdown() {
   // This is likely paranoia
   MutexAutoLock lock(mMutex);
 
   if (camera::GetCamerasChildIfExists()) {
     camera::GetChildAndCall(&camera::CamerasChild::RemoveDeviceChangeCallback,
                             this);
   }
+  GetEnumerator()->RemoveDeviceChangeCallback(this);
 
   LOG(("%s", __FUNCTION__));
-  mEnumerator = nullptr;
   mozilla::camera::Shutdown();
 }
 
 }  // namespace mozilla
--- a/dom/media/webrtc/MediaEngineWebRTC.h
+++ b/dom/media/webrtc/MediaEngineWebRTC.h
@@ -72,17 +72,16 @@ class MediaEngineWebRTC : public MediaEn
                              nsTArray<RefPtr<MediaDevice>>*);
   void EnumerateMicrophoneDevices(uint64_t aWindowId,
                                   nsTArray<RefPtr<MediaDevice>>*);
   void EnumerateSpeakerDevices(uint64_t aWindowId,
                                nsTArray<RefPtr<MediaDevice>>*);
 
   // gUM runnables can e.g. Enumerate from multiple threads
   Mutex mMutex;
-  RefPtr<mozilla::CubebDeviceEnumerator> mEnumerator;
   const bool mDelayAgnostic;
   const bool mExtendedFilter;
   // This also is set in the ctor and then never changed, but we can't make it
   // const because we pass it to a function that takes bool* in the ctor.
   bool mHasTabVideoSource;
 };
 
 }  // namespace mozilla
--- a/dom/svg/DOMSVGLengthList.cpp
+++ b/dom/svg/DOMSVGLengthList.cpp
@@ -176,20 +176,16 @@ already_AddRefed<DOMSVGLength> DOMSVGLen
   // insert a clone of newItem, and for consistency, this should happen even if
   // *this* is the list that newItem is currently in. Note that in the case of
   // newItem being in this list, the Clear() call before the InsertItemBefore()
   // call would remove it from this list, and so the InsertItemBefore() call
   // would not insert a clone of newItem, it would actually insert newItem. To
   // prevent that from happening we have to do the clone here, if necessary.
 
   RefPtr<DOMSVGLength> domItem = &newItem;
-  if (!domItem) {
-    error.Throw(NS_ERROR_DOM_SVG_WRONG_TYPE_ERR);
-    return nullptr;
-  }
   if (domItem->HasOwner() || domItem->IsReflectingAttribute()) {
     domItem = domItem->Copy();
   }
 
   ErrorResult rv;
   Clear(rv);
   MOZ_ASSERT(!rv.Failed());
   return InsertItemBefore(*domItem, 0, error);
@@ -226,20 +222,16 @@ already_AddRefed<DOMSVGLength> DOMSVGLen
 
   index = std::min(index, LengthNoFlush());
   if (index >= DOMSVGLength::MaxListIndex()) {
     error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return nullptr;
   }
 
   RefPtr<DOMSVGLength> domItem = &newItem;
-  if (!domItem) {
-    error.Throw(NS_ERROR_DOM_SVG_WRONG_TYPE_ERR);
-    return nullptr;
-  }
   if (domItem->HasOwner() || domItem->IsReflectingAttribute()) {
     domItem = domItem->Copy();  // must do this before changing anything!
   }
 
   // Ensure we have enough memory so we can avoid complex error handling below:
   if (!mItems.SetCapacity(mItems.Length() + 1, fallible) ||
       !InternalList().SetCapacity(InternalList().Length() + 1)) {
     error.Throw(NS_ERROR_OUT_OF_MEMORY);
@@ -272,25 +264,22 @@ already_AddRefed<DOMSVGLength> DOMSVGLen
 
 already_AddRefed<DOMSVGLength> DOMSVGLengthList::ReplaceItem(
     DOMSVGLength& newItem, uint32_t index, ErrorResult& error) {
   if (IsAnimValList()) {
     error.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
     return nullptr;
   }
 
-  RefPtr<DOMSVGLength> domItem = &newItem;
-  if (!domItem) {
-    error.Throw(NS_ERROR_DOM_SVG_WRONG_TYPE_ERR);
-    return nullptr;
-  }
   if (index >= LengthNoFlush()) {
     error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return nullptr;
   }
+
+  RefPtr<DOMSVGLength> domItem = &newItem;
   if (domItem->HasOwner() || domItem->IsReflectingAttribute()) {
     domItem = domItem->Copy();  // must do this before changing anything!
   }
 
   AutoChangeLengthListNotifier notifier(this);
   if (mItems[index]) {
     // Notify any existing DOM item of removal *before* modifying the lists so
     // that the DOM item can copy the *old* value at its index:
--- a/dom/xbl/nsXBLMaybeCompiled.h
+++ b/dom/xbl/nsXBLMaybeCompiled.h
@@ -74,27 +74,27 @@ class nsXBLMaybeCompiled {
 };
 
 namespace js {
 
 template <class UncompiledT>
 struct BarrierMethods<nsXBLMaybeCompiled<UncompiledT>> {
   typedef struct BarrierMethods<JSObject*> Base;
 
-  static void writeBarriers(nsXBLMaybeCompiled<UncompiledT>* functionp,
-                            nsXBLMaybeCompiled<UncompiledT> prev,
-                            nsXBLMaybeCompiled<UncompiledT> next) {
+  static void postWriteBarrier(nsXBLMaybeCompiled<UncompiledT>* functionp,
+                               nsXBLMaybeCompiled<UncompiledT> prev,
+                               nsXBLMaybeCompiled<UncompiledT> next) {
     if (next.IsCompiled()) {
-      Base::writeBarriers(
+      Base::postWriteBarrier(
           &functionp->UnsafeGetJSFunction(),
           prev.IsCompiled() ? prev.UnsafeGetJSFunction() : nullptr,
           next.UnsafeGetJSFunction());
     } else if (prev.IsCompiled()) {
-      Base::writeBarriers(&prev.UnsafeGetJSFunction(),
-                          prev.UnsafeGetJSFunction(), nullptr);
+      Base::postWriteBarrier(&prev.UnsafeGetJSFunction(),
+                             prev.UnsafeGetJSFunction(), nullptr);
     }
   }
   static void exposeToJS(nsXBLMaybeCompiled<UncompiledT> fun) {
     if (fun.IsCompiled()) {
       JS::ExposeObjectToActiveJS(fun.UnsafeGetJSFunction());
     }
   }
 };
--- a/gfx/2d/2D.h
+++ b/gfx/2d/2D.h
@@ -725,35 +725,16 @@ static inline bool operator==(const Glyp
  * as roc suggested. But for now it's a simple container for a glyph vector.
  */
 struct GlyphBuffer {
   const Glyph*
       mGlyphs;  //!< A pointer to a buffer of glyphs. Managed by the caller.
   uint32_t mNumGlyphs;  //!< Number of glyphs mGlyphs points to.
 };
 
-struct GlyphMetrics {
-  // Horizontal distance from the origin to the leftmost side of the bounding
-  // box of the drawn glyph. This can be negative!
-  Float mXBearing;
-  // Horizontal distance from the origin of this glyph to the origin of the
-  // next glyph.
-  Float mXAdvance;
-  // Vertical distance from the origin to the topmost side of the bounding box
-  // of the drawn glyph.
-  Float mYBearing;
-  // Vertical distance from the origin of this glyph to the origin of the next
-  // glyph, this is used when drawing vertically and will typically be 0.
-  Float mYAdvance;
-  // Width of the glyph's black box.
-  Float mWidth;
-  // Height of the glyph's black box.
-  Float mHeight;
-};
-
 #ifdef MOZ_ENABLE_FREETYPE
 class SharedFTFace;
 
 /** SharedFTFaceData abstracts data that may be used to back a SharedFTFace.
  * Its main function is to manage the lifetime of the data and ensure that it
  * lasts as long as the face.
  */
 class SharedFTFaceData {
@@ -905,22 +886,16 @@ class ScaledFont : public SupportsThread
    * API rather than a generic API to append paths because it allows easier
    * implementation in some backends, and more efficient implementation in
    * others.
    */
   virtual void CopyGlyphsToBuilder(const GlyphBuffer& aBuffer,
                                    PathBuilder* aBuilder,
                                    const Matrix* aTransformHint = nullptr) = 0;
 
-  /* This gets the metrics of a set of glyphs for the current font face.
-   */
-  virtual void GetGlyphDesignMetrics(const uint16_t* aGlyphIndices,
-                                     uint32_t aNumGlyphs,
-                                     GlyphMetrics* aGlyphMetrics) = 0;
-
   typedef void (*FontInstanceDataOutput)(const uint8_t* aData, uint32_t aLength,
                                          const FontVariation* aVariations,
                                          uint32_t aNumVariations, void* aBaton);
 
   virtual bool GetFontInstanceData(FontInstanceDataOutput, void*) {
     return false;
   }
 
@@ -1487,35 +1462,16 @@ class DrawTarget : public external::Atom
    * inputs.
    *
    * @param aType Type of filter node to be created.
    */
   virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) = 0;
 
   Matrix GetTransform() const { return mTransform; }
 
-  /*
-   * Get the metrics of a glyph, including any additional spacing that is taken
-   * during rasterization to this backends (for example because of antialiasing
-   * filters.
-   *
-   * aScaledFont The scaled font used when drawing.
-   * aGlyphIndices An array of indices for the glyphs whose the metrics are
-   *               wanted
-   * aNumGlyphs The amount of elements in aGlyphIndices
-   * aGlyphMetrics The glyph metrics
-   */
-  virtual void GetGlyphRasterizationMetrics(ScaledFont* aScaledFont,
-                                            const uint16_t* aGlyphIndices,
-                                            uint32_t aNumGlyphs,
-                                            GlyphMetrics* aGlyphMetrics) {
-    aScaledFont->GetGlyphDesignMetrics(aGlyphIndices, aNumGlyphs,
-                                       aGlyphMetrics);
-  }
-
   /**
    * Set a transform on the surface, this transform is applied at drawing time
    * to both the mask and source of the operation.
    *
    * Performance note: For some backends it is expensive to change the current
    * transform (because transforms affect a lot of the parts of the pipeline,
    * so new transform change can result in a pipeline flush).  To get around
    * this, DrawTarget implementations buffer transform changes and try to only
--- a/gfx/2d/DrawTargetCairo.cpp
+++ b/gfx/2d/DrawTargetCairo.cpp
@@ -1574,42 +1574,16 @@ already_AddRefed<GradientStops> DrawTarg
     GradientStop* aStops, uint32_t aNumStops, ExtendMode aExtendMode) const {
   return MakeAndAddRef<GradientStopsCairo>(aStops, aNumStops, aExtendMode);
 }
 
 already_AddRefed<FilterNode> DrawTargetCairo::CreateFilter(FilterType aType) {
   return FilterNodeSoftware::Create(aType);
 }
 
-void DrawTargetCairo::GetGlyphRasterizationMetrics(
-    ScaledFont* aScaledFont, const uint16_t* aGlyphIndices, uint32_t aNumGlyphs,
-    GlyphMetrics* aGlyphMetrics) {
-  cairo_scaled_font_t* cairoFont = aScaledFont->GetCairoScaledFont();
-  if (!cairoFont ||
-      cairo_scaled_font_status(cairoFont) != CAIRO_STATUS_SUCCESS) {
-    return;
-  }
-  cairo_set_scaled_font(mContext, cairoFont);
-  for (uint32_t i = 0; i < aNumGlyphs; i++) {
-    cairo_glyph_t glyph;
-    cairo_text_extents_t extents;
-    glyph.index = aGlyphIndices[i];
-    glyph.x = 0;
-    glyph.y = 0;
-    cairo_glyph_extents(mContext, &glyph, 1, &extents);
-
-    aGlyphMetrics[i].mXBearing = extents.x_bearing;
-    aGlyphMetrics[i].mXAdvance = extents.x_advance;
-    aGlyphMetrics[i].mYBearing = extents.y_bearing;
-    aGlyphMetrics[i].mYAdvance = extents.y_advance;
-    aGlyphMetrics[i].mWidth = extents.width;
-    aGlyphMetrics[i].mHeight = extents.height;
-  }
-}
-
 already_AddRefed<SourceSurface> DrawTargetCairo::CreateSourceSurfaceFromData(
     unsigned char* aData, const IntSize& aSize, int32_t aStride,
     SurfaceFormat aFormat) const {
   if (!aData) {
     gfxWarning() << "DrawTargetCairo::CreateSourceSurfaceFromData null aData";
     return nullptr;
   }
 
--- a/gfx/2d/DrawTargetCairo.h
+++ b/gfx/2d/DrawTargetCairo.h
@@ -147,20 +147,16 @@ class DrawTargetCairo final : public Dra
       const Rect& aBounds, SurfaceFormat aFormat) override;
 
   virtual already_AddRefed<GradientStops> CreateGradientStops(
       GradientStop* aStops, uint32_t aNumStops,
       ExtendMode aExtendMode = ExtendMode::CLAMP) const override;
 
   virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) override;
 
-  virtual void GetGlyphRasterizationMetrics(
-      ScaledFont* aScaledFont, const uint16_t* aGlyphIndices,
-      uint32_t aNumGlyphs, GlyphMetrics* aGlyphMetrics) override;
-
   virtual void* GetNativeSurface(NativeSurfaceType aType) override;
 
   bool Init(cairo_surface_t* aSurface, const IntSize& aSize,
             SurfaceFormat* aFormat = nullptr);
   bool Init(const IntSize& aSize, SurfaceFormat aFormat);
   bool Init(unsigned char* aData, const IntSize& aSize, int32_t aStride,
             SurfaceFormat aFormat);
 
--- a/gfx/2d/DrawTargetD2D1.cpp
+++ b/gfx/2d/DrawTargetD2D1.cpp
@@ -126,17 +126,18 @@ bool DrawTargetD2D1::EnsureLuminanceEffe
   HRESULT hr = mDC->CreateEffect(CLSID_D2D1ColorMatrix,
                                  getter_AddRefs(mLuminanceEffect));
   if (FAILED(hr)) {
     gfxCriticalError() << "Failed to create luminance effect. Code: "
                        << hexa(hr);
     return false;
   }
 
-  mLuminanceEffect->SetValue(D2D1_COLORMATRIX_PROP_COLOR_MATRIX, kLuminanceMatrix);
+  mLuminanceEffect->SetValue(D2D1_COLORMATRIX_PROP_COLOR_MATRIX,
+                             kLuminanceMatrix);
   mLuminanceEffect->SetValue(D2D1_COLORMATRIX_PROP_ALPHA_MODE,
                              D2D1_COLORMATRIX_ALPHA_MODE_STRAIGHT);
   return true;
 }
 
 already_AddRefed<SourceSurface> DrawTargetD2D1::IntoLuminanceSource(
     LuminanceType aLuminanceType, float aOpacity) {
   if (!EnsureInitialized()) {
@@ -1203,34 +1204,16 @@ already_AddRefed<GradientStops> DrawTarg
 
 already_AddRefed<FilterNode> DrawTargetD2D1::CreateFilter(FilterType aType) {
   if (!EnsureInitialized()) {
     return nullptr;
   }
   return FilterNodeD2D1::Create(mDC, aType);
 }
 
-void DrawTargetD2D1::GetGlyphRasterizationMetrics(ScaledFont* aScaledFont,
-                                                  const uint16_t* aGlyphIndices,
-                                                  uint32_t aNumGlyphs,
-                                                  GlyphMetrics* aGlyphMetrics) {
-  MOZ_ASSERT(aScaledFont->GetType() == FontType::DWRITE);
-
-  aScaledFont->GetGlyphDesignMetrics(aGlyphIndices, aNumGlyphs, aGlyphMetrics);
-
-  // GetDesignGlyphMetrics returns 'ideal' glyph metrics, we need to pad to
-  // account for antialiasing.
-  for (uint32_t i = 0; i < aNumGlyphs; i++) {
-    if (aGlyphMetrics[i].mWidth > 0 && aGlyphMetrics[i].mHeight > 0) {
-      aGlyphMetrics[i].mWidth += 2.0f;
-      aGlyphMetrics[i].mXBearing -= 1.0f;
-    }
-  }
-}
-
 bool DrawTargetD2D1::Init(ID3D11Texture2D* aTexture, SurfaceFormat aFormat) {
   RefPtr<ID2D1Device> device = Factory::GetD2D1Device(&mDeviceSeq);
   if (!device) {
     gfxCriticalNote << "[D2D1.1] Failed to obtain a device for "
                        "DrawTargetD2D1::Init(ID3D11Texture2D*, SurfaceFormat).";
     return false;
   }
 
--- a/gfx/2d/DrawTargetD2D1.h
+++ b/gfx/2d/DrawTargetD2D1.h
@@ -134,20 +134,16 @@ class DrawTargetD2D1 : public DrawTarget
   }
 
   virtual void* GetNativeSurface(NativeSurfaceType aType) override {
     return nullptr;
   }
 
   virtual void DetachAllSnapshots() override { MarkChanged(); }
 
-  virtual void GetGlyphRasterizationMetrics(
-      ScaledFont* aScaledFont, const uint16_t* aGlyphIndices,
-      uint32_t aNumGlyphs, GlyphMetrics* aGlyphMetrics) override;
-
   bool Init(const IntSize& aSize, SurfaceFormat aFormat);
   bool Init(ID3D11Texture2D* aTexture, SurfaceFormat aFormat);
   uint32_t GetByteSize() const;
 
   // This function will get an image for a surface, it may adjust the source
   // transform for any transformation of the resulting image relative to the
   // oritingal SourceSurface. By default, the surface and its transform are
   // interpreted in user-space, but may be specified in device-space instead.
--- a/gfx/2d/ScaledFontBase.cpp
+++ b/gfx/2d/ScaledFontBase.cpp
@@ -232,68 +232,16 @@ void ScaledFontBase::CopyGlyphsToBuilder
     RefPtr<Path> path = MakeAndAddRef<PathSkia>(skPath, FillRule::FILL_WINDING);
     path->StreamToSink(aBuilder);
     return;
   }
 #endif
   MOZ_ASSERT(false, "Path not being copied");
 }
 
-void ScaledFontBase::GetGlyphDesignMetrics(const uint16_t* aGlyphs,
-                                           uint32_t aNumGlyphs,
-                                           GlyphMetrics* aGlyphMetrics) {
-#ifdef USE_CAIRO_SCALED_FONT
-  if (mScaledFont) {
-    for (uint32_t i = 0; i < aNumGlyphs; i++) {
-      cairo_glyph_t glyph;
-      cairo_text_extents_t extents;
-      glyph.index = aGlyphs[i];
-      glyph.x = 0;
-      glyph.y = 0;
-
-      cairo_scaled_font_glyph_extents(mScaledFont, &glyph, 1, &extents);
-
-      aGlyphMetrics[i].mXBearing = extents.x_bearing;
-      aGlyphMetrics[i].mXAdvance = extents.x_advance;
-      aGlyphMetrics[i].mYBearing = extents.y_bearing;
-      aGlyphMetrics[i].mYAdvance = extents.y_advance;
-      aGlyphMetrics[i].mWidth = extents.width;
-      aGlyphMetrics[i].mHeight = extents.height;
-
-      cairo_font_options_t* options = cairo_font_options_create();
-      cairo_scaled_font_get_font_options(mScaledFont, options);
-
-      if (cairo_font_options_get_antialias(options) != CAIRO_ANTIALIAS_NONE) {
-        if (cairo_scaled_font_get_type(mScaledFont) == CAIRO_FONT_TYPE_WIN32) {
-          if (aGlyphMetrics[i].mWidth > 0 && aGlyphMetrics[i].mHeight > 0) {
-            aGlyphMetrics[i].mWidth -= 3.0f;
-            aGlyphMetrics[i].mXBearing += 1.0f;
-          }
-        }
-#  if defined(MOZ2D_HAS_MOZ_CAIRO) && defined(CAIRO_HAS_DWRITE_FONT)
-        else if (cairo_scaled_font_get_type(mScaledFont) ==
-                 CAIRO_FONT_TYPE_DWRITE) {
-          if (aGlyphMetrics[i].mWidth > 0 && aGlyphMetrics[i].mHeight > 0) {
-            aGlyphMetrics[i].mWidth -= 2.0f;
-            aGlyphMetrics[i].mXBearing += 1.0f;
-          }
-        }
-#  endif
-      }
-      cairo_font_options_destroy(options);
-    }
-    return;
-  }
-#endif
-
-  // Don't know how to get the glyph metrics...
-  MOZ_CRASH(
-      "The specific backend type is not supported for GetGlyphDesignMetrics.");
-}
-
 #ifdef USE_CAIRO_SCALED_FONT
 void ScaledFontBase::SetCairoScaledFont(cairo_scaled_font_t* font) {
   MOZ_ASSERT(!mScaledFont);
 
   if (font == mScaledFont) return;
 
   if (mScaledFont) cairo_scaled_font_destroy(mScaledFont);
 
--- a/gfx/2d/ScaledFontBase.h
+++ b/gfx/2d/ScaledFontBase.h
@@ -35,20 +35,16 @@ class ScaledFontBase : public ScaledFont
 
   virtual already_AddRefed<Path> GetPathForGlyphs(
       const GlyphBuffer& aBuffer, const DrawTarget* aTarget) override;
 
   virtual void CopyGlyphsToBuilder(const GlyphBuffer& aBuffer,
                                    PathBuilder* aBuilder,
                                    const Matrix* aTransformHint) override;
 
-  virtual void GetGlyphDesignMetrics(const uint16_t* aGlyphIndices,
-                                     uint32_t aNumGlyphs,
-                                     GlyphMetrics* aGlyphMetrics) override;
-
   virtual Float GetSize() const override { return mSize; }
 
 #ifdef USE_SKIA
   SkTypeface* GetSkTypeface();
   virtual void SetupSkFontDrawOptions(SkFont& aFont) {}
 #endif
 
 #ifdef USE_CAIRO_SCALED_FONT
--- a/gfx/2d/ScaledFontDWrite.cpp
+++ b/gfx/2d/ScaledFontDWrite.cpp
@@ -218,44 +218,16 @@ void ScaledFontDWrite::CopyGlyphsToBuild
   if (pathBuilderD2D->IsFigureActive()) {
     gfxCriticalNote
         << "Attempting to copy glyphs to PathBuilderD2D with active figure.";
   }
 
   CopyGlyphsToSink(aBuffer, pathBuilderD2D->GetSink());
 }
 
-void ScaledFontDWrite::GetGlyphDesignMetrics(const uint16_t* aGlyphs,
-                                             uint32_t aNumGlyphs,
-                                             GlyphMetrics* aGlyphMetrics) {
-  DWRITE_FONT_METRICS fontMetrics;
-  mFontFace->GetMetrics(&fontMetrics);
-
-  std::vector<DWRITE_GLYPH_METRICS> metrics(aNumGlyphs);
-  mFontFace->GetDesignGlyphMetrics(aGlyphs, aNumGlyphs, &metrics.front());
-
-  Float scaleFactor = mSize / fontMetrics.designUnitsPerEm;
-
-  for (uint32_t i = 0; i < aNumGlyphs; i++) {
-    aGlyphMetrics[i].mXBearing = metrics[i].leftSideBearing * scaleFactor;
-    aGlyphMetrics[i].mXAdvance = metrics[i].advanceWidth * scaleFactor;
-    aGlyphMetrics[i].mYBearing =
-        (metrics[i].topSideBearing - metrics[i].verticalOriginY) * scaleFactor;
-    aGlyphMetrics[i].mYAdvance = metrics[i].advanceHeight * scaleFactor;
-    aGlyphMetrics[i].mWidth =
-        (metrics[i].advanceWidth - metrics[i].leftSideBearing -
-         metrics[i].rightSideBearing) *
-        scaleFactor;
-    aGlyphMetrics[i].mHeight =
-        (metrics[i].advanceHeight - metrics[i].topSideBearing -
-         metrics[i].bottomSideBearing) *
-        scaleFactor;
-  }
-}
-
 void ScaledFontDWrite::CopyGlyphsToSink(const GlyphBuffer& aBuffer,
                                         ID2D1SimplifiedGeometrySink* aSink) {
   std::vector<UINT16> indices;
   std::vector<FLOAT> advances;
   std::vector<DWRITE_GLYPH_OFFSET> offsets;
   indices.resize(aBuffer.mNumGlyphs);
   advances.resize(aBuffer.mNumGlyphs);
   offsets.resize(aBuffer.mNumGlyphs);
--- a/gfx/2d/ScaledFontDWrite.h
+++ b/gfx/2d/ScaledFontDWrite.h
@@ -43,19 +43,16 @@ class ScaledFontDWrite final : public Sc
   already_AddRefed<Path> GetPathForGlyphs(const GlyphBuffer& aBuffer,
                                           const DrawTarget* aTarget) override;
   void CopyGlyphsToBuilder(const GlyphBuffer& aBuffer, PathBuilder* aBuilder,
                            const Matrix* aTransformHint) override;
 
   void CopyGlyphsToSink(const GlyphBuffer& aBuffer,
                         ID2D1SimplifiedGeometrySink* aSink);
 
-  void GetGlyphDesignMetrics(const uint16_t* aGlyphIndices, uint32_t aNumGlyphs,
-                             GlyphMetrics* aGlyphMetrics) override;
-
   bool CanSerialize() override { return true; }
 
   bool GetFontInstanceData(FontInstanceDataOutput aCb, void* aBaton) override;
 
   bool GetWRFontInstanceOptions(
       Maybe<wr::FontInstanceOptions>* aOutOptions,
       Maybe<wr::FontInstancePlatformOptions>* aOutPlatformOptions,
       std::vector<FontVariation>* aOutVariations) override;
--- a/gfx/layers/ipc/CanvasChild.cpp
+++ b/gfx/layers/ipc/CanvasChild.cpp
@@ -188,21 +188,29 @@ void CanvasChild::EnsureBeginTransaction
 void CanvasChild::EndTransaction() {
   if (!mRecorder) {
     return;
   }
 
   if (mIsInTransaction) {
     mRecorder->RecordEvent(RecordedCanvasEndTransaction());
     mIsInTransaction = false;
+    mLastNonEmptyTransaction = TimeStamp::NowLoRes();
   }
 
   ++mTransactionsSinceGetDataSurface;
 }
 
+bool CanvasChild::ShouldBeCleanedUp() const {
+  static const TimeDuration kCleanUpCanvasThreshold =
+      TimeDuration::FromSeconds(10);
+  return TimeStamp::NowLoRes() - mLastNonEmptyTransaction >
+         kCleanUpCanvasThreshold;
+}
+
 already_AddRefed<gfx::DrawTarget> CanvasChild::CreateDrawTarget(
     gfx::IntSize aSize, gfx::SurfaceFormat aFormat) {
   MOZ_ASSERT(mRecorder);
 
   RefPtr<gfx::DrawTarget> dummyDt = gfx::Factory::CreateDrawTarget(
       gfx::BackendType::SKIA, gfx::IntSize(1, 1), aFormat);
   RefPtr<gfx::DrawTarget> dt = MakeAndAddRef<gfx::DrawTargetRecording>(
       mRecorder, dummyDt, gfx::IntRect(gfx::IntPoint(0, 0), aSize));
--- a/gfx/layers/ipc/CanvasChild.h
+++ b/gfx/layers/ipc/CanvasChild.h
@@ -70,16 +70,22 @@ class CanvasChild final : public PCanvas
   void EnsureBeginTransaction();
 
   /**
    * Send an end transaction event to indicate the end of events for this frame.
    */
   void EndTransaction();
 
   /**
+   * @returns true if the canvas IPC classes have not been used for some time
+   *          and can be cleaned up.
+   */
+  bool ShouldBeCleanedUp() const;
+
+  /**
    * Create a DrawTargetRecording for a canvas texture.
    * @param aSize size for the DrawTarget
    * @param aFormat SurfaceFormat for the DrawTarget
    * @returns newly created DrawTargetRecording
    */
   already_AddRefed<gfx::DrawTarget> CreateDrawTarget(
       gfx::IntSize aSize, gfx::SurfaceFormat aFormat);
 
@@ -118,16 +124,17 @@ class CanvasChild final : public PCanvas
   ~CanvasChild() final;
 
   static const uint32_t kCacheDataSurfaceThreshold = 10;
 
   RefPtr<CanvasDrawEventRecorder> mRecorder;
   TextureType mTextureType = TextureType::Unknown;
   uint32_t mLastWriteLockCheckpoint = 0;
   uint32_t mTransactionsSinceGetDataSurface = kCacheDataSurfaceThreshold;
+  TimeStamp mLastNonEmptyTransaction = TimeStamp::NowLoRes();
   bool mIsInTransaction = false;
   bool mHasOutstandingWriteLock = false;
 };
 
 }  // namespace layers
 }  // namespace mozilla
 
 #endif  // mozilla_layers_CanvasChild_h
--- a/gfx/layers/ipc/CompositorBridgeChild.cpp
+++ b/gfx/layers/ipc/CompositorBridgeChild.cpp
@@ -246,27 +246,16 @@ void CompositorBridgeChild::InitForConte
     // synchronous WillClose message to the parent, and will certainly get
     // a false result and a MsgDropped processing error. This is okay.
     old->Destroy();
   }
 
   mCanSend = true;
   mIdNamespace = aNamespace;
 
-  if (gfx::gfxVars::RemoteCanvasEnabled()) {
-    ipc::Endpoint<PCanvasParent> parentEndpoint;
-    ipc::Endpoint<PCanvasChild> childEndpoint;
-    nsresult rv = PCanvas::CreateEndpoints(OtherPid(), base::GetCurrentProcId(),
-                                           &parentEndpoint, &childEndpoint);
-    if (NS_SUCCEEDED(rv)) {
-      Unused << SendInitPCanvasParent(std::move(parentEndpoint));
-      mCanvasChild = new CanvasChild(std::move(childEndpoint));
-    }
-  }
-
   sCompositorBridge = this;
 }
 
 void CompositorBridgeChild::InitForWidget(uint64_t aProcessToken,
                                           LayerManager* aLayerManager,
                                           uint32_t aNamespace) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aProcessToken);
@@ -938,22 +927,39 @@ PTextureChild* CompositorBridgeChild::Cr
   }
 
   return SendPTextureConstructor(
       textureChild, aSharedData, aReadLock, aLayersBackend, aFlags,
       LayersId{0} /* FIXME? */, aSerial, aExternalImageId);
 }
 
 already_AddRefed<CanvasChild> CompositorBridgeChild::GetCanvasChild() {
+  MOZ_ASSERT(gfx::gfxVars::RemoteCanvasEnabled());
+  if (!mCanvasChild) {
+    ipc::Endpoint<PCanvasParent> parentEndpoint;
+    ipc::Endpoint<PCanvasChild> childEndpoint;
+    nsresult rv = PCanvas::CreateEndpoints(OtherPid(), base::GetCurrentProcId(),
+                                           &parentEndpoint, &childEndpoint);
+    if (NS_SUCCEEDED(rv)) {
+      Unused << SendInitPCanvasParent(std::move(parentEndpoint));
+      mCanvasChild = new CanvasChild(std::move(childEndpoint));
+    }
+  }
+
   return do_AddRef(mCanvasChild);
 }
 
 void CompositorBridgeChild::EndCanvasTransaction() {
   if (mCanvasChild) {
     mCanvasChild->EndTransaction();
+    if (mCanvasChild->ShouldBeCleanedUp()) {
+      mCanvasChild->Destroy();
+      Unused << SendReleasePCanvasParent();
+      mCanvasChild = nullptr;
+    }
   }
 }
 
 bool CompositorBridgeChild::AllocUnsafeShmem(
     size_t aSize, ipc::SharedMemory::SharedMemoryType aType,
     ipc::Shmem* aShmem) {
   ShmemAllocated(this);
   return PCompositorBridgeChild::AllocUnsafeShmem(aSize, aType, aShmem);
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -2320,16 +2320,20 @@ bool CompositorBridgeParent::DeallocPTex
   return TextureHost::DestroyIPDLActor(actor);
 }
 
 mozilla::ipc::IPCResult CompositorBridgeParent::RecvInitPCanvasParent(
     Endpoint<PCanvasParent>&& aEndpoint) {
   MOZ_CRASH("PCanvasParent shouldn't be created via CompositorBridgeParent.");
 }
 
+mozilla::ipc::IPCResult CompositorBridgeParent::RecvReleasePCanvasParent() {
+  MOZ_CRASH("PCanvasParent shouldn't be released via CompositorBridgeParent.");
+}
+
 bool CompositorBridgeParent::IsSameProcess() const {
   return OtherPid() == base::GetCurrentProcId();
 }
 
 void CompositorBridgeParent::NotifyWebRenderContextPurge() {
   MOZ_ASSERT(CompositorLoop() == MessageLoop::current());
   RefPtr<wr::WebRenderAPI> api =
       mWrBridge->GetWebRenderAPI(wr::RenderRoot::Default);
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -270,16 +270,17 @@ class CompositorBridgeParentBase : publi
   virtual mozilla::ipc::IPCResult RecvStartFrameTimeRecording(
       const int32_t& bufferSize, uint32_t* startIndex) = 0;
   virtual mozilla::ipc::IPCResult RecvStopFrameTimeRecording(
       const uint32_t& startIndex, nsTArray<float>* intervals) = 0;
   virtual mozilla::ipc::IPCResult RecvCheckContentOnlyTDR(
       const uint32_t& sequenceNum, bool* isContentOnlyTDR) = 0;
   virtual mozilla::ipc::IPCResult RecvInitPCanvasParent(
       Endpoint<PCanvasParent>&& aEndpoint) = 0;
+  virtual mozilla::ipc::IPCResult RecvReleasePCanvasParent() = 0;
 
   bool mCanSend;
 
  private:
   RefPtr<CompositorManagerParent> mCompositorManager;
 };
 
 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(
@@ -399,16 +400,18 @@ class CompositorBridgeParent final : pub
       const LayersBackend& aLayersBackend, const TextureFlags& aFlags,
       const LayersId& aId, const uint64_t& aSerial,
       const wr::MaybeExternalImageId& aExternalImageId) override;
   bool DeallocPTextureParent(PTextureParent* actor) override;
 
   mozilla::ipc::IPCResult RecvInitPCanvasParent(
       Endpoint<PCanvasParent>&& aEndpoint) final;
 
+  mozilla::ipc::IPCResult RecvReleasePCanvasParent() final;
+
   bool IsSameProcess() const override;
 
   void NotifyWebRenderContextPurge();
   void NotifyPipelineRendered(const wr::PipelineId& aPipelineId,
                               const wr::Epoch& aEpoch,
                               const VsyncId& aCompositeStartId,
                               TimeStamp& aCompositeStart,
                               TimeStamp& aRenderStart, TimeStamp& aCompositeEnd,
--- a/gfx/layers/ipc/ContentCompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/ContentCompositorBridgeParent.cpp
@@ -622,23 +622,31 @@ PTextureParent* ContentCompositorBridgeP
 bool ContentCompositorBridgeParent::DeallocPTextureParent(
     PTextureParent* actor) {
   return TextureHost::DestroyIPDLActor(actor);
 }
 
 mozilla::ipc::IPCResult ContentCompositorBridgeParent::RecvInitPCanvasParent(
     Endpoint<PCanvasParent>&& aEndpoint) {
   MOZ_RELEASE_ASSERT(!mCanvasParent,
-                     "Canvas Parent should only be created once per "
-                     "CrossProcessCompositorBridgeParent.");
+                     "Canvas Parent must be released before recreating.");
 
   mCanvasParent = CanvasParent::Create(std::move(aEndpoint));
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+ContentCompositorBridgeParent::RecvReleasePCanvasParent() {
+  MOZ_RELEASE_ASSERT(mCanvasParent,
+                     "Canvas Parent hasn't been created.");
+
+  mCanvasParent = nullptr;
+  return IPC_OK();
+}
+
 UniquePtr<SurfaceDescriptor>
 ContentCompositorBridgeParent::LookupSurfaceDescriptorForClientDrawTarget(
     const uintptr_t aDrawTarget) {
   return mCanvasParent->LookupSurfaceDescriptorForClientDrawTarget(aDrawTarget);
 }
 
 bool ContentCompositorBridgeParent::IsSameProcess() const {
   return OtherPid() == base::GetCurrentProcId();
--- a/gfx/layers/ipc/ContentCompositorBridgeParent.h
+++ b/gfx/layers/ipc/ContentCompositorBridgeParent.h
@@ -162,16 +162,18 @@ class ContentCompositorBridgeParent fina
       const LayersId& aId, const uint64_t& aSerial,
       const wr::MaybeExternalImageId& aExternalImageId) override;
 
   bool DeallocPTextureParent(PTextureParent* actor) override;
 
   mozilla::ipc::IPCResult RecvInitPCanvasParent(
       Endpoint<PCanvasParent>&& aEndpoint) final;
 
+  mozilla::ipc::IPCResult RecvReleasePCanvasParent() final;
+
   bool IsSameProcess() const override;
 
   PCompositorWidgetParent* AllocPCompositorWidgetParent(
       const CompositorWidgetInitData& aInitData) override {
     // Not allowed.
     return nullptr;
   }
   bool DeallocPCompositorWidgetParent(
--- a/gfx/layers/ipc/PCompositorBridge.ipdl
+++ b/gfx/layers/ipc/PCompositorBridge.ipdl
@@ -253,16 +253,17 @@ parent:
   /**
    * Sent when the child has finished CaptureAllPlugins.
    */
   async AllPluginsCaptured();
 
   async PTexture(SurfaceDescriptor aSharedData, ReadLockDescriptor aReadLock, LayersBackend aBackend, TextureFlags aTextureFlags, LayersId id, uint64_t aSerial, MaybeExternalImageId aExternalImageId);
 
   async InitPCanvasParent(Endpoint<PCanvasParent> aEndpoint);
+  async ReleasePCanvasParent();
 
   sync SyncWithCompositor();
 
   // The pipelineId is the same as the layersId
   async PWebRenderBridge(PipelineId pipelineId, LayoutDeviceIntSize aSize);
 
   sync CheckContentOnlyTDR(uint32_t sequenceNum)
     returns (bool isContentOnlyTDR);
--- a/gfx/thebes/gfxDWriteFonts.cpp
+++ b/gfx/thebes/gfxDWriteFonts.cpp
@@ -615,16 +615,36 @@ gfxFloat gfxDWriteFont::MeasureGlyphWidt
       if (SUCCEEDED(hr)) {
         return NS_lround(metrics.advanceWidth * mFUnitsConvFactor);
       }
     }
   }
   return 0.0;
 }
 
+bool gfxDWriteFont::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds,
+                                   bool aTight) {
+  DWRITE_GLYPH_METRICS m;
+  HRESULT hr = mFontFace->GetDesignGlyphMetrics(&aGID, 1, &m, FALSE);
+  if (FAILED(hr)) {
+    return false;
+  }
+  gfxRect bounds(m.leftSideBearing, m.topSideBearing - m.verticalOriginY,
+                 m.advanceWidth - m.leftSideBearing - m.rightSideBearing,
+                 m.advanceHeight - m.topSideBearing - m.bottomSideBearing);
+  bounds.Scale(mFUnitsConvFactor);
+  // GetDesignGlyphMetrics returns 'ideal' glyph metrics, we need to pad to
+  // account for antialiasing.
+  if (!aTight && !aBounds->IsEmpty()) {
+    bounds.Inflate(1.0, 0.0);
+  }
+  *aBounds = bounds;
+  return true;
+}
+
 void gfxDWriteFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
                                            FontCacheSizes* aSizes) const {
   gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
   aSizes->mFontInstances += aMallocSizeOf(mMetrics);
   if (mGlyphWidths) {
     aSizes->mFontInstances +=
         mGlyphWidths->ShallowSizeOfIncludingThis(aMallocSizeOf);
   }
--- a/gfx/thebes/gfxDWriteFonts.h
+++ b/gfx/thebes/gfxDWriteFonts.h
@@ -52,16 +52,18 @@ class gfxDWriteFont : public gfxFont {
                      DrawTarget* aDrawTargetForTightBoundingBox,
                      Spacing* aSpacing,
                      mozilla::gfx::ShapedTextFlags aOrientation) override;
 
   bool ProvidesGlyphWidths() const override;
 
   int32_t GetGlyphWidth(uint16_t aGID) override;
 
+  bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) override;
+
   void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                               FontCacheSizes* aSizes) const override;
   void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                               FontCacheSizes* aSizes) const override;
 
   FontType GetType() const override { return FONT_TYPE_DWRITE; }
 
   already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont(
--- a/gfx/thebes/gfxFT2FontBase.cpp
+++ b/gfx/thebes/gfxFT2FontBase.cpp
@@ -136,43 +136,27 @@ static void SnapLineToPixels(gfxFloat& a
   // Snap offset
   aOffset = floor(offset + 0.5);
   aSize = snappedSize;
 }
 
 /**
  * Get extents for a simple character representable by a single glyph.
  * The return value is the glyph id of that glyph or zero if no such glyph
- * exists.  aExtents is only set when this returns a non-zero glyph id.
+ * exists.  aWidth/aBounds is only set when this returns a non-zero glyph id.
+ * This is just for use during initialization, and doesn't use the width cache.
  */
 uint32_t gfxFT2FontBase::GetCharExtents(char aChar, gfxFloat* aWidth,
-                                        gfxFloat* aHeight) {
+                                        gfxRect* aBounds) {
   FT_UInt gid = GetGlyph(aChar);
   int32_t width;
-  int32_t height;
-  if (gid && GetFTGlyphExtents(gid, &width, &height)) {
-    *aWidth = FLOAT_FROM_16_16(width);
-    *aHeight = FLOAT_FROM_26_6(height);
-    return gid;
-  } else {
-    return 0;
-  }
-}
-
-/**
- * Get glyph id and width for a simple character.
- * The return value is the glyph id of that glyph or zero if no such glyph
- * exists.  aWidth is only set when this returns a non-zero glyph id.
- * This is just for use during initialization, and doesn't use the width cache.
- */
-uint32_t gfxFT2FontBase::GetCharWidth(char aChar, gfxFloat* aWidth) {
-  FT_UInt gid = GetGlyph(aChar);
-  int32_t width;
-  if (gid && GetFTGlyphExtents(gid, &width)) {
-    *aWidth = FLOAT_FROM_16_16(width);
+  if (gid && GetFTGlyphExtents(gid, &width, aBounds)) {
+    if (aWidth) {
+      *aWidth = FLOAT_FROM_16_16(width);
+    }
     return gid;
   } else {
     return 0;
   }
 }
 
 /**
  * Find the closest available fixed strike size, if applicable, to the
@@ -397,42 +381,42 @@ void gfxFT2FontBase::InitMetrics() {
     mMetrics.capHeight = mMetrics.maxAscent;
   }
 
   // Release the face lock to safely load glyphs with GetCharExtents if
   // necessary without recursively locking.
   UnlockFTFace();
 
   gfxFloat width;
-  mSpaceGlyph = GetCharWidth(' ', &width);
+  mSpaceGlyph = GetCharExtents(' ', &width);
   if (mSpaceGlyph) {
     mMetrics.spaceWidth = width;
   } else {
     mMetrics.spaceWidth = mMetrics.maxAdvance;  // guess
   }
 
-  if (GetCharWidth('0', &width)) {
+  if (GetCharExtents('0', &width)) {
     mMetrics.zeroWidth = width;
   } else {
     mMetrics.zeroWidth = -1.0;  // indicates not found
   }
 
   // Prefering a measured x over sxHeight because sxHeight doesn't consider
   // hinting, but maybe the x extents are not quite right in some fancy
   // script fonts.  CSS 2.1 suggests possibly using the height of an "o",
   // which would have a more consistent glyph across fonts.
   gfxFloat xWidth;
-  gfxFloat xHeight;
-  if (GetCharExtents('x', &xWidth, &xHeight) && xHeight < 0.0) {
-    mMetrics.xHeight = -xHeight;
+  gfxRect xBounds;
+  if (GetCharExtents('x', &xWidth, &xBounds) && xBounds.y < 0.0) {
+    mMetrics.xHeight = -xBounds.y;
     mMetrics.aveCharWidth = std::max(mMetrics.aveCharWidth, xWidth);
   }
 
-  if (GetCharExtents('H', &xWidth, &xHeight) && xHeight < 0.0) {
-    mMetrics.capHeight = -xHeight;
+  if (GetCharExtents('H', nullptr, &xBounds) && xBounds.y < 0.0) {
+    mMetrics.capHeight = -xBounds.y;
   }
 
   mMetrics.aveCharWidth = std::max(mMetrics.aveCharWidth, mMetrics.zeroWidth);
   if (mMetrics.aveCharWidth == 0.0) {
     mMetrics.aveCharWidth = mMetrics.spaceWidth;
   }
   // Apparently hinting can mean that max_advance is not always accurate.
   mMetrics.maxAdvance = std::max(mMetrics.maxAdvance, mMetrics.aveCharWidth);
@@ -499,37 +483,37 @@ uint32_t gfxFT2FontBase::GetGlyph(uint32
       return GetGlyph(unicode);
     }
     return 0;
   }
 
   return GetGlyph(unicode);
 }
 
-FT_Fixed gfxFT2FontBase::GetEmboldenAdvance(FT_Face aFace, FT_Fixed aAdvance) {
-  // If freetype emboldening is being used, and it's not a zero-width glyph,
-  // adjust the advance to account for the increased width.
-  if (!mEmbolden || !aAdvance) {
-    return 0;
+FT_Vector gfxFT2FontBase::GetEmboldenStrength(FT_Face aFace) {
+  FT_Vector strength = { 0, 0 };
+  if (!mEmbolden) {
+    return strength;
   }
-  // This is the embolden "strength" used by FT_GlyphSlot_Embolden,
-  // converted from 26.6 to 16.16
-  FT_Fixed strength =
+  // This is the embolden "strength" used by FT_GlyphSlot_Embolden.
+  strength.x =
       FT_MulFix(aFace->units_per_EM, aFace->size->metrics.y_scale) / 24;
+  strength.y = strength.x;
   if (aFace->glyph->format == FT_GLYPH_FORMAT_BITMAP) {
-    strength &= -64;
-    if (!strength) {
-      strength = 64;
+    strength.x &= -64;
+    if (!strength.x) {
+      strength.x = 64;
     }
+    strength.y &= -64;
   }
-  return strength << 10;
+  return strength;
 }
 
 bool gfxFT2FontBase::GetFTGlyphExtents(uint16_t aGID, int32_t* aAdvance,
-                                       int32_t* aHeight) {
+                                       gfxRect* aBounds) {
   gfxFT2LockedFace face(this);
   MOZ_ASSERT(face.get());
   if (!face.get()) {
     // Failed to get the FT_Face? Give up already.
     NS_WARNING("failed to get FT_Face!");
     return false;
   }
 
@@ -542,42 +526,61 @@ bool gfxFT2FontBase::GetFTGlyphExtents(u
   }
 
   bool hintMetrics = ShouldHintMetrics();
 
   // Normalize out the loaded FT glyph size and then scale to the actually
   // desired size, in case these two sizes differ.
   gfxFloat extentsScale = GetAdjustedSize() / mFTSize;
 
+  FT_Vector bold = GetEmboldenStrength(face.get());
+
   // Due to freetype bug 52683 we MUST use the linearHoriAdvance field when
   // dealing with a variation font; also use it for scalable fonts when not
   // applying hinting. Otherwise, prefer hinted width from glyph->advance.x.
-  FT_Fixed advance;
-  if (face.get()->glyph->format == FT_GLYPH_FORMAT_OUTLINE &&
-      (!hintMetrics || FT_HAS_MULTIPLE_MASTERS(face.get()))) {
-    advance = face.get()->glyph->linearHoriAdvance;
-  } else {
-    advance = face.get()->glyph->advance.x << 10;  // convert 26.6 to 16.16
+  if (aAdvance) {
+    FT_Fixed advance;
+    if (face.get()->glyph->format == FT_GLYPH_FORMAT_OUTLINE &&
+        (!hintMetrics || FT_HAS_MULTIPLE_MASTERS(face.get()))) {
+      advance = face.get()->glyph->linearHoriAdvance;
+    } else {
+      advance = face.get()->glyph->advance.x << 10;  // convert 26.6 to 16.16
+    }
+    if (advance) {
+      advance += bold.x << 10;  // convert 26.6 to 16.16
+    }
+    // Hinting was requested, but FT did not apply any hinting to the metrics.
+    // Round the advance here to approximate hinting as Cairo does. This must
+    // happen BEFORE we apply the glyph extents scale, just like FT hinting
+    // would.
+    if (hintMetrics && (mFTLoadFlags & FT_LOAD_NO_HINTING)) {
+      advance = (advance + 0x8000) & 0xffff0000u;
+    }
+    *aAdvance = NS_lround(advance * extentsScale);
   }
-  advance += GetEmboldenAdvance(face.get(), advance);
-  // Hinting was requested, but FT did not apply any hinting to the metrics.
-  // Round the advance here to approximate hinting as Cairo does. This must
-  // happen BEFORE we apply the glyph extents scale, just like FT hinting
-  // would.
-  if (hintMetrics && (mFTLoadFlags & FT_LOAD_NO_HINTING)) {
-    advance = (advance + 0x8000) & 0xffff0000u;
-  }
-  *aAdvance = NS_lround(advance * extentsScale);
 
-  if (aHeight) {
-    FT_F26Dot6 height = -face.get()->glyph->metrics.horiBearingY;
+  if (aBounds) {
+    const FT_Glyph_Metrics& metrics = face.get()->glyph->metrics;
+    FT_F26Dot6 x = metrics.horiBearingX;
+    FT_F26Dot6 y = -metrics.horiBearingY;
+    FT_F26Dot6 x2 = x + metrics.width;
+    FT_F26Dot6 y2 = y + metrics.height;
+    // Synthetic bold moves the glyph top and right boundaries.
+    y -= bold.y;
+    x2 += bold.x;
     if (hintMetrics && (mFTLoadFlags & FT_LOAD_NO_HINTING)) {
-      height &= -64;
+      x &= -64;
+      y &= -64;
+      x2 = (x2 + 63) & -64;
+      y2 = (y2 + 63) & -64;
     }
-    *aHeight = NS_lround(height * extentsScale);
+    *aBounds = gfxRect(FLOAT_FROM_26_6(x) * extentsScale,
+                       FLOAT_FROM_26_6(y) * extentsScale,
+                       FLOAT_FROM_26_6(x2 - x) * extentsScale,
+                       FLOAT_FROM_26_6(y2 - y) * extentsScale);
   }
   return true;
 }
 
 int32_t gfxFT2FontBase::GetGlyphWidth(uint16_t aGID) {
   if (!mGlyphWidths) {
     mGlyphWidths =
         mozilla::MakeUnique<nsDataHashtable<nsUint32HashKey, int32_t>>(128);
@@ -591,16 +594,21 @@ int32_t gfxFT2FontBase::GetGlyphWidth(ui
   if (!GetFTGlyphExtents(aGID, &width)) {
     width = 0;
   }
   mGlyphWidths->Put(aGID, width);
 
   return width;
 }
 
+bool gfxFT2FontBase::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds,
+                                    bool aTight) {
+  return GetFTGlyphExtents(aGID, nullptr, aBounds);
+}
+
 // For variation fonts, figure out the variation coordinates to be applied
 // for each axis, in freetype's order (which may not match the order of
 // axes in mStyle.variationSettings, so we need to search by axis tag).
 /*static*/
 void gfxFT2FontBase::SetupVarCoords(
     FT_MM_Var* aMMVar, const nsTArray<gfxFontVariation>& aVariations,
     FT_Face aFTFace) {
   if (!aMMVar) {
--- a/gfx/thebes/gfxFT2FontBase.h
+++ b/gfx/thebes/gfxFT2FontBase.h
@@ -25,39 +25,40 @@ class gfxFT2FontBase : public gfxFont {
 
   uint32_t GetGlyph(uint32_t aCharCode);
   uint32_t GetSpaceGlyph() override;
   bool ProvidesGetGlyph() const override { return true; }
   virtual uint32_t GetGlyph(uint32_t unicode,
                             uint32_t variation_selector) override;
   bool ProvidesGlyphWidths() const override { return true; }
   int32_t GetGlyphWidth(uint16_t aGID) override;
+  bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) override;
 
   FontType GetType() const override { return FONT_TYPE_FT2; }
 
   static void SetupVarCoords(FT_MM_Var* aMMVar,
                              const nsTArray<gfxFontVariation>& aVariations,
                              FT_Face aFTFace);
 
   FT_Face LockFTFace();
   void UnlockFTFace();
 
  private:
-  uint32_t GetCharExtents(char aChar, gfxFloat* aWidth, gfxFloat* aHeight);
-  uint32_t GetCharWidth(char aChar, gfxFloat* aWidth);
+  uint32_t GetCharExtents(char aChar, gfxFloat* aWidth,
+                          gfxRect* aBounds = nullptr);
 
-  // Get advance (and optionally height) of a single glyph from FreeType,
+  // Get advance (and optionally bounds) of a single glyph from FreeType,
   // and return true, or return false if we failed.
   bool GetFTGlyphExtents(uint16_t aGID, int32_t* aWidth,
-                         int32_t* aHeight = nullptr);
+                         gfxRect* aBounds = nullptr);
 
  protected:
   void InitMetrics();
   const Metrics& GetHorizontalMetrics() override;
-  FT_Fixed GetEmboldenAdvance(FT_Face aFace, FT_Fixed aAdvance);
+  FT_Vector GetEmboldenStrength(FT_Face aFace);
 
   RefPtr<mozilla::gfx::SharedFTFace> mFTFace;
 
   uint32_t mSpaceGlyph;
   Metrics mMetrics;
   int mFTLoadFlags;
   bool mEmbolden;
   gfxFloat mFTSize;
--- a/gfx/thebes/gfxFT2Fonts.cpp
+++ b/gfx/thebes/gfxFT2Fonts.cpp
@@ -211,18 +211,20 @@ void gfxFT2Font::FillGlyphDataForChar(FT
 
     gd->glyphIndex = 0;
     return;
   }
 
   gd->glyphIndex = gid;
   gd->lsbDelta = face->glyph->lsb_delta;
   gd->rsbDelta = face->glyph->rsb_delta;
-  gd->xAdvance =
-      face->glyph->advance.x + GetEmboldenAdvance(face, face->glyph->advance.x);
+  gd->xAdvance = face->glyph->advance.x;
+  if (gd->xAdvance) {
+    gd->xAdvance += GetEmboldenStrength(face).x;
+  }
 }
 
 void gfxFT2Font::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
                                         FontCacheSizes* aSizes) const {
   gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
   aSizes->mFontInstances +=
       mCharGlyphCache.ShallowSizeOfExcludingThis(aMallocSizeOf);
 }
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -825,20 +825,19 @@ gfxFont::RoundingFlags gfxFont::GetRound
   // Could do something fancy here for ScaleFactors of
   // AxisAlignedTransforms, but we leave things simple.
   // Not much point rounding if a matrix will mess things up anyway.
   // Also check if the font already knows hint metrics is off...
   if (aDrawTarget->GetTransform().HasNonTranslation() || !ShouldHintMetrics()) {
     return RoundingFlags(0);
   }
 
-  cairo_t* cr = nullptr;
-  if (aDrawTarget->GetBackendType() == BackendType::CAIRO) {
-    cr = static_cast<cairo_t*>(
-        aDrawTarget->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT));
+  cairo_t* cr = static_cast<cairo_t*>(
+      aDrawTarget->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT));
+  if (cr) {
     cairo_surface_t* target = cairo_get_target(cr);
 
     // Check whether the cairo surface's font options hint metrics.
     cairo_font_options_t* fontOptions = cairo_font_options_create();
     cairo_surface_get_font_options(target, fontOptions);
     cairo_hint_metrics_t hintMetrics =
         cairo_font_options_get_hint_metrics(fontOptions);
     cairo_font_options_destroy(fontOptions);
@@ -3370,48 +3369,41 @@ void gfxFont::SetupGlyphExtents(DrawTarg
                                      &svgBounds)) {
     gfxFloat d2a = aExtents->GetAppUnitsPerDevUnit();
     aExtents->SetTightGlyphExtents(
         aGlyphID, gfxRect(svgBounds.X() * d2a, svgBounds.Y() * d2a,
                           svgBounds.Width() * d2a, svgBounds.Height() * d2a));
     return;
   }
 
-  RefPtr<ScaledFont> sf = GetScaledFont(aDrawTarget);
-  uint16_t glyphIndex = aGlyphID;
-  GlyphMetrics metrics;
-  if (mAntialiasOption == kAntialiasNone) {
-    sf->GetGlyphDesignMetrics(&glyphIndex, 1, &metrics);
-  } else {
-    aDrawTarget->GetGlyphRasterizationMetrics(sf, &glyphIndex, 1, &metrics);
-  }
+  gfxRect bounds;
+  GetGlyphBounds(aGlyphID, &bounds, mAntialiasOption == kAntialiasNone);
 
   const Metrics& fontMetrics = GetMetrics(nsFontMetrics::eHorizontal);
   int32_t appUnitsPerDevUnit = aExtents->GetAppUnitsPerDevUnit();
-  if (!aNeedTight && metrics.mXBearing >= 0.0 &&
-      metrics.mYBearing >= -fontMetrics.maxAscent &&
-      metrics.mHeight + metrics.mYBearing <= fontMetrics.maxDescent) {
-    uint32_t appUnitsWidth = uint32_t(
-        ceil((metrics.mXBearing + metrics.mWidth) * appUnitsPerDevUnit));
+  if (!aNeedTight && bounds.x >= 0.0 && bounds.y >= -fontMetrics.maxAscent &&
+      bounds.height + bounds.y <= fontMetrics.maxDescent) {
+    uint32_t appUnitsWidth =
+        uint32_t(ceil((bounds.x + bounds.width) * appUnitsPerDevUnit));
     if (appUnitsWidth < gfxGlyphExtents::INVALID_WIDTH) {
       aExtents->SetContainedGlyphWidthAppUnits(aGlyphID,
                                                uint16_t(appUnitsWidth));
       return;
     }
   }
 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
   if (!aNeedTight) {
     ++gGlyphExtentsSetupFallBackToTight;
   }
 #endif
 
   gfxFloat d2a = appUnitsPerDevUnit;
-  gfxRect bounds(metrics.mXBearing * d2a, metrics.mYBearing * d2a,
-                 metrics.mWidth * d2a, metrics.mHeight * d2a);
-  aExtents->SetTightGlyphExtents(aGlyphID, bounds);
+  aExtents->SetTightGlyphExtents(
+      aGlyphID, gfxRect(bounds.x * d2a, bounds.y * d2a, bounds.width * d2a,
+                        bounds.height * d2a));
 }
 
 // Try to initialize font metrics by reading sfnt tables directly;
 // set mIsValid=TRUE and return TRUE on success.
 // Return FALSE if the gfxFontEntry subclass does not
 // implement GetFontTable(), or for non-sfnt fonts where tables are
 // not available.
 // If this returns TRUE without setting the mIsValid flag, then we -did-
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -1914,16 +1914,21 @@ class gfxFont {
   // if they do not override this, harfbuzz will use unhinted widths
   // derived from the font tables
   virtual bool ProvidesGlyphWidths() const { return false; }
 
   // The return value is interpreted as a horizontal advance in 16.16 fixed
   // point format.
   virtual int32_t GetGlyphWidth(uint16_t aGID) { return -1; }
 
+  virtual bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds,
+                              bool aTight = false) {
+    return false;
+  }
+
   bool IsSpaceGlyphInvisible(DrawTarget* aRefDrawTarget,
                              const gfxTextRun* aTextRun);
 
   void AddGlyphChangeObserver(GlyphChangeObserver* aObserver);
   void RemoveGlyphChangeObserver(GlyphChangeObserver* aObserver);
 
   // whether font contains substitution lookups containing spaces
   bool HasSubstitutionRulesWithSpaceLookups(Script aRunScript);
--- a/gfx/thebes/gfxGDIFont.cpp
+++ b/gfx/thebes/gfxGDIFont.cpp
@@ -42,16 +42,17 @@ static inline cairo_antialias_t GetCairo
 
 gfxGDIFont::gfxGDIFont(GDIFontEntry* aFontEntry, const gfxFontStyle* aFontStyle,
                        AntialiasOption anAAOption)
     : gfxFont(nullptr, aFontEntry, aFontStyle, anAAOption),
       mFont(nullptr),
       mFontFace(nullptr),
       mMetrics(nullptr),
       mSpaceGlyph(0),
+      mIsBitmap(false),
       mScriptCache(nullptr) {
   mNeedsSyntheticBold = aFontStyle->NeedsSyntheticBold(aFontEntry);
 
   Initialize();
 
   if (mFont) {
     mUnscaledFont = aFontEntry->LookupUnscaledFont(mFont);
   }
@@ -270,16 +271,18 @@ void gfxGDIFont::Initialize() {
     mMetrics->maxAdvance = metrics.tmMaxCharWidth;
     mMetrics->aveCharWidth = std::max<gfxFloat>(1, metrics.tmAveCharWidth);
     // The font is monospace when TMPF_FIXED_PITCH is *not* set!
     // See http://msdn2.microsoft.com/en-us/library/ms534202(VS.85).aspx
     if (!(metrics.tmPitchAndFamily & TMPF_FIXED_PITCH)) {
       mMetrics->maxAdvance = mMetrics->aveCharWidth;
     }
 
+    mIsBitmap = !(metrics.tmPitchAndFamily & TMPF_VECTOR);
+
     // For fonts with USE_TYPO_METRICS set in the fsSelection field,
     // let the OS/2 sTypo* metrics override the previous values.
     // (see http://www.microsoft.com/typography/otspec/os2.htm#fss)
     // Using the equivalent values from oMetrics provides inconsistent
     // results with CFF fonts, so we instead rely on OS2Table.
     gfxFontEntry::AutoTable os2Table(mFontEntry,
                                      TRUETYPE_TAG('O', 'S', '/', '2'));
     if (os2Table) {
@@ -484,16 +487,60 @@ int32_t gfxGDIFont::GetGlyphWidth(uint16
     width = devWidth << 16;
     mGlyphWidths->Put(aGID, width);
     return width;
   }
 
   return -1;
 }
 
+bool gfxGDIFont::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) {
+  DCForMetrics dc;
+  AutoSelectFont fs(dc, GetHFONT());
+
+  if (mIsBitmap) {
+    int devWidth;
+    if (!GetCharWidthI(dc, aGID, 1, nullptr, &devWidth)) {
+      return false;
+    }
+    devWidth = std::min(std::max(0, devWidth), 0x7fff);
+
+    *aBounds = gfxRect(0, -mMetrics->maxAscent, devWidth,
+                       mMetrics->maxAscent + mMetrics->maxDescent);
+    return true;
+  }
+
+  const MAT2 kIdentityMatrix = {{0, 1}, {0, 0}, {0, 0}, {0, 1}};
+  GLYPHMETRICS gm;
+  if (GetGlyphOutlineW(dc, aGID, GGO_METRICS | GGO_GLYPH_INDEX, &gm, 0, nullptr,
+                       &kIdentityMatrix) == GDI_ERROR) {
+    return false;
+  }
+
+  if (gm.gmBlackBoxX == 1 && gm.gmBlackBoxY == 1 &&
+      !GetGlyphOutlineW(dc, aGID, GGO_NATIVE | GGO_GLYPH_INDEX, &gm, 0, nullptr,
+                        &kIdentityMatrix)) {
+    // Workaround for GetGlyphOutline returning 1x1 bounding box
+    // for <space> glyph that is in fact empty.
+    gm.gmBlackBoxX = 0;
+    gm.gmBlackBoxY = 0;
+  } else if (gm.gmBlackBoxX > 0 && !aTight) {
+    // The bounding box reported by Windows supposedly contains the glyph's
+    // "black" area; however, antialiasing (especially with ClearType) means
+    // that the actual image that needs to be rendered may "bleed" into the
+    // adjacent pixels, mainly on the right side.
+    gm.gmptGlyphOrigin.x -= 1;
+    gm.gmBlackBoxX += 3;
+  }
+
+  *aBounds = gfxRect(gm.gmptGlyphOrigin.x, -gm.gmptGlyphOrigin.y,
+                     gm.gmBlackBoxX, gm.gmBlackBoxY);
+  return true;
+}
+
 void gfxGDIFont::AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                                         FontCacheSizes* aSizes) const {
   gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
   aSizes->mFontInstances += aMallocSizeOf(mMetrics);
   if (mGlyphWidths) {
     aSizes->mFontInstances +=
         mGlyphWidths->ShallowSizeOfIncludingThis(aMallocSizeOf);
   }
--- a/gfx/thebes/gfxGDIFont.h
+++ b/gfx/thebes/gfxGDIFont.h
@@ -50,16 +50,18 @@ class gfxGDIFont : public gfxFont {
 
   uint32_t GetGlyph(uint32_t aUnicode, uint32_t aVarSelector) override;
 
   bool ProvidesGlyphWidths() const override { return true; }
 
   // get hinted glyph width in pixels as 16.16 fixed-point value
   int32_t GetGlyphWidth(uint16_t aGID) override;
 
+  bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) override;
+
   void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                               FontCacheSizes* aSizes) const;
   void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                               FontCacheSizes* aSizes) const;
 
   FontType GetType() const override { return FONT_TYPE_GDI; }
 
  protected:
@@ -79,16 +81,17 @@ class gfxGDIFont : public gfxFont {
   // have generic support for this in gfxFont::Draw instead.)
   void FillLogFont(LOGFONTW& aLogFont, gfxFloat aSize);
 
   HFONT mFont;
   cairo_font_face_t* mFontFace;
 
   Metrics* mMetrics;
   uint32_t mSpaceGlyph;
+  bool mIsBitmap;
 
   bool mNeedsSyntheticBold;
 
   // cache of glyph IDs (used for non-sfnt fonts only)
   mozilla::UniquePtr<nsDataHashtable<nsUint32HashKey, uint32_t> > mGlyphIDs;
   SCRIPT_CACHE mScriptCache;
 
   // cache of glyph widths in 16.16 fixed-point pixels
--- a/gfx/thebes/gfxMacFont.cpp
+++ b/gfx/thebes/gfxMacFont.cpp
@@ -507,16 +507,37 @@ int32_t gfxMacFont::GetGlyphWidth(uint16
   }
 
   CGSize advance;
   ::CTFontGetAdvancesForGlyphs(mCTFont, kCTFontDefaultOrientation, &aGID,
                                &advance, 1);
   return advance.width * 0x10000;
 }
 
+bool gfxMacFont::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) {
+  CGRect bb;
+  if (!::CGFontGetGlyphBBoxes(mCGFont, &aGID, 1, &bb)) {
+    return false;
+  }
+
+  // broken fonts can return incorrect bounds for some null characters,
+  // see https://bugzilla.mozilla.org/show_bug.cgi?id=534260
+  if (bb.origin.x == -32767 && bb.origin.y == -32767 &&
+      bb.size.width == 65534 && bb.size.height == 65534) {
+    *aBounds = gfxRect(0, 0, 0, 0);
+    return true;
+  }
+
+  gfxRect bounds(bb.origin.x, -(bb.origin.y + bb.size.height), bb.size.width,
+                 bb.size.height);
+  bounds.Scale(mFUnitsConvFactor);
+  *aBounds = bounds;
+  return true;
+}
+
 // Try to initialize font metrics via platform APIs (CG/CT),
 // and set mIsValid = TRUE on success.
 // We ONLY call this for local (platform) fonts that are not sfnt format;
 // for sfnts, including ALL downloadable fonts, we prefer to use
 // InitMetricsFromSfntTables and avoid platform APIs.
 void gfxMacFont::InitMetricsFromPlatform() {
   CTFontRef ctFont =
       ::CTFontCreateWithGraphicsFont(mCGFont, mAdjustedSize, nullptr, nullptr);
--- a/gfx/thebes/gfxMacFont.h
+++ b/gfx/thebes/gfxMacFont.h
@@ -36,16 +36,18 @@ class gfxMacFont : public gfxFont {
   // with embedded color bitmaps (Apple Color Emoji), as Core Text renders
   // the glyphs with non-linear scaling at small pixel sizes.
   bool ProvidesGlyphWidths() const override {
     return mVariationFont || mFontEntry->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x'));
   }
 
   int32_t GetGlyphWidth(uint16_t aGID) override;
 
+  bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds, bool aTight) override;
+
   already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont(
       mozilla::gfx::DrawTarget* aTarget) override;
 
   bool ShouldRoundXOffset(cairo_t* aCairo) const override;
 
   void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                               FontCacheSizes* aSizes) const override;
   void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
--- a/gfx/thebes/gfxUserFontSet.cpp
+++ b/gfx/thebes/gfxUserFontSet.cpp
@@ -1133,16 +1133,34 @@ gfxUserFontFamily* gfxUserFontSet::GetFa
   gfxUserFontFamily* family = mFontFamilies.GetWeak(key);
   if (!family) {
     family = new gfxUserFontFamily(aFamilyName);
     mFontFamilies.Put(key, family);
   }
   return family;
 }
 
+void gfxUserFontSet::ForgetLocalFaces() {
+  for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) {
+    const auto fam = iter.Data();
+    const auto& fonts = fam->GetFontList();
+    for (const auto& f : fonts) {
+      auto ufe = static_cast<gfxUserFontEntry*>(f.get());
+      // If the user font entry has loaded an entry using src:local(),
+      // discard it as no longer valid, and reset the load state so that
+      // the load will be re-done based on the updated font list.
+      if (ufe->GetPlatformFontEntry() &&
+          ufe->GetPlatformFontEntry()->IsLocalUserFont()) {
+        ufe->mPlatformFontEntry = nullptr;
+        ufe->LoadCanceled();
+      }
+    }
+  }
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // gfxUserFontSet::UserFontCache - re-use platform font entries for user fonts
 // across pages/fontsets rather than instantiating new platform fonts.
 //
 // Entries are added to this cache when a platform font is instantiated from
 // downloaded data, and removed when the platform font entry is destroyed.
 // We don't need to use a timed expiration scheme here because the gfxFontEntry
 // for a downloaded font will be kept alive by its corresponding gfxFont
--- a/gfx/thebes/gfxUserFontSet.h
+++ b/gfx/thebes/gfxUserFontSet.h
@@ -293,16 +293,22 @@ class gfxUserFontSet {
   // Generation is bumped on font loads but that doesn't affect name-style
   // mappings. Rebuilds do however affect name-style mappings so need to
   // lookup fontlists again when that happens.
   uint64_t GetRebuildGeneration() { return mRebuildGeneration; }
 
   // rebuild if local rules have been used
   void RebuildLocalRules();
 
+  // Discard any font entries created for src:local(), so that they will
+  // be reloaded next time they're needed. This is called when the platform
+  // font list has changed, which means local font entries that were set up
+  // may no longer be valid.
+  void ForgetLocalFaces();
+
   class UserFontCache {
    public:
     // Record a loaded user-font in the cache. This requires that the
     // font-entry's userFontData has been set up already, as it relies
     // on the URI and Principal recorded there.
     static void CacheFont(gfxFontEntry* aFontEntry);
 
     // The given gfxFontEntry is being destroyed, so remove any record that
--- a/js/public/CompileOptions.h
+++ b/js/public/CompileOptions.h
@@ -201,30 +201,32 @@ class JS_PUBLIC_API ReadOnlyCompileOptio
   unsigned scriptSourceOffset = 0;
 
   // isRunOnce only applies to non-function scripts.
   bool isRunOnce = false;
 
   bool nonSyntacticScope = false;
   bool noScriptRval = false;
 
- private:
-  friend class CompileOptions;
+ protected:
+  // Flag used to bypass the filename validation callback.
+  // See also SetFilenameValidationCallback.
+  bool skipFilenameValidation_ = false;
 
- protected:
   ReadOnlyCompileOptions() = default;
 
   // Set all POD options (those not requiring reference counts, copies,
   // rooting, or other hand-holding) not set by copyPODTransitiveOptions to
   // their values in |rhs|.
   void copyPODNonTransitiveOptions(const ReadOnlyCompileOptions& rhs);
 
  public:
   // Read-only accessors for non-POD options. The proper way to set these
   // depends on the derived type.
+  bool skipFilenameValidation() const { return skipFilenameValidation_; }
   const char* filename() const { return filename_; }
   const char* introducerFilename() const { return introducerFilename_; }
   const char16_t* sourceMapURL() const { return sourceMapURL_; }
 
  private:
   void operator=(const ReadOnlyCompileOptions&) = delete;
 };
 
@@ -384,16 +386,21 @@ class MOZ_STACK_CLASS JS_PUBLIC_API Comp
     return *this;
   }
 
   CompileOptions& setNoScriptRval(bool nsr) {
     noScriptRval = nsr;
     return *this;
   }
 
+  CompileOptions& setSkipFilenameValidation(bool b) {
+    skipFilenameValidation_ = b;
+    return *this;
+  }
+
   CompileOptions& setSelfHostingMode(bool shm) {
     selfHostingMode = shm;
     return *this;
   }
 
   CompileOptions& setSourceIsLazy(bool l) {
     sourceIsLazy = l;
     return *this;
--- a/js/public/Id.h
+++ b/js/public/Id.h
@@ -196,23 +196,19 @@ struct BarrierMethods<jsid> {
     if (JSID_IS_STRING(id)) {
       return reinterpret_cast<gc::Cell*>(JSID_TO_STRING(id));
     }
     if (JSID_IS_SYMBOL(id)) {
       return reinterpret_cast<gc::Cell*>(JSID_TO_SYMBOL(id));
     }
     return nullptr;
   }
-  static void writeBarriers(jsid* idp, jsid prev, jsid next) {
-    if (JSID_IS_STRING(prev)) {
-      JS::IncrementalPreWriteBarrier(JS::GCCellPtr(JSID_TO_STRING(prev)));
-    }
-    if (JSID_IS_SYMBOL(prev)) {
-      JS::IncrementalPreWriteBarrier(JS::GCCellPtr(JSID_TO_SYMBOL(prev)));
-    }
+  static void postWriteBarrier(jsid* idp, jsid prev, jsid next) {
+    MOZ_ASSERT_IF(JSID_IS_STRING(next),
+                  !gc::IsInsideNursery(JSID_TO_STRING(next)));
   }
   static void exposeToJS(jsid id) {
     if (JSID_IS_GCTHING(id)) {
       js::gc::ExposeGCThingToActiveJS(JSID_TO_GCTHING(id));
     }
   }
 };
 
--- a/js/public/RootingAPI.h
+++ b/js/public/RootingAPI.h
@@ -204,16 +204,20 @@ struct PersistentRootedMarker;
 
 namespace JS {
 
 template <typename T>
 class Rooted;
 template <typename T>
 class PersistentRooted;
 
+JS_FRIEND_API void HeapObjectPostWriteBarrier(JSObject** objp, JSObject* prev,
+                                              JSObject* next);
+JS_FRIEND_API void HeapStringPostWriteBarrier(JSString** objp, JSString* prev,
+                                              JSString* next);
 JS_FRIEND_API void HeapObjectWriteBarriers(JSObject** objp, JSObject* prev,
                                            JSObject* next);
 JS_FRIEND_API void HeapStringWriteBarriers(JSString** objp, JSString* prev,
                                            JSString* next);
 JS_FRIEND_API void HeapScriptWriteBarriers(JSScript** objp, JSScript* prev,
                                            JSScript* next);
 
 /**
@@ -269,19 +273,23 @@ inline void AssertGCThingIsNotNurseryAll
  *
  * Heap<T> is an abstraction that hides some of the complexity required to
  * maintain GC invariants for the contained reference. It uses operator
  * overloading to provide a normal pointer interface, but adds barriers to
  * notify the GC of changes.
  *
  * Heap<T> implements the following barriers:
  *
- *  - Pre-write barrier (necessary for incremental GC).
  *  - Post-write barrier (necessary for generational GC).
- *  - Read barrier (necessary for cycle collector integration).
+ *  - Read barrier (necessary for incremental GC and cycle collector
+ *    integration).
+ *
+ * Note Heap<T> does not have a pre-write barrier as used internally in the
+ * engine. The read barrier is used to mark anything read from a Heap<T> during
+ * an incremental GC.
  *
  * Heap<T> may be moved or destroyed outside of GC finalization and hence may be
  * used in dynamic storage such as a Vector.
  *
  * Heap<T> instances must be traced when their containing object is traced to
  * keep the pointed-to GC thing alive.
  *
  * Heap<T> objects should only be used on the heap. GC references stored on the
@@ -309,17 +317,17 @@ class MOZ_NON_MEMMOVABLE Heap : public j
   /*
    * For Heap, move semantics are equivalent to copy semantics. In C++, a
    * copy constructor taking const-ref is the way to get a single function
    * that will be used for both lvalue and rvalue copies, so we can simply
    * omit the rvalue variant.
    */
   explicit Heap(const Heap<T>& p) { init(p.ptr); }
 
-  ~Heap() { writeBarriers(ptr, SafelyInitialized<T>()); }
+  ~Heap() { postWriteBarrier(ptr, SafelyInitialized<T>()); }
 
   DECLARE_POINTER_CONSTREF_OPS(T);
   DECLARE_POINTER_ASSIGN_OPS(Heap, T);
 
   const T* address() const { return &ptr; }
 
   void exposeToActiveJS() const { js::BarrierMethods<T>::exposeToJS(ptr); }
   const T& get() const {
@@ -337,27 +345,27 @@ class MOZ_NON_MEMMOVABLE Heap : public j
   }
   explicit operator bool() {
     return bool(js::BarrierMethods<T>::asGCThingOrNull(ptr));
   }
 
  private:
   void init(const T& newPtr) {
     ptr = newPtr;
-    writeBarriers(SafelyInitialized<T>(), ptr);
+    postWriteBarrier(SafelyInitialized<T>(), ptr);
   }
 
   void set(const T& newPtr) {
     T tmp = ptr;
     ptr = newPtr;
-    writeBarriers(tmp, ptr);
+    postWriteBarrier(tmp, ptr);
   }
 
-  void writeBarriers(const T& prev, const T& next) {
-    js::BarrierMethods<T>::writeBarriers(&ptr, prev, next);
+  void postWriteBarrier(const T& prev, const T& next) {
+    js::BarrierMethods<T>::postWriteBarrier(&ptr, prev, next);
   }
 
   T ptr;
 };
 
 static MOZ_ALWAYS_INLINE bool ObjectIsTenured(JSObject* obj) {
   return !js::gc::IsInsideNursery(reinterpret_cast<js::gc::Cell*>(obj));
 }
@@ -441,29 +449,23 @@ class TenuredHeap : public js::HeapBase<
   TenuredHeap() : bits(0) {
     static_assert(sizeof(T) == sizeof(TenuredHeap<T>),
                   "TenuredHeap<T> must be binary compatible with T.");
   }
   explicit TenuredHeap(T p) : bits(0) { setPtr(p); }
   explicit TenuredHeap(const TenuredHeap<T>& p) : bits(0) {
     setPtr(p.getPtr());
   }
-  ~TenuredHeap() { pre(); }
 
   void setPtr(T newPtr) {
     MOZ_ASSERT((reinterpret_cast<uintptr_t>(newPtr) & flagsMask) == 0);
     MOZ_ASSERT(js::gc::IsCellPointerValidOrNull(newPtr));
     if (newPtr) {
       AssertGCThingMustBeTenured(newPtr);
     }
-    pre();
-    unbarrieredSetPtr(newPtr);
-  }
-
-  void unbarrieredSetPtr(T newPtr) {
     bits = (bits & flagsMask) | reinterpret_cast<uintptr_t>(newPtr);
   }
 
   void setFlags(uintptr_t flagsToSet) {
     MOZ_ASSERT((flagsToSet & ~flagsMask) == 0);
     bits |= flagsToSet;
   }
 
@@ -509,22 +511,16 @@ class TenuredHeap : public js::HeapBase<
   }
 
  private:
   enum {
     maskBits = 3,
     flagsMask = (1 << maskBits) - 1,
   };
 
-  void pre() {
-    if (T prev = unbarrieredGetPtr()) {
-      JS::IncrementalPreWriteBarrier(JS::GCCellPtr(prev));
-    }
-  }
-
   uintptr_t bits;
 };
 
 static MOZ_ALWAYS_INLINE bool ObjectIsMarkedGray(
     const JS::TenuredHeap<JSObject*>& obj) {
   return ObjectIsMarkedGray(obj.unbarrieredGetPtr());
 }
 
@@ -698,69 +694,58 @@ struct PtrBarrierMethodsBase {
     }
   }
 };
 
 }  // namespace detail
 
 template <typename T>
 struct BarrierMethods<T*> : public detail::PtrBarrierMethodsBase<T> {
-  static void writeBarriers(T** vp, T* prev, T* next) {
-    if (prev) {
-      JS::IncrementalPreWriteBarrier(JS::GCCellPtr(prev));
-    }
+  static void postWriteBarrier(T** vp, T* prev, T* next) {
     if (next) {
       JS::AssertGCThingIsNotNurseryAllocable(
           reinterpret_cast<js::gc::Cell*>(next));
     }
   }
 };
 
 template <>
 struct BarrierMethods<JSObject*>
     : public detail::PtrBarrierMethodsBase<JSObject> {
-  static void writeBarriers(JSObject** vp, JSObject* prev, JSObject* next) {
-    JS::HeapObjectWriteBarriers(vp, prev, next);
+  static void postWriteBarrier(JSObject** vp, JSObject* prev, JSObject* next) {
+    JS::HeapObjectPostWriteBarrier(vp, prev, next);
   }
   static void exposeToJS(JSObject* obj) {
     if (obj) {
       JS::ExposeObjectToActiveJS(obj);
     }
   }
 };
 
 template <>
 struct BarrierMethods<JSFunction*>
     : public detail::PtrBarrierMethodsBase<JSFunction> {
-  static void writeBarriers(JSFunction** vp, JSFunction* prev,
-                            JSFunction* next) {
-    JS::HeapObjectWriteBarriers(reinterpret_cast<JSObject**>(vp),
-                                reinterpret_cast<JSObject*>(prev),
-                                reinterpret_cast<JSObject*>(next));
+  static void postWriteBarrier(JSFunction** vp, JSFunction* prev,
+                               JSFunction* next) {
+    JS::HeapObjectPostWriteBarrier(reinterpret_cast<JSObject**>(vp),
+                                   reinterpret_cast<JSObject*>(prev),
+                                   reinterpret_cast<JSObject*>(next));
   }
   static void exposeToJS(JSFunction* fun) {
     if (fun) {
       JS::ExposeObjectToActiveJS(reinterpret_cast<JSObject*>(fun));
     }
   }
 };
 
 template <>
 struct BarrierMethods<JSString*>
     : public detail::PtrBarrierMethodsBase<JSString> {
-  static void writeBarriers(JSString** vp, JSString* prev, JSString* next) {
-    JS::HeapStringWriteBarriers(vp, prev, next);
-  }
-};
-
-template <>
-struct BarrierMethods<JSScript*>
-    : public detail::PtrBarrierMethodsBase<JSScript> {
-  static void writeBarriers(JSScript** vp, JSScript* prev, JSScript* next) {
-    JS::HeapScriptWriteBarriers(vp, prev, next);
+  static void postWriteBarrier(JSString** vp, JSString* prev, JSString* next) {
+    JS::HeapStringPostWriteBarrier(vp, prev, next);
   }
 };
 
 // Provide hash codes for Cell kinds that may be relocated and, thus, not have
 // a stable address to use as the base for a hash code. Instead of the address,
 // this hasher uses Cell::getUniqueId to provide exact matches and as a base
 // for generating hash codes.
 //
--- a/js/public/TracingAPI.h
+++ b/js/public/TracingAPI.h
@@ -414,17 +414,17 @@ inline void TraceEdge(JSTracer* trc, JS:
 }
 
 template <typename T>
 inline void TraceEdge(JSTracer* trc, JS::TenuredHeap<T>* thingp,
                       const char* name) {
   MOZ_ASSERT(thingp);
   if (T ptr = thingp->unbarrieredGetPtr()) {
     js::gc::TraceExternalEdge(trc, &ptr, name);
-    thingp->unbarrieredSetPtr(ptr);
+    thingp->setPtr(ptr);
   }
 }
 
 // Edges that are always traced as part of root marking do not require
 // incremental barriers. This function allows for marking non-barriered
 // pointers, but asserts that this happens during root marking.
 //
 // Note that while |edgep| must never be null, it is fine for |*edgep| to be
--- a/js/public/Value.h
+++ b/js/public/Value.h
@@ -1116,16 +1116,18 @@ inline bool SameType(const Value& lhs, c
 #endif
 }
 
 }  // namespace JS
 
 /************************************************************************/
 
 namespace JS {
+JS_PUBLIC_API void HeapValuePostWriteBarrier(Value* valuep, const Value& prev,
+                                             const Value& next);
 JS_PUBLIC_API void HeapValueWriteBarriers(Value* valuep, const Value& prev,
                                           const Value& next);
 
 template <>
 struct GCPolicy<JS::Value> {
   static void trace(JSTracer* trc, Value* v, const char* name) {
     js::UnsafeTraceManuallyBarrieredEdge(trc, v, name);
   }
@@ -1141,19 +1143,19 @@ struct GCPolicy<JS::Value> {
 
 namespace js {
 
 template <>
 struct BarrierMethods<JS::Value> {
   static gc::Cell* asGCThingOrNull(const JS::Value& v) {
     return v.isGCThing() ? v.toGCThing() : nullptr;
   }
-  static void writeBarriers(JS::Value* v, const JS::Value& prev,
-                            const JS::Value& next) {
-    JS::HeapValueWriteBarriers(v, prev, next);
+  static void postWriteBarrier(JS::Value* v, const JS::Value& prev,
+                               const JS::Value& next) {
+    JS::HeapValuePostWriteBarrier(v, prev, next);
   }
   static void exposeToJS(const JS::Value& v) { JS::ExposeValueToActiveJS(v); }
 };
 
 template <class Wrapper>
 class MutableValueOperations;
 
 /**
--- a/js/rust/src/heap.rs
+++ b/js/rust/src/heap.rs
@@ -29,16 +29,24 @@ pub unsafe trait Trace {
  *
  * Heap<T> instances must be traced when their containing object is traced to
  * keep the pointed-to GC thing alive.
  *
  * Heap<T> objects should only be used on the heap. GC references stored on the
  * C/C++ stack must use Rooted/Handle/MutableHandle instead.
  *
  * Type T must be a public GC pointer type.
+ *
+ * Note that the rust version of Heap<T> implements different barriers to the
+ * C++ version, which also provides features to help integration with
+ * cycle-collected C++ objects. That version has a read barrier which performs
+ * gray unmarking and also marks the contents during an incremental GC. This
+ * version has a pre-write barrier instead, and this enforces the
+ * snapshot-at-the-beginning invariant which is necessary for incremental GC in
+ * the absence of the read barrier.
  */
 #[repr(C)]
 #[derive(Debug)]
 pub struct Heap<T: GCMethods + Copy> {
     ptr: UnsafeCell<T>,
 }
 
 impl<T: GCMethods + Copy> Heap<T> {
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1798,16 +1798,37 @@ static bool EnableTrackAllocations(JSCon
   return true;
 }
 
 static bool DisableTrackAllocations(JSContext* cx, unsigned argc, Value* vp) {
   SetAllocationMetadataBuilder(cx, nullptr);
   return true;
 }
 
+static bool SetTestFilenameValidationCallback(JSContext* cx, unsigned argc,
+                                              Value* vp) {
+  CallArgs args = CallArgsFromVp(argc, vp);
+
+  // Accept all filenames that start with "safe". In system code also accept
+  // filenames starting with "system".
+  auto testCb = [](const char* filename, bool isSystemRealm) -> bool {
+    if (strstr(filename, "safe") == filename) {
+      return true;
+    }
+    if (isSystemRealm && strstr(filename, "system") == filename) {
+      return true;
+    }
+    return false;
+  };
+  JS::SetFilenameValidationCallback(testCb);
+
+  args.rval().setUndefined();
+  return true;
+}
+
 static void FinalizeExternalString(const JSStringFinalizer* fin,
                                    char16_t* chars);
 
 static const JSStringFinalizer ExternalStringFinalizer = {
     FinalizeExternalString};
 
 static void FinalizeExternalString(const JSStringFinalizer* fin,
                                    char16_t* chars) {
@@ -6194,16 +6215,21 @@ static const JSFunctionSpecWithHelp Test
 "  Start capturing the JS stack at every allocation. Note that this sets an\n"
 "  object metadata callback that will override any other object metadata\n"
 "  callback that may be set."),
 
     JS_FN_HELP("disableTrackAllocations", DisableTrackAllocations, 0, 0,
 "disableTrackAllocations()",
 "  Stop capturing the JS stack at every allocation."),
 
+    JS_FN_HELP("setTestFilenameValidationCallback", SetTestFilenameValidationCallback, 0, 0,
+"setTestFilenameValidationCallback()",
+"  Set the filename validation callback to a callback that accepts only\n"
+"  filenames starting with 'safe' or (only in system realms) 'system'."),
+
     JS_FN_HELP("newExternalString", NewExternalString, 1, 0,
 "newExternalString(str)",
 "  Copies str's chars and returns a new external string."),
 
     JS_FN_HELP("newMaybeExternalString", NewMaybeExternalString, 1, 0,
 "newMaybeExternalString(str)",
 "  Like newExternalString but uses the JS_NewMaybeExternalString API."),
 
--- a/js/src/gc/Barrier.cpp
+++ b/js/src/gc/Barrier.cpp
@@ -186,16 +186,42 @@ template struct JS_PUBLIC_API MovableCel
 template struct JS_PUBLIC_API MovableCellHasher<JSScript*>;
 template struct JS_PUBLIC_API MovableCellHasher<LazyScript*>;
 template struct JS_PUBLIC_API MovableCellHasher<ScriptSourceObject*>;
 template struct JS_PUBLIC_API MovableCellHasher<SavedFrame*>;
 template struct JS_PUBLIC_API MovableCellHasher<WasmInstanceObject*>;
 
 }  // namespace js
 
+// Post-write barrier, used by the C++ Heap<T> implementation.
+
+JS_PUBLIC_API void JS::HeapObjectPostWriteBarrier(JSObject** objp,
+                                                  JSObject* prev,
+                                                  JSObject* next) {
+  MOZ_ASSERT(objp);
+  js::InternalBarrierMethods<JSObject*>::postBarrier(objp, prev, next);
+}
+
+JS_PUBLIC_API void JS::HeapStringPostWriteBarrier(JSString** strp,
+                                                  JSString* prev,
+                                                  JSString* next) {
+  MOZ_ASSERT(strp);
+  js::InternalBarrierMethods<JSString*>::postBarrier(strp, prev, next);
+}
+
+JS_PUBLIC_API void JS::HeapValuePostWriteBarrier(JS::Value* valuep,
+                                                 const Value& prev,
+                                                 const Value& next) {
+  MOZ_ASSERT(valuep);
+  js::InternalBarrierMethods<JS::Value>::postBarrier(valuep, prev, next);
+}
+
+// Combined pre- and post-write barriers, used by the rust Heap<T>
+// implementation.
+
 JS_PUBLIC_API void JS::HeapObjectWriteBarriers(JSObject** objp, JSObject* prev,
                                                JSObject* next) {
   MOZ_ASSERT(objp);
   js::InternalBarrierMethods<JSObject*>::preBarrier(prev);
   js::InternalBarrierMethods<JSObject*>::postBarrier(objp, prev, next);
 }
 
 JS_PUBLIC_API void JS::HeapStringWriteBarriers(JSString** strp, JSString* prev,
--- a/js/src/gc/Barrier.h
+++ b/js/src/gc/Barrier.h
@@ -279,17 +279,17 @@
  * GCPtr<T>::post and HeapPtr<T>::post
  *  -> InternalBarrierMethods<T*>::postBarrier
  *      -> T::writeBarrierPost
  *  -> InternalBarrierMethods<Value>::postBarrier
  *      -> StoreBuffer::put
  *
  * Barriers for use outside of the JS engine call into the same barrier
  * implementations at InternalBarrierMethods<T>::post via an indirect call to
- * Heap(.+)WriteBarriers.
+ * Heap(.+)PostWriteBarrier.
  *
  * These clases are designed to be used to wrap GC thing pointers or values that
  * act like them (i.e. JS::Value and jsid).  It is possible to use them for
  * other types by supplying the necessary barrier implementations but this
  * is not usually necessary and should be done with caution.
  */
 
 class JSFlatString;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/script-filename-validation-1.js
@@ -0,0 +1,50 @@
+load(libdir + "asserts.js");
+
+setTestFilenameValidationCallback();
+
+// Filenames starting with "safe" are fine.
+assertEq(evaluate("2", {fileName: "safe.js"}), 2);
+assertEq(evaluate("eval(3)", {fileName: "safe.js"}), 3);
+assertEq(evaluate("Function('return 4')()", {fileName: "safe.js"}), 4);
+
+// Delazification is fine.
+function foo(x) {
+    function bar(x) { return x + 1; }
+    return bar(x);
+}
+assertEq(foo(1), 2);
+
+// These are all blocked.
+assertThrowsInstanceOf(() => evaluate("throw 2", {fileName: "unsafe.js"}), InternalError);
+assertThrowsInstanceOf(() => evaluate("throw 2", {fileName: "system.js"}), InternalError);
+assertThrowsInstanceOf(() => evaluate("throw 2", {fileName: ""}), InternalError);
+assertThrowsInstanceOf(() => evaluate("throw 2"), InternalError);
+assertThrowsInstanceOf(() => eval("throw 2"), InternalError);
+assertThrowsInstanceOf(() => Function("return 1"), InternalError);
+assertThrowsInstanceOf(() => parseModule("{ function x() {} }"), InternalError);
+
+// The error message must contain the filename.
+var ex = null;
+try {
+    evaluate("throw 2", {fileName: "file://foo.js"});
+} catch (e) {
+    ex = e;
+}
+assertEq(ex.toString(), "InternalError: unsafe filename: file://foo.js");
+
+// Off-thread parse throws too, when finishing.
+if (helperThreadCount() > 0) {
+    offThreadCompileScript('throw 1');
+    assertThrowsInstanceOf(() => runOffThreadScript(), InternalError);
+}
+
+// Unsafe filename is accepted if we opt-out.
+assertEq(evaluate("2", {fileName: "unsafe.js", skipFileNameValidation: true}), 2);
+assertEq(evaluate("3", {skipFileNameValidation: true}), 3);
+
+// In system realms we also accept filenames starting with "system".
+var systemRealm = newGlobal({newCompartment: true, systemPrincipal: true});
+assertEq(systemRealm.evaluate("1 + 2", {fileName: "system.js"}), 3);
+assertEq(systemRealm.evaluate("2 + 2", {fileName: "safe.js"}), 4);
+assertThrowsInstanceOf(() => systemRealm.evaluate("1 + 2", {fileName: "unsafe.js"}),
+                       systemRealm.InternalError);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/script-filename-validation-2.js
@@ -0,0 +1,22 @@
+load(libdir + "asserts.js");
+load(libdir + 'bytecode-cache.js');
+
+// Install the callback after evaluating the script and saving the bytecode
+// (generation 0). XDR decoding after this should throw.
+
+var g = newGlobal({cloneSingletons: true});
+test = `
+  assertEq(generation, 0);
+`;
+assertThrowsInstanceOf(() => {
+  evalWithCache(test, {
+    global: g,
+    checkAfter: function (ctx) {
+      assertEq(g.generation, 0);
+      setTestFilenameValidationCallback();
+    }
+  });
+}, g.InternalError);
+
+// Generation should be 1 (XDR decoding threw an exception).
+assertEq(g.generation, 1);
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -179,16 +179,17 @@ MSG_DEF(JSMSG_BUILD_ID_NOT_AVAILABLE,  0
 MSG_DEF(JSMSG_BYTECODE_TOO_BIG,        2, JSEXN_INTERNALERR, "bytecode {0} too large (limit {1})")
 MSG_DEF(JSMSG_NEED_DIET,               1, JSEXN_INTERNALERR, "{0} too large")
 MSG_DEF(JSMSG_OUT_OF_MEMORY,           0, JSEXN_INTERNALERR, "out of memory")
 MSG_DEF(JSMSG_OVER_RECURSED,           0, JSEXN_INTERNALERR, "too much recursion")
 MSG_DEF(JSMSG_TOO_BIG_TO_ENCODE,       0, JSEXN_INTERNALERR, "data are to big to encode")
 MSG_DEF(JSMSG_TOO_DEEP,                1, JSEXN_INTERNALERR, "{0} nested too deeply")
 MSG_DEF(JSMSG_UNCAUGHT_EXCEPTION,      1, JSEXN_INTERNALERR, "uncaught exception: {0}")
 MSG_DEF(JSMSG_UNKNOWN_FORMAT,          1, JSEXN_INTERNALERR, "unknown bytecode format {0}")
+MSG_DEF(JSMSG_UNSAFE_FILENAME,         1, JSEXN_INTERNALERR, "unsafe filename: {0}")
 
 // Frontend
 MSG_DEF(JSMSG_ACCESSOR_WRONG_ARGS,     3, JSEXN_SYNTAXERR, "{0} functions must have {1} argument{2}")
 MSG_DEF(JSMSG_ARRAY_INIT_TOO_BIG,      0, JSEXN_INTERNALERR, "array initializer too large")
 MSG_DEF(JSMSG_AS_AFTER_IMPORT_STAR,    0, JSEXN_SYNTAXERR, "missing keyword 'as' after import *")
 MSG_DEF(JSMSG_AS_AFTER_RESERVED_WORD,  1, JSEXN_SYNTAXERR, "missing keyword 'as' after reserved word '{0}'")
 MSG_DEF(JSMSG_AWAIT_IN_PARAMETER,      0, JSEXN_SYNTAXERR, "await expression can't be used in parameter")
 MSG_DEF(JSMSG_AWAIT_OUTSIDE_ASYNC,     0, JSEXN_SYNTAXERR, "await is only valid in async functions and async generators")
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1837,16 +1837,21 @@ JS_PUBLIC_API void JS::AssertObjectBelon
   MOZ_RELEASE_ASSERT(CurrentThreadCanAccessRuntime(rt));
 }
 
 JS_PUBLIC_API void SetHelperThreadTaskCallback(
     void (*callback)(js::RunnableTask*)) {
   HelperThreadTaskCallback = callback;
 }
 
+JS_PUBLIC_API void JS::SetFilenameValidationCallback(
+    JS::FilenameValidationCallback cb) {
+  js::gFilenameValidationCallback = cb;
+}
+
 /*** Standard internal methods **********************************************/
 
 JS_PUBLIC_API bool JS_GetPrototype(JSContext* cx, HandleObject obj,
                                    MutableHandleObject result) {
   cx->check(obj);
   return GetPrototype(cx, obj, result);
 }
 
@@ -3546,16 +3551,17 @@ void JS::TransitiveCompileOptions::copyP
 void JS::ReadOnlyCompileOptions::copyPODNonTransitiveOptions(
     const ReadOnlyCompileOptions& rhs) {
   lineno = rhs.lineno;
   column = rhs.column;
   scriptSourceOffset = rhs.scriptSourceOffset;
   isRunOnce = rhs.isRunOnce;
   noScriptRval = rhs.noScriptRval;
   nonSyntacticScope = rhs.nonSyntacticScope;
+  skipFilenameValidation_ = rhs.skipFilenameValidation_;
 }
 
 JS::OwningCompileOptions::OwningCompileOptions(JSContext* cx)
     : ReadOnlyCompileOptions(),
       elementRoot(cx),
       elementAttributeNameRoot(cx),
       introductionScriptRoot(cx),
       scriptOrModuleRoot(cx) {}
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -391,16 +391,30 @@ namespace JS {
 JS_PUBLIC_API bool InitSelfHostedCode(JSContext* cx);
 
 /**
  * Asserts (in debug and release builds) that `obj` belongs to the current
  * thread's context.
  */
 JS_PUBLIC_API void AssertObjectBelongsToCurrentThread(JSObject* obj);
 
+/**
+ * Install a process-wide callback to validate script filenames. The JS engine
+ * will invoke this callback for each JS script it parses or XDR decodes.
+ *
+ * If the callback returns |false|, an exception is thrown and parsing/decoding
+ * will be aborted.
+ *
+ * See also CompileOptions::setSkipFilenameValidation to opt-out of the callback
+ * for specific parse jobs.
+ */
+using FilenameValidationCallback = bool (*)(const char* filename,
+                                            bool isSystemRealm);
+JS_PUBLIC_API void SetFilenameValidationCallback(FilenameValidationCallback cb);
+
 } /* namespace JS */
 
 /**
  * Set callback to send tasks to XPCOM thread pools
  */
 JS_PUBLIC_API void SetHelperThreadTaskCallback(
     void (*callback)(js::RunnableTask*));
 
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -1805,16 +1805,23 @@ static bool ParseCompileOptions(JSContex
     }
     fileNameBytes = JS_EncodeStringToLatin1(cx, s);
     if (!fileNameBytes) {
       return false;
     }
     options.setFile(fileNameBytes.get());
   }
 
+  if (!JS_GetProperty(cx, opts, "skipFileNameValidation", &v)) {
+    return false;
+  }
+  if (!v.isUndefined()) {
+    options.setSkipFilenameValidation(ToBoolean(v));
+  }
+
   if (!JS_GetProperty(cx, opts, "element", &v)) {
     return false;
   }
   if (v.isObject()) {
     options.setElement(&v.toObject());
   }
 
   if (!JS_GetProperty(cx, opts, "elementAttributeName", &v)) {
@@ -8468,16 +8475,17 @@ static const JSFunctionSpecWithHelp shel
 
     JS_FN_HELP("evaluate", Evaluate, 2, 0,
 "evaluate(code[, options])",
 "  Evaluate code as though it were the contents of a file.\n"
 "  options is an optional object that may have these properties:\n"
 "      isRunOnce: use the isRunOnce compiler option (default: false)\n"
 "      noScriptRval: use the no-script-rval compiler option (default: false)\n"
 "      fileName: filename for error messages and debug info\n"
+"      skipFileNameValidation: skip the filename-validation callback\n"
 "      lineNumber: starting line number for error messages and debug info\n"
 "      columnNumber: starting column number for error messages and debug info\n"
 "      global: global in which to execute the code\n"
 "      newContext: if true, create and use a new cx (default: false)\n"
 "      catchTermination: if true, catch termination (failure without\n"
 "         an exception value, as for slow scripts or out-of-memory)\n"
 "         and return 'terminated'\n"
 "      element: if present with value |v|, convert |v| to an object |o| and\n"
--- a/js/src/vm/JSContext-inl.h
+++ b/js/src/vm/JSContext-inl.h
@@ -299,17 +299,23 @@ inline js::LifoAlloc& JSContext::typeLif
 
 inline js::Nursery& JSContext::nursery() { return runtime()->gc.nursery(); }
 
 inline void JSContext::minorGC(JS::GCReason reason) {
   runtime()->gc.minorGC(reason);
 }
 
 inline bool JSContext::runningWithTrustedPrincipals() {
-  return !realm() || realm()->principals() == runtime()->trustedPrincipals();
+  if (!realm()) {
+    return true;
+  }
+  if (!runtime()->trustedPrincipals()) {
+    return false;
+  }
+  return realm()->principals() == runtime()->trustedPrincipals();
 }
 
 inline void JSContext::enterRealm(JS::Realm* realm) {
   // We should never enter a realm while in the atoms zone.
   MOZ_ASSERT_IF(zone(), !zone()->isAtomsZone());
 
   realm->enter();
   setRealm(realm);
--- a/js/src/vm/JSContext.h
+++ b/js/src/vm/JSContext.h
@@ -799,17 +799,17 @@ struct JSContext : public JS::RootingCon
 
   bool isThrowingOverRecursed() const { return throwing && overRecursed_; }
   bool isPropagatingForcedReturn() const { return propagatingForcedReturn_; }
   void setPropagatingForcedReturn() { propagatingForcedReturn_ = true; }
   void clearPropagatingForcedReturn() { propagatingForcedReturn_ = false; }
 
   /*
    * See JS_SetTrustedPrincipals in jsapi.h.
-   * Note: !cx->compartment is treated as trusted.
+   * Note: !cx->realm() is treated as trusted.
    */
   inline bool runningWithTrustedPrincipals();
 
   JS_FRIEND_API size_t
   sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 
   void trace(JSTracer* trc);
 
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -1716,28 +1716,58 @@ ScriptSourceObject* ScriptSourceObject::
 
 ScriptSourceObject* ScriptSourceObject::unwrappedCanonical() const {
   MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtimeFromAnyThread()));
 
   JSObject* obj = &getReservedSlot(CANONICAL_SLOT).toObject();
   return &UncheckedUnwrap(obj)->as<ScriptSourceObject>();
 }
 
+static MOZ_MUST_USE bool MaybeValidateFilename(
+    JSContext* cx, HandleScriptSourceObject sso,
+    const ReadOnlyCompileOptions& options) {
+  // When parsing off-thread we want to do filename validation on the main
+  // thread. This makes off-thread parsing more pure and is simpler because we
+  // can't easily throw exceptions off-thread.
+  MOZ_ASSERT(!cx->isHelperThreadContext());
+
+  if (!gFilenameValidationCallback) {
+    return true;
+  }
+
+  const char* filename = sso->source()->filename();
+  if (!filename || options.skipFilenameValidation()) {
+    return true;
+  }
+
+  if (gFilenameValidationCallback(filename, cx->realm()->isSystem())) {
+    return true;
+  }
+
+  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_UNSAFE_FILENAME,
+                           filename);
+  return false;
+}
+
 /* static */
 bool ScriptSourceObject::initFromOptions(
     JSContext* cx, HandleScriptSourceObject source,
     const ReadOnlyCompileOptions& options) {
   cx->releaseCheck(source);
   MOZ_ASSERT(source->isCanonical());
   MOZ_ASSERT(source->getReservedSlot(ELEMENT_SLOT).isMagic(JS_GENERIC_MAGIC));
   MOZ_ASSERT(
       source->getReservedSlot(ELEMENT_PROPERTY_SLOT).isMagic(JS_GENERIC_MAGIC));
   MOZ_ASSERT(source->getReservedSlot(INTRODUCTION_SCRIPT_SLOT)
                  .isMagic(JS_GENERIC_MAGIC));
 
+  if (!MaybeValidateFilename(cx, source, options)) {
+    return false;
+  }
+
   RootedObject element(cx, options.element());
   RootedString elementAttributeName(cx, options.elementAttributeName());
   if (!initElementProperties(cx, source, element, elementAttributeName)) {
     return false;
   }
 
   // There is no equivalent of cross-compartment wrappers for scripts. If the
   // introduction script and ScriptSourceObject are in different compartments,
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -66,16 +66,18 @@ using mozilla::NegativeInfinity;
 using mozilla::PodZero;
 using mozilla::PositiveInfinity;
 
 /* static */ MOZ_THREAD_LOCAL(JSContext*) js::TlsContext;
 /* static */
 Atomic<size_t> JSRuntime::liveRuntimesCount;
 Atomic<JS::LargeAllocationFailureCallback> js::OnLargeAllocationFailure;
 
+JS::FilenameValidationCallback js::gFilenameValidationCallback = nullptr;
+
 namespace js {
 void (*HelperThreadTaskCallback)(js::RunnableTask*);
 
 bool gCanUseExtraThreads = true;
 }  // namespace js
 
 void js::DisableExtraThreads() { gCanUseExtraThreads = false; }
 
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -1099,15 +1099,17 @@ extern const JSSecurityCallbacks NullSec
 // and may be null. See comment in jsapi.h.
 extern mozilla::Atomic<JS::LargeAllocationFailureCallback>
     OnLargeAllocationFailure;
 
 // This callback is set by JS::SetBuildIdOp and may be null. See comment in
 // jsapi.h.
 extern mozilla::Atomic<JS::BuildIdOp> GetBuildId;
 
+extern JS::FilenameValidationCallback gFilenameValidationCallback;
+
 // This callback is set by js::SetHelperThreadTaskCallback and may be null.
 // See comment in jsapi.h.
 extern void (*HelperThreadTaskCallback)(js::RunnableTask*);
 
 } /* namespace js */
 
 #endif /* vm_Runtime_h */
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2620,16 +2620,17 @@ void js::FillSelfHostingCompileOptions(C
    * of self-hosted builtins.
    *
    * Additionally, the special syntax callFunction(fun, receiver, ...args)
    * is supported, for which bytecode is emitted that invokes |fun| with
    * |receiver| as the this-object and ...args as the arguments.
    */
   options.setIntroductionType("self-hosted");
   options.setFileAndLine("self-hosted", 1);
+  options.setSkipFilenameValidation(true);
   options.setSelfHostingMode(true);
   options.setForceFullParse();
   options.werrorOption = true;
   options.extraWarningsOption = true;
   options.setForceStrictMode();
 }
 
 GlobalObject* JSRuntime::createSelfHostingGlobal(JSContext* cx) {
--- a/js/xpconnect/loader/ScriptPreloader.cpp
+++ b/js/xpconnect/loader/ScriptPreloader.cpp
@@ -368,17 +368,17 @@ void ScriptPreloader::FinishContentStart
   // a perf impact.
   mozilla::Telemetry::Accumulate(
       mozilla::Telemetry::MEMORY_UNIQUE_CONTENT_STARTUP,
       nsMemoryReporterManager::ResidentUnique() / 1024);
 #endif
 }
 
 bool ScriptPreloader::WillWriteScripts() {
-  return Active() && (XRE_IsParentProcess() || mChildActor);
+  return !mDataPrepared && (XRE_IsParentProcess() || mChildActor);
 }
 
 Result<nsCOMPtr<nsIFile>, nsresult> ScriptPreloader::GetCacheFile(
     const nsAString& suffix) {
   NS_ENSURE_TRUE(mProfD, Err(NS_ERROR_NOT_INITIALIZED));
 
   nsCOMPtr<nsIFile> cacheFile;
   MOZ_TRY(mProfD->Clone(getter_AddRefs(cacheFile)));
--- a/js/xpconnect/loader/ScriptPreloader.h
+++ b/js/xpconnect/loader/ScriptPreloader.h
@@ -396,18 +396,18 @@ class ScriptPreloader : public nsIObserv
 
   void PrepareCacheWriteInternal();
 
   void CacheWriteComplete();
 
   void FinishContentStartup();
 
   // Returns true if scripts added to the cache now will be encoded and
-  // written to the cache. If we've passed the startup script loading
-  // window, or this is a content process which hasn't been asked to return
+  // written to the cache. If we've already encoded scripts for the cache
+  // write, or this is a content process which hasn't been asked to return
   // script bytecode, this will return false.
   bool WillWriteScripts();
 
   // Returns a file pointer for the cache file with the given name in the
   // current profile.
   Result<nsCOMPtr<nsIFile>, nsresult> GetCacheFile(const nsAString& suffix);
 
   // Waits for the given cached script to finish compiling off-thread, or
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -10415,16 +10415,17 @@ Maybe<MotionPathData> nsLayoutUtils::Res
     anchorPoint.x += CSSPixel::FromAppUnits(aFrame->GetPosition().x);
     anchorPoint.y += CSSPixel::FromAppUnits(aFrame->GetPosition().y);
   }
 
   return Some(
       MotionPathData{point - anchorPoint.ToUnknownPoint(), angle, shift});
 }
 
+// NOTE: Returns Nothing() if |aFrame| is not in out-of-process.
 static Maybe<ScreenRect> GetFrameVisibleRectOnScreen(const nsIFrame* aFrame) {
   // We actually want the in-process top prescontext here.
   nsPresContext* topContextInProcess =
       aFrame->PresContext()->GetToplevelContentDocumentPresContext();
   if (!topContextInProcess) {
     // We are in chrome process.
     return Nothing();
   }
@@ -10438,17 +10439,17 @@ static Maybe<ScreenRect> GetFrameVisible
   BrowserChild* browserChild = BrowserChild::GetFrom(docShell);
   if (!browserChild) {
     // We are not in out-of-process iframe.
     return Nothing();
   }
 
   if (!browserChild->GetEffectsInfo().IsVisible()) {
     // There is no visible rect on this iframe at all.
-    return Nothing();
+    return Some(ScreenRect());
   }
 
   nsIFrame* rootFrame = topContextInProcess->PresShell()->GetRootFrame();
   nsRect transformedToIFrame = nsLayoutUtils::TransformFrameRectToAncestor(
       aFrame, aFrame->GetRectRelativeToSelf(), rootFrame);
 
   LayoutDeviceRect rectInLayoutDevicePixel = LayoutDeviceRect::FromAppUnits(
       transformedToIFrame, topContextInProcess->AppUnitsPerDevPixel());
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -133,16 +133,22 @@ bool nsPresContext::IsDOMPaintEventPendi
   return false;
 }
 
 void nsPresContext::ForceReflowForFontInfoUpdate() {
   // Flush the device context's font cache, so that we won't risk getting
   // stale nsFontMetrics objects from it.
   DeviceContext()->FlushFontCache();
 
+  // If there's a user font set, discard any src:local() faces it may have
+  // loaded because their font entries may no longer be valid.
+  if (Document()->GetFonts()) {
+    Document()->GetFonts()->GetUserFontSet()->ForgetLocalFaces();
+  }
+
   // We can trigger reflow by pretending a font.* preference has changed;
   // this is the same mechanism as gfxPlatform::ForceGlobalReflow() uses
   // if new fonts are installed during the session, for example.
   PreferenceChanged("font.internaluseonly.changed");
 }
 
 static bool IsVisualCharset(NotNull<const Encoding*> aCharset) {
   return aCharset == ISO_8859_8_ENCODING;
--- a/layout/generic/ReflowInput.cpp
+++ b/layout/generic/ReflowInput.cpp
@@ -2378,20 +2378,25 @@ void ReflowInput::InitConstraints(
     } else if (NS_FRAME_GET_TYPE(mFrameType) == NS_CSS_FRAME_TYPE_ABSOLUTE) {
       // XXX not sure if this belongs here or somewhere else - cwk
       InitAbsoluteConstraints(aPresContext, cbri,
                               cbSize.ConvertTo(cbri->GetWritingMode(), wm),
                               aFrameType);
     } else {
       AutoMaybeDisableFontInflation an(mFrame);
 
-      bool isBlock = NS_CSS_FRAME_TYPE_BLOCK == NS_FRAME_GET_TYPE(mFrameType);
+      // Note: all flex and grid items are block-level, even if they have
+      // e.g. 'display:-moz-box' (which doesn't get NS_CSS_FRAME_TYPE_BLOCK).
+      const bool isBlockLevel =
+          NS_CSS_FRAME_TYPE_BLOCK == NS_FRAME_GET_TYPE(mFrameType) ||
+          mFrame->IsFlexOrGridItem();
       typedef nsIFrame::ComputeSizeFlags ComputeSizeFlags;
-      ComputeSizeFlags computeSizeFlags =
-          isBlock ? ComputeSizeFlags::eDefault : ComputeSizeFlags::eShrinkWrap;
+      ComputeSizeFlags computeSizeFlags = isBlockLevel
+                                              ? ComputeSizeFlags::eDefault
+                                              : ComputeSizeFlags::eShrinkWrap;
       if (mFlags.mIClampMarginBoxMinSize) {
         computeSizeFlags = ComputeSizeFlags(
             computeSizeFlags | ComputeSizeFlags::eIClampMarginBoxMinSize);
       }
       if (mFlags.mBClampMarginBoxMinSize) {
         computeSizeFlags = ComputeSizeFlags(
             computeSizeFlags | ComputeSizeFlags::eBClampMarginBoxMinSize);
       }
@@ -2429,17 +2434,17 @@ void ReflowInput::InitConstraints(
             mStyleMargin->mMargin.GetIEnd(wm).IsAuto()) {
           computeSizeFlags = ComputeSizeFlags(computeSizeFlags |
                                               ComputeSizeFlags::eShrinkWrap);
         }
       } else {
         // Make sure legend frames with display:block and width:auto still
         // shrink-wrap.
         // Also shrink-wrap blocks that are orthogonal to their container.
-        if (isBlock &&
+        if (isBlockLevel &&
             ((aFrameType == LayoutFrameType::Legend &&
               mFrame->Style()->GetPseudoType() !=
                   PseudoStyleType::scrolledContent) ||
              (aFrameType == LayoutFrameType::Scroll &&
               mFrame->GetContentInsertionFrame()->IsLegendFrame()) ||
              (mCBReflowInput &&
               mCBReflowInput->GetWritingMode().IsOrthogonalTo(mWritingMode)))) {
           computeSizeFlags = ComputeSizeFlags(computeSizeFlags |
@@ -2480,17 +2485,17 @@ void ReflowInput::InitConstraints(
       ComputedBSize() = size.BSize(wm);
       NS_ASSERTION(ComputedISize() >= 0, "Bogus inline-size");
       NS_ASSERTION(
           ComputedBSize() == NS_UNCONSTRAINEDSIZE || ComputedBSize() >= 0,
           "Bogus block-size");
 
       // Exclude inline tables, side captions, outside ::markers, flex and grid
       // items from block margin calculations.
-      if (isBlock && !IsSideCaption(mFrame, mStyleDisplay, cbwm) &&
+      if (isBlockLevel && !IsSideCaption(mFrame, mStyleDisplay, cbwm) &&
           mStyleDisplay->mDisplay != StyleDisplay::InlineTable &&
           !alignCB->IsFlexOrGridContainer() &&
           !(mFrame->Style()->GetPseudoType() == PseudoStyleType::marker &&
             mFrame->GetParent()->StyleList()->mListStylePosition ==
                 NS_STYLE_LIST_STYLE_POSITION_OUTSIDE)) {
         CalculateBlockSideMargins(aFrameType);
       }
     }
--- a/layout/generic/TextDrawTarget.h
+++ b/layout/generic/TextDrawTarget.h
@@ -603,21 +603,16 @@ class TextDrawTarget : public DrawTarget
 
   already_AddRefed<GradientStops> CreateGradientStops(
       GradientStop* aStops, uint32_t aNumStops,
       ExtendMode aExtendMode) const override {
     MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
     return nullptr;
   }
 
-  void* GetNativeSurface(NativeSurfaceType aType) override {
-    MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
-    return nullptr;
-  }
-
   void DetachAllSnapshots() override {
     MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
   }
 };
 
 }  // namespace layout
 }  // namespace mozilla
 
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -7588,17 +7588,18 @@ nsresult nsTextFrame::GetCharacterRectsI
     rect.x = point.x;
     rect.y = point.y;
 
     nscoord iSize = 0;
     if (aInOffset < kContentEnd) {
       gfxSkipCharsIterator nextIter(iter);
       nextIter.AdvanceOriginal(1);
       if (!nextIter.IsOriginalCharSkipped() &&
-          !mTextRun->IsClusterStart(nextIter.GetSkippedOffset())) {
+          !mTextRun->IsClusterStart(nextIter.GetSkippedOffset()) &&
+          nextIter.GetOriginalOffset() < kContentEnd) {
         FindClusterEnd(mTextRun, kContentEnd, &nextIter);
       }
 
       gfxFloat advance = mTextRun->GetAdvanceWidth(
           Range(iter.GetSkippedOffset(), nextIter.GetSkippedOffset()),
           &properties);
       iSize = NSToCoordCeilClamped(advance);
     }
new file mode 100644
--- /dev/null
+++ b/layout/reftests/box/box-as-grid-or-flex-item-1-ref.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<title>Reference for bug 1580302</title>
+<style>
+  .grid, .flex {
+    width: 60px;
+    height: 60px;
+    border: 1px solid black;
+  }
+  .grid { display: grid; }
+  .flex { display: flex; }
+  .fCol { flex-direction: column; }
+
+  .item {
+    background: lightblue;
+  }
+  .flexible {
+    flex: 1;
+  }
+</style>
+<body>
+  <!-- The item should fill the grid here (by virtue of the default-stretchy
+       behavior of justify-items and align-items): -->
+  <div class="grid">
+    <div class="item">e</div>
+  </div>
+
+  <!-- For the rest, the item should fill the flex container in the cross axis,
+       and if it's flexible, also fill the container in the main axis. -->
+  <div class="flex">
+    <div class="item">e</div>
+  </div>
+  <div class="flex">
+    <div class="item flexible">e</div>
+  </div>
+  <div class="flex fCol">
+    <div class="item">e</div>
+  </div>
+  <div class="flex fCol">
+    <div class="item flexible">e</div>
+  </div>
+</body>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/box/box-as-grid-or-flex-item-1.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<title>Test for bug 1580302</title>
+<style>
+  .grid, .flex {
+    width: 60px;
+    height: 60px;
+    border: 1px solid black;
+  }
+  .grid { display: grid; }
+  .flex { display: flex; }
+  .fCol { flex-direction: column; }
+
+  .item {
+    display: -moz-box;
+    background: lightblue;
+  }
+  .flexible {
+    flex: 1;
+  }
+</style>
+<body>
+  <!-- The item should fill the grid here (by virtue of justify-items/align-items
+       default behavior): -->
+  <div class="grid">
+    <div class="item">e</div>
+  </div>
+
+  <!-- For the rest, the item should fill the flex container in the cross axis,
+       and if it's flexible, also fill the container in the main axis. -->
+  <div class="flex">
+    <div class="item">e</div>
+  </div>
+  <div class="flex">
+    <div class="item flexible">e</div>
+  </div>
+  <div class="flex fCol">
+    <div class="item">e</div>
+  </div>
+  <div class="flex fCol">
+    <div class="item flexible">e</div>
+  </div>
+</body>
--- a/layout/reftests/box/reftest.list
+++ b/layout/reftests/box/reftest.list
@@ -1,8 +1,10 @@
+test-pref(layout.css.xul-box-display-values.content.enabled,true) test-pref(layout.css.xul-box-display-values.survive-blockification.enabled,true) == box-as-grid-or-flex-item-1.html box-as-grid-or-flex-item-1-ref.html
+
 == flexbox-abspos-container-1a.html flexbox-abspos-container-1-ref.html
 == flexbox-abspos-container-1b.html flexbox-abspos-container-1-ref.html
 == flexbox-abspos-container-1c.html flexbox-abspos-container-1-ref.html
 == flexbox-abspos-container-1d.html flexbox-abspos-container-1-ref.html
 == flexbox-abspos-container-2.html  flexbox-abspos-container-2-ref.html
 == flexbox-attributes-no-box-horizontal.xhtml flexbox-attributes-no-box-horizontal-ref.xhtml
 == flexbox-attributes-no-box-vertical.xhtml flexbox-attributes-no-box-vertical-ref.xhtml
 == flexbox-attributes-no-input-horizontal.xhtml flexbox-attributes-no-input-horizontal-ref.xhtml
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
@@ -663,34 +663,34 @@ void PeerConnectionMedia::IceConnectionS
   if (mParent) {
     mParent->IceConnectionStateChange(aState);
   }
 }
 
 void PeerConnectionMedia::OnCandidateFound_m(
     const std::string& aTransportId, const CandidateInfo& aCandidateInfo) {
   ASSERT_ON_THREAD(mMainThread);
-  if (mParent) {
-    mParent->OnCandidateFound(aTransportId, aCandidateInfo);
-  }
-
   if (mStunAddrsRequest && !aCandidateInfo.mMDNSAddress.empty()) {
     MOZ_ASSERT(!aCandidateInfo.mActualAddress.empty());
 
     auto itor = mRegisteredMDNSHostnames.find(aCandidateInfo.mMDNSAddress);
 
     // We'll see the address twice if we're generating both UDP and TCP
     // candidates.
     if (itor == mRegisteredMDNSHostnames.end()) {
       mRegisteredMDNSHostnames.insert(aCandidateInfo.mMDNSAddress);
       mStunAddrsRequest->SendRegisterMDNSHostname(
           nsCString(aCandidateInfo.mMDNSAddress.c_str()),
           nsCString(aCandidateInfo.mActualAddress.c_str()));
     }
   }
+
+  if (mParent) {
+    mParent->OnCandidateFound(aTransportId, aCandidateInfo);
+  }
 }
 
 void PeerConnectionMedia::AlpnNegotiated_s(const std::string& aAlpn) {
   GetMainThread()->Dispatch(
       WrapRunnable(this, &PeerConnectionMedia::AlpnNegotiated_m, aAlpn),
       NS_DISPATCH_NORMAL);
 }
 
--- a/media/webrtc/signaling/src/sdp/sipcc/sdp_attr.c
+++ b/media/webrtc/signaling/src/sdp/sipcc/sdp_attr.c
@@ -1475,17 +1475,17 @@ sdp_result_e sdp_parse_attr_fmtp (sdp_t 
                 sdp_p->debug_str);
             sdp_p->conf_p->num_invalid_param++;
             SDP_FREE(temp_ptr);
             return (SDP_INVALID_PARAMETER);
         }
 
         for (i = low_val; i <= high_val; i++) {
             mapword = i/SDP_NE_BITS_PER_WORD;
-            bmap = SDP_NE_BIT_0 << (i%32);
+            bmap = ((unsigned)SDP_NE_BIT_0) << (i%32);
             fmtp_p->bmap[mapword] |= bmap;
         }
         if (high_val > fmtp_p->maxval) {
             fmtp_p->maxval = high_val;
         }
     }
 
     if (fmtp_p->maxval == 0) {
--- a/media/webrtc/trunk/webrtc/modules/video_capture/linux/device_info_linux.cc
+++ b/media/webrtc/trunk/webrtc/modules/video_capture/linux/device_info_linux.cc
@@ -38,47 +38,37 @@ namespace videocapturemodule {
 VideoCaptureModule::DeviceInfo* VideoCaptureImpl::CreateDeviceInfo() {
   return new videocapturemodule::DeviceInfoLinux();
 }
 
 #ifdef WEBRTC_LINUX
 void DeviceInfoLinux::HandleEvent(inotify_event* event, int fd)
 {
     if (event->mask & IN_CREATE) {
-        if (fd == _fd_v4l || fd == _fd_snd) {
+        if (fd == _fd_v4l) {
             DeviceChange();
         } else if ((event->mask & IN_ISDIR) && (fd == _fd_dev)) {
             if (_wd_v4l < 0) {
                 // Sometimes inotify_add_watch failed if we call it immediately after receiving this event
                 // Adding 5ms delay to let file system settle down
                 usleep(5*1000);
                 _wd_v4l = inotify_add_watch(_fd_v4l, "/dev/v4l/by-path/", IN_CREATE | IN_DELETE | IN_DELETE_SELF);
                 if (_wd_v4l >= 0) {
                     DeviceChange();
                 }
             }
-            if (_wd_snd < 0) {
-                usleep(5*1000);
-                _wd_snd = inotify_add_watch(_fd_snd, "/dev/snd/by-path/", IN_CREATE | IN_DELETE | IN_DELETE_SELF);
-                if (_wd_snd >= 0) {
-                    DeviceChange();
-                }
-            }
         }
     } else if (event->mask & IN_DELETE) {
-        if (fd == _fd_v4l || fd == _fd_snd) {
+        if (fd == _fd_v4l) {
             DeviceChange();
         }
     } else if (event->mask & IN_DELETE_SELF) {
         if (fd == _fd_v4l) {
             inotify_rm_watch(_fd_v4l, _wd_v4l);
             _wd_v4l = -1;
-        } else if (fd == _fd_snd) {
-            inotify_rm_watch(_fd_snd, _wd_snd);
-            _wd_snd = -1;
         } else {
             assert(false);
         }
     }
 }
 
 int DeviceInfoLinux::EventCheck(int fd)
 {
@@ -135,55 +125,43 @@ int DeviceInfoLinux::ProcessInotifyEvent
                 break;
             }
         }
         if (EventCheck(_fd_v4l) > 0) {
             if (HandleEvents(_fd_v4l) < 0) {
                 break;
             }
         }
-        if (EventCheck(_fd_snd) > 0) {
-            if (HandleEvents(_fd_snd) < 0) {
-                break;
-            }
-        }
     }
     return 0;
 }
 
 bool DeviceInfoLinux::InotifyEventThread(void* obj)
 {
     return static_cast<DeviceInfoLinux*> (obj)->InotifyProcess();
 }
 
 bool DeviceInfoLinux::InotifyProcess()
 {
     _fd_v4l = inotify_init();
-    _fd_snd = inotify_init();
     _fd_dev = inotify_init();
-    if (_fd_v4l >= 0 && _fd_snd >= 0 && _fd_dev >= 0) {
+    if (_fd_v4l >= 0 && _fd_dev >= 0) {
         _wd_v4l = inotify_add_watch(_fd_v4l, "/dev/v4l/by-path/", IN_CREATE | IN_DELETE | IN_DELETE_SELF);
-        _wd_snd = inotify_add_watch(_fd_snd, "/dev/snd/by-path/", IN_CREATE | IN_DELETE | IN_DELETE_SELF);
         _wd_dev = inotify_add_watch(_fd_dev, "/dev/", IN_CREATE);
         ProcessInotifyEvents();
 
         if (_wd_v4l >= 0) {
           inotify_rm_watch(_fd_v4l, _wd_v4l);
         }
 
-        if (_wd_snd >= 0) {
-          inotify_rm_watch(_fd_snd, _wd_snd);
-        }
-
         if (_wd_dev >= 0) {
           inotify_rm_watch(_fd_dev, _wd_dev);
         }
 
         close(_fd_v4l);
-        close(_fd_snd);
         close(_fd_dev);
         return true;
     } else {
         return false;
     }
 }
 #endif
 
--- a/media/webrtc/trunk/webrtc/modules/video_capture/linux/device_info_linux.h
+++ b/media/webrtc/trunk/webrtc/modules/video_capture/linux/device_info_linux.h
@@ -59,15 +59,15 @@ private:
 #ifdef WEBRTC_LINUX
     void HandleEvent(inotify_event* event, int fd);
     int EventCheck(int fd);
     int HandleEvents(int fd);
     int ProcessInotifyEvents();
     std::unique_ptr<rtc::PlatformThread> _inotifyEventThread;
     static bool InotifyEventThread(void*);
     bool InotifyProcess();
-    int _fd_v4l, _fd_snd, _fd_dev, _wd_v4l, _wd_snd, _wd_dev; /* accessed on InotifyEventThread thread */
+    int _fd_v4l, _fd_dev, _wd_v4l, _wd_dev; /* accessed on InotifyEventThread thread */
     Atomic32 _isShutdown;
 #endif
 };
 }  // namespace videocapturemodule
 }  // namespace webrtc
 #endif // MODULES_VIDEO_CAPTURE_MAIN_SOURCE_LINUX_DEVICE_INFO_LINUX_H_
--- a/media/webrtc/trunk/webrtc/modules/video_capture/objc/device_info_objc.mm
+++ b/media/webrtc/trunk/webrtc/modules/video_capture/objc/device_info_objc.mm
@@ -108,27 +108,31 @@
 
   NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
 
   id deviceWasConnectedObserver = [notificationCenter addObserverForName:AVCaptureDeviceWasConnectedNotification
       object:nil
       queue:[NSOperationQueue mainQueue]
       usingBlock:^(NSNotification *note) {
           [_lock lock];
-          if(_owner)
+          AVCaptureDevice *device = [note object];
+          BOOL isVideoDevice = [device hasMediaType: AVMediaTypeVideo];
+          if(isVideoDevice && _owner)
               _owner->DeviceChange();
           [_lock unlock];
       }];
 
   id deviceWasDisconnectedObserver = [notificationCenter addObserverForName:AVCaptureDeviceWasDisconnectedNotification
       object:nil
       queue:[NSOperationQueue mainQueue]
       usingBlock:^(NSNotification *note) {
           [_lock lock];
-          if(_owner)
+          AVCaptureDevice *device = [note object];
+          BOOL isVideoDevice = [device hasMediaType: AVMediaTypeVideo];
+          if(isVideoDevice && _owner)
               _owner->DeviceChange();
           [_lock unlock];
       }];
 
   _observers = [[NSArray alloc] initWithObjects:deviceWasConnectedObserver, deviceWasDisconnectedObserver, nil];
 }
 
 @end
--- a/media/webrtc/trunk/webrtc/modules/video_capture/windows/device_info_ds.cc
+++ b/media/webrtc/trunk/webrtc/modules/video_capture/windows/device_info_ds.cc
@@ -18,26 +18,26 @@
 #include <dvdmedia.h>
 #include <Dbt.h>
 #include <ks.h>
 #include <ksmedia.h>
 
 namespace webrtc {
 namespace videocapturemodule {
 
-BOOL isCaptureDevice(DEV_BROADCAST_HDR *pHdr)
+BOOL isVideoDevice(DEV_BROADCAST_HDR *pHdr)
 {
   if (pHdr == NULL) {
     return FALSE;
   }
   if (pHdr->dbch_devicetype != DBT_DEVTYP_DEVICEINTERFACE) {
     return FALSE;
   }
   DEV_BROADCAST_DEVICEINTERFACE* pDi = (DEV_BROADCAST_DEVICEINTERFACE*)pHdr;
-  return pDi->dbcc_classguid == KSCATEGORY_CAPTURE;
+  return pDi->dbcc_classguid == KSCATEGORY_VIDEO_CAMERA;
 }
 
 LRESULT CALLBACK WndProc(HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
 {
     DeviceInfoDS* pParent;
     if (uiMsg == WM_CREATE)
     {
         pParent = (DeviceInfoDS*)((LPCREATESTRUCT)lParam)->lpCreateParams;
@@ -45,17 +45,17 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT
     }
     else if (uiMsg == WM_DESTROY)
     {
         SetWindowLongPtr(hWnd, GWLP_USERDATA, NULL);
     }
     else if (uiMsg == WM_DEVICECHANGE)
     {
         pParent = (DeviceInfoDS*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
-        if (pParent && isCaptureDevice((PDEV_BROADCAST_HDR)lParam))
+        if (pParent && isVideoDevice((PDEV_BROADCAST_HDR)lParam))
         {
             pParent->DeviceChange();
         }
     }
     return DefWindowProc(hWnd, uiMsg, wParam, lParam);
 }
 
 // static
@@ -125,17 +125,17 @@ DeviceInfoDS::DeviceInfoDS()
   if (RegisterClass(&_wndClass)) {
     _hwnd = CreateWindow(_wndClass.lpszClassName, NULL, 0, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL,
                          NULL, _hInstance, this);
 
     DEV_BROADCAST_DEVICEINTERFACE di = { 0 };
     di.dbcc_size = sizeof(di);
     di.dbcc_devicetype  = DBT_DEVTYP_DEVICEINTERFACE;
-    di.dbcc_classguid  = KSCATEGORY_CAPTURE;
+    di.dbcc_classguid  = KSCATEGORY_VIDEO_CAMERA;
 
     _hdevnotify = RegisterDeviceNotification(_hwnd, &di,
                                              DEVICE_NOTIFY_WINDOW_HANDLE);
   }
 }
 
 DeviceInfoDS::~DeviceInfoDS() {
   RELEASE_AND_CLEAR(_dsMonikerDevEnum);
--- a/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/SDKProcessor.java
+++ b/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/SDKProcessor.java
@@ -228,17 +228,17 @@ public class SDKProcessor {
         // We expect a list of jars on the commandline. If missing, whinge about it.
         if (args.length < 3 || args.length % 2 != 1) {
             System.err.println("Usage: java SDKProcessor sdkjar max-sdk-version outdir [configfile fileprefix]*");
             System.exit(1);
         }
 
         System.out.println("Processing platform bindings...");
 
-        final String sdkJar = args[0];
+        final File sdkJar = new File(args[0]);
         sMaxSdkVersion = Integer.parseInt(args[1]);
         final String outdir = args[2];
 
         final LintCliClient lintClient = new LintCliClient();
         sApiLookup = ApiLookup.get(lintClient);
 
         for (int argIndex = 3; argIndex < args.length; argIndex += 2) {
             final String configFile = args[argIndex];
@@ -271,17 +271,17 @@ public class SDKProcessor {
                     "namespace mozilla {\n" +
                     "namespace java {\n" +
                     "namespace sdk {\n" +
                     "\n");
 
             // Used to track the calls to the various class-specific initialisation functions.
             ClassLoader loader = null;
             try {
-                loader = URLClassLoader.newInstance(new URL[]{new URL("file://" + sdkJar)},
+                loader = URLClassLoader.newInstance(new URL[]{sdkJar.toURI().toURL()},
                         SDKProcessor.class.getClassLoader());
             } catch (Exception e) {
                 throw new RuntimeException(e.toString());
             }
 
             try {
                 final ClassInfo[] classes = getClassList(configFile);
                 for (final ClassInfo cls : classes) {
--- a/mobile/android/geckoview/build.gradle
+++ b/mobile/android/geckoview/build.gradle
@@ -489,17 +489,17 @@ task("generateSDKBindings", type: JavaEx
     args 16
     args "${topobjdir}/widget/android/bindings"
 
     // Configure the arguments at evaluation-time, not at configuration-time.
     doFirst {
         // From -Pgenerate_sdk_bindings_args=... on command line; missing in
         // `android-gradle-dependencies` toolchain task.
         if (project.hasProperty('generate_sdk_bindings_args')) {
-            args project.generate_sdk_bindings_args.split(':')
+            args project.generate_sdk_bindings_args.split(';')
         }
     }
 
     workingDir "${topsrcdir}/widget/android/bindings"
 
     dependsOn project(':annotations').jar
 }
 
--- a/mobile/android/mach_commands.py
+++ b/mobile/android/mach_commands.py
@@ -75,17 +75,17 @@ class MachCommands(MachCommandBase):
     def android_generate_sdk_bindings(self, inputs, args):
         import itertools
 
         def stem(input):
             # Turn "/path/to/ClassName-classes.txt" into "ClassName".
             return os.path.basename(input).rsplit('-classes.txt', 1)[0]
 
         bindings_inputs = list(itertools.chain(*((input, stem(input)) for input in inputs)))
-        bindings_args = '-Pgenerate_sdk_bindings_args={}'.format(':'.join(bindings_inputs))
+        bindings_args = '-Pgenerate_sdk_bindings_args={}'.format(';'.join(bindings_inputs))
 
         ret = self.gradle(
             self.substs['GRADLE_ANDROID_GENERATE_SDK_BINDINGS_TASKS'] + [bindings_args] + args,
             verbose=True)
 
         return ret
 
     @SubCommand('android', 'generate-generated-jni-wrappers',
--- a/netwerk/build/components.conf
+++ b/netwerk/build/components.conf
@@ -599,16 +599,17 @@ elif toolkit == 'cocoa':
         'type': 'nsNetworkLinkService',
         'headers': ['/netwerk/system/mac/nsNetworkLinkService.h'],
         'init_method': 'Init',
     }
 elif toolkit == 'android':
     link_service = {
         'type': 'nsAndroidNetworkLinkService',
         'headers': ['/netwerk/system/android/nsAndroidNetworkLinkService.h'],
+        'init_method': 'Init',
     }
 elif buildconfig.substs['OS_ARCH'] == 'Linux':
     link_service = {
         'type': 'nsNetworkLinkService',
         'headers': ['/netwerk/system/linux/nsNetworkLinkService.h'],
         'init_method': 'Init',
     }
 
--- a/netwerk/system/android/nsAndroidNetworkLinkService.cpp
+++ b/netwerk/system/android/nsAndroidNetworkLinkService.cpp
@@ -2,41 +2,109 @@
 /* vim: set ts=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 "nsAndroidNetworkLinkService.h"
 #include "nsServiceManagerUtils.h"
 
+#include "nsIObserverService.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Services.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Telemetry.h"
+
 #include "AndroidBridge.h"
 
 namespace java = mozilla::java;
 
-NS_IMPL_ISUPPORTS(nsAndroidNetworkLinkService, nsINetworkLinkService)
+static mozilla::LazyLogModule gNotifyAddrLog("nsAndroidNetworkLinkService");
+#define LOG(args) MOZ_LOG(gNotifyAddrLog, mozilla::LogLevel::Debug, args)
+
+NS_IMPL_ISUPPORTS(nsAndroidNetworkLinkService, nsINetworkLinkService,
+                  nsIObserver)
+
+nsAndroidNetworkLinkService::nsAndroidNetworkLinkService()
+    : mStatusIsKnown(false) {}
+
+nsresult nsAndroidNetworkLinkService::Init() {
+  nsCOMPtr<nsIObserverService> observerService =
+      mozilla::services::GetObserverService();
+  if (!observerService) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsresult rv;
+  rv = observerService->AddObserver(this, "xpcom-shutdown-threads", false);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-nsAndroidNetworkLinkService::nsAndroidNetworkLinkService() {}
+  mNetlinkSvc = new mozilla::net::NetlinkService();
+  rv = mNetlinkSvc->Init(this);
+  if (NS_FAILED(rv)) {
+    mNetlinkSvc = nullptr;
+    LOG(("Cannot initialize NetlinkService [rv=0x%08" PRIx32 "]",
+         static_cast<uint32_t>(rv)));
+    return rv;
+  }
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
 
-nsAndroidNetworkLinkService::~nsAndroidNetworkLinkService() {}
+nsresult nsAndroidNetworkLinkService::Shutdown() {
+  // remove xpcom shutdown observer
+  nsCOMPtr<nsIObserverService> observerService =
+      mozilla::services::GetObserverService();
+  if (observerService)
+    observerService->RemoveObserver(this, "xpcom-shutdown-threads");
+
+  if (mNetlinkSvc) {
+    mNetlinkSvc->Shutdown();
+    mNetlinkSvc = nullptr;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidNetworkLinkService::Observe(nsISupports* subject, const char* topic,
+                                     const char16_t* data) {
+  if (!strcmp("xpcom-shutdown-threads", topic)) {
+    Shutdown();
+  }
+
+  return NS_OK;
+}
 
 NS_IMETHODIMP
 nsAndroidNetworkLinkService::GetIsLinkUp(bool* aIsUp) {
+  if (mNetlinkSvc && mStatusIsKnown) {
+    mNetlinkSvc->GetIsLinkUp(aIsUp);
+    return NS_OK;
+  }
+
   if (!mozilla::AndroidBridge::Bridge()) {
     // Fail soft here and assume a connection exists
     NS_WARNING("GetIsLinkUp is not supported without a bridge connection");
     *aIsUp = true;
     return NS_OK;
   }
 
   *aIsUp = java::GeckoAppShell::IsNetworkLinkUp();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsAndroidNetworkLinkService::GetLinkStatusKnown(bool* aIsKnown) {
+  if (mStatusIsKnown) {
+    *aIsKnown = true;
+    return NS_OK;
+  }
+
   NS_ENSURE_TRUE(mozilla::AndroidBridge::Bridge(), NS_ERROR_NOT_IMPLEMENTED);
 
   *aIsKnown = java::GeckoAppShell::IsNetworkLinkKnown();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsAndroidNetworkLinkService::GetLinkType(uint32_t* aLinkType) {
@@ -50,11 +118,65 @@ nsAndroidNetworkLinkService::GetLinkType
   }
 
   *aLinkType = java::GeckoAppShell::GetNetworkLinkType();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsAndroidNetworkLinkService::GetNetworkID(nsACString& aNetworkID) {
-  aNetworkID.Truncate();
+  if (!mNetlinkSvc) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  mNetlinkSvc->GetNetworkID(aNetworkID);
   return NS_OK;
 }
+
+void nsAndroidNetworkLinkService::OnNetworkChanged() {
+  if (mozilla::StaticPrefs::network_notify_changed()) {
+    if (!mNetworkChangeTime.IsNull()) {
+      mozilla::Telemetry::AccumulateTimeDelta(
+          mozilla::Telemetry::NETWORK_TIME_BETWEEN_NETWORK_CHANGE_EVENTS,
+          mNetworkChangeTime);
+    }
+    mNetworkChangeTime = mozilla::TimeStamp::Now();
+
+    RefPtr<nsAndroidNetworkLinkService> self = this;
+    NS_DispatchToMainThread(NS_NewRunnableFunction(
+        "nsAndroidNetworkLinkService::OnNetworkChanged",
+        [self]() { self->SendEvent(NS_NETWORK_LINK_DATA_CHANGED); }));
+  }
+}
+
+void nsAndroidNetworkLinkService::OnLinkUp() {
+  RefPtr<nsAndroidNetworkLinkService> self = this;
+  NS_DispatchToMainThread(NS_NewRunnableFunction(
+      "nsAndroidNetworkLinkService::OnLinkUp",
+      [self]() { self->SendEvent(NS_NETWORK_LINK_DATA_UP); }));
+}
+
+void nsAndroidNetworkLinkService::OnLinkDown() {
+  RefPtr<nsAndroidNetworkLinkService> self = this;
+  NS_DispatchToMainThread(NS_NewRunnableFunction(
+      "nsAndroidNetworkLinkService::OnLinkDown",
+      [self]() { self->SendEvent(NS_NETWORK_LINK_DATA_DOWN); }));
+}
+
+void nsAndroidNetworkLinkService::OnLinkStatusKnown() { mStatusIsKnown = true; }
+
+/* Sends the given event. Assumes aEventID never goes out of scope (static
+ * strings are ideal).
+ */
+void nsAndroidNetworkLinkService::SendEvent(const char* aEventID) {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  LOG(("SendEvent: %s\n", aEventID));
+
+  nsCOMPtr<nsIObserverService> observerService =
+      mozilla::services::GetObserverService();
+
+  if (observerService) {
+    observerService->NotifyObservers(static_cast<nsINetworkLinkService*>(this),
+                                     NS_NETWORK_LINK_TOPIC,
+                                     NS_ConvertASCIItoUTF16(aEventID).get());
+  }
+}
--- a/netwerk/system/android/nsAndroidNetworkLinkService.h
+++ b/netwerk/system/android/nsAndroidNetworkLinkService.h
@@ -3,21 +3,48 @@
 /* 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 NSANDROIDNETWORKLINKSERVICE_H_
 #define NSANDROIDNETWORKLINKSERVICE_H_
 
 #include "nsINetworkLinkService.h"
+#include "nsIObserver.h"
+#include "../netlink/NetlinkService.h"
+#include "mozilla/TimeStamp.h"
 
-class nsAndroidNetworkLinkService : public nsINetworkLinkService {
+class nsAndroidNetworkLinkService
+    : public nsINetworkLinkService,
+      public nsIObserver,
+      public mozilla::net::NetlinkServiceListener {
  public:
-  NS_DECL_ISUPPORTS
+  NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSINETWORKLINKSERVICE
+  NS_DECL_NSIOBSERVER
 
   nsAndroidNetworkLinkService();
 
+  nsresult Init();
+
+  void OnNetworkChanged() override;
+  void OnLinkUp() override;
+  void OnLinkDown() override;
+  void OnLinkStatusKnown() override;
+
  private:
-  virtual ~nsAndroidNetworkLinkService();
+  virtual ~nsAndroidNetworkLinkService() = default;
+
+  // Called when xpcom-shutdown-threads is received.
+  nsresult Shutdown();
+
+  // Sends the network event.
+  void SendEvent(const char* aEventID);
+
+  mozilla::Atomic<bool, mozilla::Relaxed> mStatusIsKnown;
+
+  RefPtr<mozilla::net::NetlinkService> mNetlinkSvc;
+
+  // Time stamp of last NS_NETWORK_LINK_DATA_CHANGED event
+  mozilla::TimeStamp mNetworkChangeTime;
 };
 
 #endif /* NSANDROIDNETWORKLINKSERVICE_H_ */
--- a/netwerk/system/moz.build
+++ b/netwerk/system/moz.build
@@ -6,15 +6,18 @@
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     DIRS += ['win32']
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
     DIRS += ['mac']
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
-    DIRS += ['android']
+    DIRS += [
+        'android',
+        'netlink'
+    ]
 
 elif CONFIG['OS_ARCH'] == 'Linux':
     DIRS += [
         'linux',
         'netlink'
     ]
--- a/netwerk/system/netlink/NetlinkService.cpp
+++ b/netwerk/system/netlink/NetlinkService.cpp
@@ -58,16 +58,17 @@ static void GetAddrStr(const in_common_a
 
 class NetlinkAddress {
  public:
   NetlinkAddress() {}
 
   uint8_t Family() const { return mIfam.ifa_family; }
   uint32_t GetIndex() const { return mIfam.ifa_index; }
   uint8_t GetPrefixLen() const { return mIfam.ifa_prefixlen; }
+  bool ScopeIsUniverse() const { return mIfam.ifa_scope == RT_SCOPE_UNIVERSE; }
   const in_common_addr* GetAddrPtr() const { return &mAddr; }
 
   bool Equals(const NetlinkAddress* aOther) const {
     if (mIfam.ifa_family != aOther->mIfam.ifa_family) {
       return false;
     }
     if (mIfam.ifa_index != aOther->mIfam.ifa_index) {
       // addresses are different when they are on a different interface
@@ -679,22 +680,46 @@ void NetlinkService::OnLinkMessage(struc
 }
 
 void NetlinkService::CheckLinks() {
   if (!mInitialScanFinished) {
     // Wait until we get all links via netlink
     return;
   }
 
+  LOG(("NetlinkService::CheckLinks"));
+
+  // Link is up when there is non-local address associated with it.
   bool newLinkUp = false;
-  for (auto iter = mLinks.ConstIter(); !iter.Done(); iter.Next()) {
-    if (iter.Data()->IsUp()) {
+  for (uint32_t i = 0; i < mAddresses.Length(); ++i) {
+#ifdef NL_DEBUG_LOG
+    nsAutoCString dbgStr;
+    GetAddrStr(mAddresses[i]->GetAddrPtr(), mAddresses[i]->Family(), dbgStr);
+    LOG(("checking address %s on iface %d", dbgStr.get(),
+         mAddresses[i]->GetIndex()));
+#else
+    LOG(("checking address on iface %d", mAddresses[i]->GetIndex()));
+#endif
+    if (!mAddresses[i]->ScopeIsUniverse()) {
+      LOG(("  is not global"));
+      continue;
+    }
+
+    NetlinkLink* link = nullptr;
+    if (!mLinks.Get(mAddresses[i]->GetIndex(), &link)) {
+      LOG(("  cannot get link"));
+      continue;
+    }
+
+    if (link->IsUp()) {
+      LOG(("  link is up"));
       newLinkUp = true;
       break;
     }
+    LOG(("  link is down"));
   }
 
   if (mLinkUp != newLinkUp) {
     RefPtr<NetlinkServiceListener> listener;
     {
       MutexAutoLock lock(mMutex);
       listener = mListener;
       mLinkUp = newLinkUp;
@@ -740,17 +765,19 @@ void NetlinkService::OnAddrMessage(struc
     LOG(("Adding address [ifidx=%u, addr=%s/%u]", address->GetIndex(),
          addrStr.get(), address->GetPrefixLen()));
     mAddresses.AppendElement(address.forget());
   }
 
   NetlinkLink* link;
   if (mLinks.Get(ifIdx, &link)) {
     if (link->IsUp()) {
-      // Address changed on a link that is up. This might change network ID.
+      // Address change on a link that is up might change link status and
+      // network ID.
+      CheckLinks();
       TriggerNetworkIDCalculation();
     }
   }
 }
 
 void NetlinkService::OnRouteMessage(struct nlmsghdr* aNlh) {
   LOG(("NetlinkService::OnRouteMessage"));
   nsAutoPtr<NetlinkRoute> route(new NetlinkRoute());
@@ -1147,33 +1174,47 @@ class NeighborComparator {
   bool Equals(const NetlinkNeighbor* a, const NetlinkNeighbor* b) const {
     return (memcmp(a->GetMACPtr(), b->GetMACPtr(), ETH_ALEN) == 0);
   }
   bool LessThan(const NetlinkNeighbor* a, const NetlinkNeighbor* b) const {
     return (memcmp(a->GetMACPtr(), b->GetMACPtr(), ETH_ALEN) < 0);
   }
 };
 
+class LinknameComparator {
+ public:
+  bool LessThan(const nsCString& aA, const nsCString& aB) const {
+    return aA < aB;
+  }
+  bool Equals(const nsCString& aA, const nsCString& aB) const {
+    return aA == aB;
+  }
+};
+
 bool NetlinkService::CalculateIDForFamily(uint8_t aFamily, SHA1Sum* aSHA1) {
   LOG(("NetlinkService::CalculateIDForFamily [family=%s]",
        aFamily == AF_INET ? "AF_INET" : "AF_INET6"));
 
   bool retval = false;
 
   nsTArray<nsAutoPtr<NetlinkRoute> >* routesPtr;
   nsAutoPtr<NetlinkRoute>* routeCheckResultPtr;
   if (aFamily == AF_INET) {
     routesPtr = &mIPv4Routes;
     routeCheckResultPtr = &mIPv4RouteCheckResult;
   } else {
     routesPtr = &mIPv6Routes;
     routeCheckResultPtr = &mIPv6RouteCheckResult;
   }
 
-  // All known GW neighbors
+  // All GW neighbors for which we know MAC address. We'll probably have at
+  // most only one, but in case we have more default routes, we hash them all
+  // even though the routing rules sends the traffic only via one of them.
+  // If the system switches between them, we'll detect the change with
+  // mIPv4/6RouteCheckResult.
   nsTArray<NetlinkNeighbor*> gwNeighbors;
 
   // Check all default routes and try to get MAC of the gateway
   for (uint32_t i = 0; i < (*routesPtr).Length(); ++i) {
 #ifdef NL_DEBUG_LOG
     nsAutoCString routeDbgStr;
     (*routesPtr)[i]->GetAsString(routeDbgStr);
     LOG(("Checking default route: %s", routeDbgStr.get()));
@@ -1216,17 +1257,59 @@ bool NetlinkService::CalculateIDForFamil
     nsAutoCString neighDbgStr;
     gwNeighbors[i]->GetAsString(neighDbgStr);
     LOG(("Hashing MAC address of neighbor: %s", neighDbgStr.get()));
 #endif
     aSHA1->update(gwNeighbors[i]->GetMACPtr(), ETH_ALEN);
     retval = true;
   }
 
+  if (!gwNeighbors.Length() && mLinkUp) {
+    // If we don't know MAC of the gateway and link is up, it's probably not
+    // an ethernet link. If the name of the link begins with "rmnet_data" then
+    // the mobile data is used. We cannot easily differentiate when user
+    // switches sim cards so let's treat mobile data as a single network. We'll
+    // simply hash link name. If the traffic is redirected via some VPN, it'll
+    // still be detected below.
+
+    // TODO: maybe we could get operator name via AndroidBridge
+    nsTArray<nsCString> linkNames;
+    for (auto iter = mLinks.ConstIter(); !iter.Done(); iter.Next()) {
+      if (iter.Data()->IsUp()) {
+        nsAutoCString linkName;
+        iter.Data()->GetName(linkName);
+        if (StringBeginsWith(linkName, NS_LITERAL_CSTRING("rmnet_data"))) {
+          // Check whether there is some non-local address associated with this
+          // link.
+          for (uint32_t i = 0; i < mAddresses.Length(); ++i) {
+            if (mAddresses[i]->Family() == aFamily &&
+                mAddresses[i]->ScopeIsUniverse() &&
+                mAddresses[i]->GetIndex() == iter.Data()->GetIndex()) {
+              linkNames.AppendElement(linkName);
+              break;
+            }
+          }
+        }
+      }
+    }
+
+    // Sort link names to ensure consistent results
+    linkNames.Sort(LinknameComparator());
+
+    for (uint32_t i = 0; i < linkNames.Length(); ++i) {
+      LOG(("Hashing name of adapter: %s", linkNames[i].get()));
+      aSHA1->update(linkNames[i].BeginReading(), linkNames[i].Length());
+      retval = true;
+    }
+  }
+
   if (!*routeCheckResultPtr) {
+    // If we don't have result for route check to mRouteCheckIPv4/6 host. The
+    // network is either unreachable or the checking was disabled by removing IP
+    // from the preferences. In any case, there is no more to do.
     LOG(("There is no route check result."));
     return retval;
   }
 
   // Check whether we know next hop for mRouteCheckIPv4/6 host
   const in_common_addr* addrPtr = (*routeCheckResultPtr)->GetGWAddrPtr();
   if (addrPtr) {
     // If we know MAC address of the next hop for mRouteCheckIPv4/6 host, hash
@@ -1364,16 +1447,18 @@ bool NetlinkService::CalculateIDForFamil
 void NetlinkService::CalculateNetworkID() {
   MOZ_ASSERT(!NS_IsMainThread(), "Must not be called on the main thread");
   MOZ_ASSERT(mRecalculateNetworkId);
 
   mRecalculateNetworkId = false;
 
   SHA1Sum sha1;
 
+  CheckLinks();
+
   bool idChanged = false;
   bool found4 = CalculateIDForFamily(AF_INET, &sha1);
   bool found6 = CalculateIDForFamily(AF_INET6, &sha1);
 
   if (found4 || found6) {
     // This 'addition' could potentially be a fixed number from the
     // profile or something.
     nsAutoCString addition("local-rubbish");
--- a/security/manager/ssl/nsNSSComponent.cpp
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -702,30 +702,21 @@ nsNSSComponent::HasActiveSmartCards(bool
 }
 
 NS_IMETHODIMP
 nsNSSComponent::HasUserCertsInstalled(bool* result) {
   NS_ENSURE_ARG_POINTER(result);
 
   BlockUntilLoadableRootsLoaded();
 
-  *result = false;
-  UniqueCERTCertList certList(CERT_FindUserCertsByUsage(
-      CERT_GetDefaultCertDB(), certUsageSSLClient, false, true, nullptr));
-  if (!certList) {
-    return NS_OK;
-  }
+  // FindNonCACertificatesWithPrivateKeys won't ever return an empty list, so
+  // all we need to do is check if this is null or not.
+  UniqueCERTCertList certList(FindNonCACertificatesWithPrivateKeys());
+  *result = !!certList;
 
-  // check if the list is empty
-  if (CERT_LIST_END(CERT_LIST_HEAD(certList), certList)) {
-    return NS_OK;
-  }
-
-  // The list is not empty, meaning at least one cert is installed
-  *result = true;
   return NS_OK;
 }
 
 nsresult nsNSSComponent::BlockUntilLoadableRootsLoaded() {
   MonitorAutoLock rootsLoadedLock(mLoadableRootsLoadedMonitor);
   while (!mLoadableRootsLoaded) {
     rootsLoadedLock.Wait();
   }
@@ -2171,16 +2162,84 @@ already_AddRefed<SharedCertVerifier> Get
   RefPtr<SharedCertVerifier> result;
   rv = nssComponent->GetDefaultCertVerifier(getter_AddRefs(result));
   if (NS_FAILED(rv)) {
     return nullptr;
   }
   return result.forget();
 }
 
+// Lists all private keys on all modules and returns a list of any corresponding
+// certificates. Returns null if no such certificates can be found. Also returns
+// null if an error is encountered, because this is called as part of the client
+// auth data callback, and NSS ignores any errors returned by the callback.
+UniqueCERTCertList FindNonCACertificatesWithPrivateKeys() {
+  MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+          ("FindNonCACertificatesWithPrivateKeys"));
+  UniqueCERTCertList certsWithPrivateKeys(CERT_NewCertList());
+  if (!certsWithPrivateKeys) {
+    return nullptr;
+  }
+
+  AutoSECMODListReadLock secmodLock;
+  SECMODModuleList* list = SECMOD_GetDefaultModuleList();
+  while (list) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+            ("  module '%s'", list->module->commonName));
+    for (int i = 0; i < list->module->slotCount; i++) {
+      PK11SlotInfo* slot = list->module->slots[i];
+      MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+              ("    slot '%s'", PK11_GetSlotName(slot)));
+      // We may need to log in to be able to find private keys.
+      if (PK11_Authenticate(slot, true, nullptr) != SECSuccess) {
+        MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("    (couldn't authenticate)"));
+        continue;
+      }
+      UniqueSECKEYPrivateKeyList privateKeys(
+          PK11_ListPrivKeysInSlot(slot, nullptr, nullptr));
+      if (!privateKeys) {
+        MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("      (no private keys)"));
+        continue;
+      }
+      for (SECKEYPrivateKeyListNode* node = PRIVKEY_LIST_HEAD(privateKeys);
+           !PRIVKEY_LIST_END(node, privateKeys);
+           node = PRIVKEY_LIST_NEXT(node)) {
+        UniqueCERTCertList certs(PK11_GetCertsMatchingPrivateKey(node->key));
+        if (!certs) {
+          MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+                  ("      PK11_GetCertsMatchingPrivateKey encountered an error "
+                   "- returning"));
+          return nullptr;
+        }
+        if (CERT_LIST_EMPTY(certs)) {
+          MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("      (no certs for key)"));
+          continue;
+        }
+        for (CERTCertListNode* n = CERT_LIST_HEAD(certs);
+             !CERT_LIST_END(n, certs); n = CERT_LIST_NEXT(n)) {
+          if (!CERT_IsCACert(n->cert, nullptr)) {
+            MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+                    ("      found '%s'", n->cert->subjectName));
+            UniqueCERTCertificate cert(CERT_DupCertificate(n->cert));
+            if (CERT_AddCertToListTail(certsWithPrivateKeys.get(),
+                                       cert.get()) == SECSuccess) {
+              Unused << cert.release();
+            }
+          }
+        }
+      }
+    }
+    list = list->next;
+  }
+  if (CERT_LIST_EMPTY(certsWithPrivateKeys)) {
+    return nullptr;
+  }
+  return certsWithPrivateKeys;
+}
+
 }  // namespace psm
 }  // namespace mozilla
 
 NS_IMPL_ISUPPORTS(PipUIContext, nsIInterfaceRequestor)
 
 PipUIContext::PipUIContext() {}
 
 PipUIContext::~PipUIContext() {}
--- a/security/manager/ssl/nsNSSComponent.h
+++ b/security/manager/ssl/nsNSSComponent.h
@@ -32,16 +32,17 @@ class nsIPrompt;
 class nsIX509CertList;
 class SmartCardThreadList;
 
 namespace mozilla {
 namespace psm {
 
 MOZ_MUST_USE
 ::already_AddRefed<mozilla::psm::SharedCertVerifier> GetDefaultCertVerifier();
+UniqueCERTCertList FindNonCACertificatesWithPrivateKeys();
 
 }  // namespace psm
 }  // namespace mozilla
 
 #define NS_NSSCOMPONENT_CID                          \
   {                                                  \
     0x4cb64dfd, 0xca98, 0x4e24, {                    \
       0xbe, 0xfd, 0x0d, 0x92, 0x85, 0xa3, 0x3b, 0xcb \
--- a/security/manager/ssl/nsNSSIOLayer.cpp
+++ b/security/manager/ssl/nsNSSIOLayer.cpp
@@ -1887,84 +1887,16 @@ SECStatus nsNSS_SSLGetClientAuthData(voi
     info->SetSentClientCert();
     Telemetry::ScalarAdd(Telemetry::ScalarID::SECURITY_CLIENT_CERT,
                          NS_LITERAL_STRING("sent"), 1);
   }
 
   return runnable->mRV;
 }
 
-// Lists all private keys on all modules and returns a list of any corresponding
-// certificates. Returns null if no such certificates can be found. Also returns
-// null if an error is encountered, because this is called as part of the client
-// auth data callback, and NSS ignores any errors returned by the callback.
-UniqueCERTCertList FindNonCACertificatesWithPrivateKeys() {
-  MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
-          ("FindNonCACertificatesWithPrivateKeys"));
-  UniqueCERTCertList certsWithPrivateKeys(CERT_NewCertList());
-  if (!certsWithPrivateKeys) {
-    return nullptr;
-  }
-
-  AutoSECMODListReadLock secmodLock;
-  SECMODModuleList* list = SECMOD_GetDefaultModuleList();
-  while (list) {
-    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
-            ("  module '%s'", list->module->commonName));
-    for (int i = 0; i < list->module->slotCount; i++) {
-      PK11SlotInfo* slot = list->module->slots[i];
-      MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
-              ("    slot '%s'", PK11_GetSlotName(slot)));
-      // We may need to log in to be able to find private keys.
-      if (PK11_Authenticate(slot, true, nullptr) != SECSuccess) {
-        MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("    (couldn't authenticate)"));
-        continue;
-      }
-      UniqueSECKEYPrivateKeyList privateKeys(
-          PK11_ListPrivKeysInSlot(slot, nullptr, nullptr));
-      if (!privateKeys) {
-        MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("      (no private keys)"));
-        continue;
-      }
-      for (SECKEYPrivateKeyListNode* node = PRIVKEY_LIST_HEAD(privateKeys);
-           !PRIVKEY_LIST_END(node, privateKeys);
-           node = PRIVKEY_LIST_NEXT(node)) {
-        UniqueCERTCertList certs(PK11_GetCertsMatchingPrivateKey(node->key));
-        if (!certs) {
-          MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
-                  ("      PK11_GetCertsMatchingPrivateKey encountered an error "
-                   "- returning"));
-          return nullptr;
-        }
-        if (CERT_LIST_EMPTY(certs)) {
-          MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("      (no certs for key)"));
-          continue;
-        }
-        for (CERTCertListNode* n = CERT_LIST_HEAD(certs);
-             !CERT_LIST_END(n, certs); n = CERT_LIST_NEXT(n)) {
-          if (!CERT_IsCACert(n->cert, nullptr)) {
-            MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
-                    ("      found '%s'", n->cert->subjectName));
-            UniqueCERTCertificate cert(CERT_DupCertificate(n->cert));
-            if (CERT_AddCertToListTail(certsWithPrivateKeys.get(),
-                                       cert.get()) == SECSuccess) {
-              Unused << cert.release();
-            }
-          }
-        }
-      }
-    }
-    list = list->next;
-  }
-  if (CERT_LIST_EMPTY(certsWithPrivateKeys)) {
-    return nullptr;
-  }
-  return certsWithPrivateKeys;
-}
-
 void ClientAuthDataRunnable::RunOnTargetThread() {
   // We check the value of a pref in this runnable, so this runnable should only
   // be run on the main thread.
   MOZ_ASSERT(NS_IsMainThread());
 
   UniqueCERTCertificate cert;
   UniqueSECKEYPrivateKey privKey;
   void* wincx = mSocketInfo;
--- a/taskcluster/ci/generate-profile/kind.yml
+++ b/taskcluster/ci/generate-profile/kind.yml
@@ -122,17 +122,17 @@ jobs:
                   name: public/build
                   path: /builds/worker/artifacts/blobber_upload_dir
         run:
             using: mozharness
             need-xvfb: true
             job-script: taskcluster/scripts/tester/test-linux.sh
             script: android_emulator_pgo.py
             tooltool-downloads: internal
-            options: [installer-path=/builds/worker/fetches/target.apk]
+            options: [installer-path=/builds/worker/fetches/geckoview-androidTest.apk]
             config:
                 - android/android_common.py
                 - android/androidarm_4_3.py
                 - android/android_pgo.py
         fetches:
             toolchain:
                 - linux64-clang
 
--- a/taskcluster/ci/toolchain/geckodriver.yml
+++ b/taskcluster/ci/toolchain/geckodriver.yml
@@ -2,16 +2,17 @@
 # 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/.
 ---
 job-defaults:
     description: "geckodriver toolchain build"
     worker-type: b-linux
     worker:
         max-run-time: 1800
+    run-on-projects: ['trunk']
     run:
         script: build-geckodriver.sh
         toolchain-artifact: public/build/geckodriver.tar.xz
         sparse-profile: null
         resources:
             - 'testing/geckodriver'
             - 'testing/mozbase/rust'
             - 'testing/webdriver'
--- a/taskcluster/taskgraph/transforms/run_pgo_profile.py
+++ b/taskcluster/taskgraph/transforms/run_pgo_profile.py
@@ -16,17 +16,17 @@ transforms = TransformSequence()
 
 
 @transforms.add
 def run_profile_data(config, jobs):
     for job in jobs:
         build_platform = job['attributes'].get('build_platform')
         instr = 'instrumented-build-{}'.format(job['name'])
         if 'android' in build_platform:
-            artifact = 'target.apk'
+            artifact = 'geckoview-androidTest.apk'
         elif 'macosx64' in build_platform:
             artifact = 'target.dmg'
         elif 'win' in build_platform:
             artifact = 'target.zip'
         else:
             artifact = 'target.tar.bz2'
         job.setdefault('fetches', {})[instr] = [artifact]
         yield job
--- a/testing/mozbase/mozprofile/setup.py
+++ b/testing/mozbase/mozprofile/setup.py
@@ -33,16 +33,16 @@ setup(name=PACKAGE_NAME,
       author_email='tools@lists.mozilla.org',
       url='https://wiki.mozilla.org/Auto-tools/Projects/Mozbase',
       license='MPL 2.0',
       packages=['mozprofile'],
       include_package_data=True,
       zip_safe=False,
       install_requires=deps,
       extras_require={'manifest': ['manifestparser >= 0.6']},
-      tests_require=['mozhttpd'],
+      tests_require=['wptserve'],
       entry_points="""
       # -*- Entry points: -*-
       [console_scripts]
       mozprofile = mozprofile:cli
       view-profile = mozprofile:view_profile
       diff-profiles = mozprofile:diff_profiles
       """, )
--- a/testing/mozbase/mozprofile/tests/test_preferences.py
+++ b/testing/mozbase/mozprofile/tests/test_preferences.py
@@ -2,23 +2,23 @@
 
 # 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/.
 
 from __future__ import absolute_import
 
 import mozfile
-import mozhttpd
 import os
 import shutil
 import tempfile
 
 import mozunit
 import pytest
+from wptserve import server
 
 from mozprofile.cli import MozProfileCLI
 from mozprofile.prefs import Preferences, PreferencesReadError
 from mozprofile.profile import Profile
 
 here = os.path.dirname(os.path.abspath(__file__))
 
 
@@ -378,23 +378,23 @@ def test_read_prefs_with_interpolation()
         "abc": "something"
     }
     path = os.path.join(here, 'files', 'prefs_with_interpolation.js')
     read_prefs = Preferences.read_prefs(path, interpolation=values)
     assert dict(read_prefs) == expected_prefs
 
 
 def test_read_prefs_ttw():
-    """test reading preferences through the web via mozhttpd"""
+    """test reading preferences through the web via wptserve"""
 
-    # create a MozHttpd instance
+    # create a WebTestHttpd instance
     docroot = os.path.join(here, 'files')
     host = '127.0.0.1'
     port = 8888
-    httpd = mozhttpd.MozHttpd(host=host, port=port, docroot=docroot)
+    httpd = server.WebTestHttpd(host=host, port=port, doc_root=docroot)
 
     # create a preferences instance
     prefs = Preferences()
 
     try:
         # start server
         httpd.start(block=False)
 
--- a/testing/mozharness/scripts/android_emulator_pgo.py
+++ b/testing/mozharness/scripts/android_emulator_pgo.py
@@ -217,17 +217,17 @@ class AndroidProfileRun(TestingMixin, Ba
         adbdevice.mkdir(outputdir)
 
         try:
             # Run Fennec a first time to initialize its profile
             driver = Marionette(
                 app='fennec',
                 package_name=app,
                 adb_path=adb,
-                bin="target.apk",
+                bin="geckoview-androidTest.apk",
                 prefs=prefs,
                 connect_to_running_emulator=True,
                 startup_timeout=1000,
                 env=env,
             )
             driver.start_session()
 
             # Now generate the profile and wait for it to complete
--- a/testing/xpcshell/moz-http2/moz-http2.js
+++ b/testing/xpcshell/moz-http2/moz-http2.js
@@ -59,17 +59,17 @@ var framer_module = node_http2_root + "/
 var http2_framer = require(framer_module);
 var Serializer = http2_framer.Serializer;
 var originalTransform = Serializer.prototype._transform;
 var newTransform = function (frame, encoding, done) {
   if (frame.type == 'DATA') {
     // Insert our empty DATA frame
     emptyFrame = {};
     emptyFrame.type = 'DATA';
-    emptyFrame.data = new Buffer(0);
+    emptyFrame.data = Buffer.alloc(0);
     emptyFrame.flags = [];
     emptyFrame.stream = frame.stream;
     var buffers = [];
     Serializer['DATA'](emptyFrame, buffers);
     Serializer.commonHeader(emptyFrame, buffers);
     for (var i = 0; i < buffers.length; i++) {
       this.push(buffers[i]);
     }
@@ -163,42 +163,42 @@ function executeRunLater(arg) {
 }
 
 var Compressor = http2_compression.Compressor;
 var HeaderSetCompressor = http2_compression.HeaderSetCompressor;
 var originalCompressHeaders = Compressor.prototype.compress;
 
 function insertSoftIllegalHpack(headers) {
   var originalCompressed = originalCompressHeaders.apply(this, headers);
-  var illegalLiteral = new Buffer([
+  var illegalLiteral = Buffer.from([
       0x00, // Literal, no index
       0x08, // Name: not huffman encoded, 8 bytes long
       0x3a, 0x69, 0x6c, 0x6c, 0x65, 0x67, 0x61, 0x6c, // :illegal
       0x10, // Value: not huffman encoded, 16 bytes long
       // REALLY NOT LEGAL
       0x52, 0x45, 0x41, 0x4c, 0x4c, 0x59, 0x20, 0x4e, 0x4f, 0x54, 0x20, 0x4c, 0x45, 0x47, 0x41, 0x4c
   ]);
   var newBufferLength = originalCompressed.length + illegalLiteral.length;
-  var concatenated = new Buffer(newBufferLength);
+  var concatenated = Buffer.alloc(newBufferLength);
   originalCompressed.copy(concatenated, 0);
   illegalLiteral.copy(concatenated, originalCompressed.length);
   return concatenated;
 }
 
 function insertHardIllegalHpack(headers) {
   var originalCompressed = originalCompressHeaders.apply(this, headers);
   // Now we have to add an invalid header
   var illegalIndexed = HeaderSetCompressor.integer(5000, 7);
   // The above returns an array of buffers, but there's only one buffer, so
   // get rid of the array.
   illegalIndexed = illegalIndexed[0];
   // Set the first bit to 1 to signal this is an indexed representation
   illegalIndexed[0] |= 0x80;
   var newBufferLength = originalCompressed.length + illegalIndexed.length;
-  var concatenated = new Buffer(newBufferLength);
+  var concatenated = Buffer.alloc(newBufferLength);
   originalCompressed.copy(concatenated, 0);
   illegalIndexed.copy(concatenated, originalCompressed.length);
   return concatenated;
 }
 
 var h11required_conn = null;
 var h11required_header = "yes";
 var didRst = false;
@@ -360,17 +360,17 @@ function handleRequest(req, res) {
           headers: {'x-pushed-request': 'true', 'Accept-Encoding' : 'br'}});
     push3.writeHead(200, {
       'pushed' : 'yes',
       'content-length' : 6,
       'subresource' : '3',
       'content-encoding' : 'br',
       'X-Connection-Http2': 'yes'
     });
-    push3.end(new Buffer([0x8b, 0x00, 0x80, 0x33, 0x0a, 0x03])); // '3\n'
+    push3.end(Buffer.from([0x8b, 0x00, 0x80, 0x33, 0x0a, 0x03])); // '3\n'
 
     content = '0';
   }
 
   else if (u.pathname === "/big") {
     content = generateContent(128 * 1024);
     var hash = crypto.createHash('md5');
     hash.update(content);
@@ -540,22 +540,22 @@ function handleRequest(req, res) {
     res.setHeader('Alt-Svc', 'h2=' + req.headers['x-altsvc']);
   }
   // for use with test_trr.js
   else if (u.pathname === "/dns-cname") {
     // asking for cname.example.com
     var content;
     if(0 == cname_confirm) {
       // ... this sends a CNAME back to pointing-elsewhere.example.com
-      content = new Buffer("00000100000100010000000005636E616D65076578616D706C6503636F6D0000050001C00C0005000100000037002012706F696E74696E672D656C73657768657265076578616D706C6503636F6D00", "hex");
+      content = Buffer.from("00000100000100010000000005636E616D65076578616D706C6503636F6D0000050001C00C0005000100000037002012706F696E74696E672D656C73657768657265076578616D706C6503636F6D00", "hex");
       cname_confirm++;
     }
     else {
       // ... this sends an A 99.88.77.66 entry back for pointing-elsewhere.example.com
-      content = new Buffer("00000100000100010000000012706F696E74696E672D656C73657768657265076578616D706C6503636F6D0000010001C00C0001000100000037000463584D42", "hex");
+      content = Buffer.from("00000100000100010000000012706F696E74696E672D656C73657768657265076578616D706C6503636F6D0000010001C00C0001000100000037000463584D42", "hex");
     }
     res.setHeader('Content-Type', 'application/dns-message');
     res.setHeader('Content-Length', content.length);
     res.writeHead(200);
     res.write(content);
     res.end("");
     return;
 
@@ -582,17 +582,17 @@ function handleRequest(req, res) {
         res.writeHead(401);
         res.end("bad boy!");
         return;
       }
     }
 
     if (u.query["push"]) {
       // push.example.com has AAAA entry 2018::2018
-      var pcontent= new Buffer("0000010000010001000000000470757368076578616D706C6503636F6D00001C0001C00C001C000100000037001020180000000000000000000000002018", "hex");
+      var pcontent= Buffer.from("0000010000010001000000000470757368076578616D706C6503636F6D00001C0001C00C001C000100000037001020180000000000000000000000002018", "hex");
       push = res.push({
         hostname: 'foo.example.com:' + serverPort,
         port: serverPort,
         path: '/dns-pushed-response?dns=AAAAAAABAAAAAAAABHB1c2gHZXhhbXBsZQNjb20AABwAAQ',
         method: 'GET',
         headers: {
           'accept' : 'application/dns-message'
         }
@@ -601,17 +601,17 @@ function handleRequest(req, res) {
         'content-type': 'application/dns-message',
         'pushed' : 'yes',
         'content-length' : pcontent.length,
         'X-Connection-Http2': 'yes'
       });
       push.end(pcontent);
     }
 
-    let payload = new Buffer("");
+    let payload = Buffer.from("");
 
     function emitResponse(response, requestPayload) {
       let packet = dnsPacket.decode(requestPayload);
 
       // This shuts down the connection so we can test if the client reconnects
       if (packet.questions.length > 0 &&
         packet.questions[0].name == "closeme.com") {
         response.stream.connection.close('INTERNAL_ERROR', response.stream.id);
@@ -711,17 +711,17 @@ function handleRequest(req, res) {
     return;
   }
   else if (u.pathname === "/dns-cname-a") {
     // test23 asks for cname-a.example.com
     // this responds with a CNAME to here.example.com *and* an A record
     // for here.example.com
     var content;
 
-    content = new Buffer("0000" +
+    content = Buffer.from("0000" +
                          "0100" +
                          "0001" + // QDCOUNT
                          "0002" + // ANCOUNT
                          "00000000" + // NSCOUNT + ARCOUNT
                          "07636E616D652d61" + // cname-a
                          "076578616D706C6503636F6D00" + // .example.com
                          "00010001" + // question type (A) + question class (IN)
 
@@ -752,17 +752,17 @@ function handleRequest(req, res) {
 
   }
   else if (u.pathname === '/dns-750ms') {
     // it's just meant to be this slow - the test doesn't care about the actual response
     return;
   }
   // for use with test_esni_dns_fetch.js
   else if (u.pathname === "/esni-dns") {
-    content = new Buffer("0000" +
+    content = Buffer.from("0000" +
                          "8180" +
                          "0001" + // QDCOUNT
                          "0001" + // ANCOUNT
                          "00000000" + // NSCOUNT + ARCOUNT
                          "055F65736E69076578616D706C6503636F6D00" + // _esni.example.com
                          "00100001" + // question type (TXT) + question class (IN)
 
                          "C00C" + // name pointer to .example.com
@@ -779,20 +779,20 @@ function handleRequest(req, res) {
     res.write(content);
     res.end("");
     return;
   }
 
   // for use with test_esni_dns_fetch.js
   else if (u.pathname === "/esni-dns-push") {
     // _esni_push.example.com has A entry 127.0.0.1
-    var content= new Buffer("0000010000010001000000000A5F65736E695F70757368076578616D706C6503636F6D0000010001C00C000100010000003700047F000001", "hex");
+    var content= Buffer.from("0000010000010001000000000A5F65736E695F70757368076578616D706C6503636F6D0000010001C00C000100010000003700047F000001", "hex");
 
     // _esni_push.example.com has TXT entry 2062586B67646D39705932556761584D6762586B676347467A63336476636D513D
-    var pcontent= new Buffer("0000818000010001000000000A5F65736E695F70757368076578616D706C6503636F6D0000100001C00C001000010000003700212062586B67646D39705932556761584D6762586B676347467A63336476636D513D", "hex");
+    var pcontent= Buffer.from("0000818000010001000000000A5F65736E695F70757368076578616D706C6503636F6D0000100001C00C001000010000003700212062586B67646D39705932556761584D6762586B676347467A63336476636D513D", "hex");
 
     push = res.push({
       hostname: 'foo.example.com:' + serverPort,
       port: serverPort,
       path: '/dns-pushed-response?dns=AAABAAABAAAAAAABCl9lc25pX3B1c2gHZXhhbXBsZQNjb20AABAAAQAAKRAAAAAAAAAIAAgABAABAAA',
       method: 'GET',
       headers: {
         'accept' : 'application/dns-message'
--- a/testing/xpcshell/node-http2/lib/http.js
+++ b/testing/xpcshell/node-http2/lib/http.js
@@ -1099,17 +1099,17 @@ OutgoingRequest.prototype._start = funct
 
   for (var key in options.headers) {
     this.setHeader(key, options.headers[key]);
   }
   var headers = this._headers;
   delete headers.host;
 
   if (options.auth) {
-    headers.authorization = 'Basic ' + new Buffer(options.auth).toString('base64');
+    headers.authorization = 'Basic ' + Buffer.from(options.auth).toString('base64');
   }
 
   headers[':scheme'] = options.protocol.slice(0, -1);
   headers[':method'] = options.method;
   headers[':authority'] = options.host;
   headers[':path'] = options.path;
 
   this._log.info({ scheme: headers[':scheme'], method: headers[':method'],
--- a/testing/xpcshell/node-http2/lib/protocol/compressor.js
+++ b/testing/xpcshell/node-http2/lib/protocol/compressor.js
@@ -61,17 +61,17 @@ function entryFromPair(pair) {
 //
 // * The default header table size limit is 4096 bytes.
 // * The size of an entry is defined as follows: the size of an entry is the sum of its name's
 //   length in bytes, of its value's length in bytes and of 32 bytes.
 // * The size of a header table is the sum of the size of its entries.
 var DEFAULT_HEADER_TABLE_LIMIT = 4096;
 
 function size(entry) {
-  return (new Buffer(entry[0] + entry[1], 'utf8')).length + 32;
+  return (Buffer.from(entry[0] + entry[1], 'utf8')).length + 32;
 }
 
 // The `add(index, entry)` can be used to [manage the header table][tablemgmt]:
 // [tablemgmt]: https://tools.ietf.org/html/rfc7541#section-4
 //
 // * it pushes the new `entry` at the beggining of the table
 // * before doing such a modification, it has to be ensured that the header table size will stay
 //   lower than or equal to the header table size limit. To achieve this, entries are evicted from
@@ -391,17 +391,17 @@ HeaderSetCompressor.prototype._flush = f
 //       1. Compute Q and R, quotient and remainder of I divided by 2^7
 //       2. If Q is strictly greater than 0, write one 1 bit; otherwise, write one 0 bit
 //       3. Encode R on the next 7 bits
 //       4. I = Q
 
 HeaderSetCompressor.integer = function writeInteger(I, N) {
   var limit = Math.pow(2,N) - 1;
   if (I < limit) {
-    return [new Buffer([I])];
+    return [Buffer.from([I])];
   }
 
   var bytes = [];
   if (N !== 0) {
     bytes.push(limit);
   }
   I -= limit;
 
@@ -413,17 +413,17 @@ HeaderSetCompressor.integer = function w
     if (Q > 0) {
       R += 128;
     }
     bytes.push(R);
 
     I = Q;
   }
 
-  return [new Buffer(bytes)];
+  return [Buffer.from(bytes)];
 };
 
 // The inverse algorithm:
 //
 // 1. Set I to the number coded on the lower N bits of the first byte
 // 2. If I is smaller than 2^N - 1 then return I
 // 2. Else the number is encoded on more than one byte, so do the following steps:
 //    1. Set M to 0
@@ -525,17 +525,17 @@ HuffmanTable.prototype.encode = function
       }
     }
   }
 
   if (space !== 8) {
     add(this.codes[256] >> (this.lengths[256] - space));
   }
 
-  return new Buffer(result);
+  return Buffer.from(result);
 };
 
 HuffmanTable.prototype.decode = function decode(buffer) {
   var result = [];
   var subtree = this.tree;
 
   for (var i = 0; i < buffer.length; i++) {
     var byte = buffer[i];
@@ -547,17 +547,17 @@ HuffmanTable.prototype.decode = function
       subtree = subtree[bit];
       if (subtree.length === 1) {
         result.push(subtree[0]);
         subtree = this.tree;
       }
     }
   }
 
-  return new Buffer(result);
+  return Buffer.from(result);
 };
 
 // The initializer arrays for the Huffman tables are generated with feeding the tables from the
 // spec to this sed command:
 //
 //     sed -e "s/^.* [|]//g" -e "s/|//g" -e "s/ .*//g" -e "s/^/  '/g" -e "s/$/',/g"
 
 HuffmanTable.huffmanTable = new HuffmanTable([
@@ -847,17 +847,17 @@ HuffmanTable.huffmanTable = new HuffmanT
 //     |   Value Length (0-N bytes)    |
 //     +---+---+---+---+---+---+---+---+
 //     ...
 //     +---+---+---+---+---+---+---+---+
 //     |  Field Bytes Without Encoding |
 //     +---+---+---+---+---+---+---+---+
 
 HeaderSetCompressor.string = function writeString(str) {
-  str = new Buffer(str, 'utf8');
+  str = Buffer.from(str, 'utf8');
 
   var huffman = HuffmanTable.huffmanTable.encode(str);
   if (huffman.length < str.length) {
     var length = HeaderSetCompressor.integer(huffman.length, 7);
     length[0][0] |= 128;
     return length.concat(huffman);
   }
 
@@ -1336,17 +1336,17 @@ Decompressor.prototype._transform = func
 
 // Concatenate an array of buffers into a new buffer
 function concat(buffers) {
   var size = 0;
   for (var i = 0; i < buffers.length; i++) {
     size += buffers[i].length;
   }
 
-  var concatenated = new Buffer(size);
+  var concatenated = Buffer.alloc(size);
   for (var cursor = 0, j = 0; j < buffers.length; cursor += buffers[j].length, j++) {
     buffers[j].copy(concatenated, cursor);
   }
 
   return concatenated;
 }
 
 // Cut `buffer` into chunks not larger than `size`
--- a/testing/xpcshell/node-http2/lib/protocol/connection.js
+++ b/testing/xpcshell/node-http2/lib/protocol/connection.js
@@ -510,17 +510,17 @@ Connection.prototype._generatePingId = f
     }
   } while(id in this._pings);
   return id;
 };
 
 // Sending a ping and calling `callback` when the answer arrives
 Connection.prototype.ping = function ping(callback) {
   var id = this._generatePingId();
-  var data = new Buffer(id, 'hex');
+  var data = Buffer.from(id, 'hex');
   this._pings[id] = callback;
 
   this._log.debug({ data: data }, 'Sending PING.');
   this.push({
     type: 'PING',
     flags: {
       ACK: false
     },
--- a/testing/xpcshell/node-http2/lib/protocol/endpoint.js
+++ b/testing/xpcshell/node-http2/lib/protocol/endpoint.js
@@ -73,17 +73,17 @@ function Endpoint(log, role, settings, f
   // * Initializing error handling.
   this._initializeErrorHandling();
 }
 Endpoint.prototype = Object.create(Duplex.prototype, { constructor: { value: Endpoint } });
 
 // Handshake
 // ---------
 
-var CLIENT_PRELUDE = new Buffer('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n');
+var CLIENT_PRELUDE = Buffer.from('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n');
 
 // Writing the client header is simple and synchronous.
 Endpoint.prototype._writePrelude = function _writePrelude() {
   this._log.debug('Sending the client connection header prelude.');
   this.push(CLIENT_PRELUDE);
 };
 
 // The asynchronous process of reading the client header:
--- a/testing/xpcshell/node-http2/lib/protocol/framer.js
+++ b/testing/xpcshell/node-http2/lib/protocol/framer.js
@@ -76,17 +76,17 @@ Deserializer.prototype = Object.create(T
 
 // The Deserializer is stateful, and it's two main alternating states are: *waiting for header* and
 // *waiting for payload*. The state is stored in the boolean property `_waitingForHeader`.
 //
 // When entering a new state, a `_buffer` is created that will hold the accumulated data (header or
 // payload). The `_cursor` is used to track the progress.
 Deserializer.prototype._next = function(size) {
   this._cursor = 0;
-  this._buffer = new Buffer(size);
+  this._buffer = Buffer.alloc(size);
   this._waitingForHeader = !this._waitingForHeader;
   if (this._waitingForHeader) {
     this._frame = {};
   }
 };
 
 // Parsing an incoming buffer is an iterative process because it can hold multiple frames if it's
 // large enough. A `cursor` is used to track the progress in parsing the incoming `chunk`.
@@ -199,17 +199,17 @@ var frameTypes = [];
 
 var frameFlags = {};
 
 var genericAttributes = ['type', 'flags', 'stream'];
 
 var typeSpecificAttributes = {};
 
 Serializer.commonHeader = function writeCommonHeader(frame, buffers) {
-  var headerBuffer = new Buffer(COMMON_HEADER_SIZE);
+  var headerBuffer = Buffer.alloc(COMMON_HEADER_SIZE);
 
   var size = 0;
   for (var i = 0; i < buffers.length; i++) {
     size += buffers[i].length;
   }
   headerBuffer.writeUInt8(0, 0);
   headerBuffer.writeUInt16BE(size, 1);
 
@@ -358,17 +358,17 @@ typeSpecificAttributes.HEADERS = ['prior
 //     +---------------------------------------------------------------+
 //     |                           Padding (*)                       ...
 //     +---------------------------------------------------------------+
 //
 // The payload of a HEADERS frame contains a Headers Block
 
 Serializer.HEADERS = function writeHeadersPriority(frame, buffers) {
   if (frame.flags.PRIORITY) {
-    var buffer = new Buffer(5);
+    var buffer = Buffer.alloc(5);
     assert((0 <= frame.priorityDependency) && (frame.priorityDependency <= 0x7fffffff), frame.priorityDependency);
     buffer.writeUInt32BE(frame.priorityDependency, 0);
     if (frame.exclusiveDependency) {
       buffer[0] |= 0x80;
     }
     assert((0 <= frame.priorityWeight) && (frame.priorityWeight <= 0xff), frame.priorityWeight);
     buffer.writeUInt8(frame.priorityWeight, 4);
     buffers.push(buffer);
@@ -392,17 +392,17 @@ Deserializer.HEADERS = function readHead
   var dataOffset = 0;
   var paddingLength = 0;
   if (frame.flags.PADDED) {
     paddingLength = (buffer.readUInt8(dataOffset) & 0xff);
     dataOffset = 1;
   }
 
   if (frame.flags.PRIORITY) {
-    var dependencyData = new Buffer(4);
+    var dependencyData = Buffer.alloc(4);
     buffer.copy(dependencyData, 0, dataOffset, dataOffset + 4);
     dataOffset += 4;
     frame.exclusiveDependency = !!(dependencyData[0] & 0x80);
     dependencyData[0] &= 0x7f;
     frame.priorityDependency = dependencyData.readUInt32BE(0);
     frame.priorityWeight = buffer.readUInt8(dataOffset);
     dataOffset += 1;
   }
@@ -437,34 +437,34 @@ typeSpecificAttributes.PRIORITY = ['prio
 //     |E|                 Stream Dependency? (31)                     |
 //     +-+-------------+-----------------------------------------------+
 //     |  Weight? (8)  |
 //     +-+-------------+
 //
 // The payload of a PRIORITY frame contains an exclusive bit, a 31-bit dependency, and an 8-bit weight
 
 Serializer.PRIORITY = function writePriority(frame, buffers) {
-  var buffer = new Buffer(5);
+  var buffer = Buffer.alloc(5);
   assert((0 <= frame.priorityDependency) && (frame.priorityDependency <= 0x7fffffff), frame.priorityDependency);
   buffer.writeUInt32BE(frame.priorityDependency, 0);
   if (frame.exclusiveDependency) {
     buffer[0] |= 0x80;
   }
   assert((0 <= frame.priorityWeight) && (frame.priorityWeight <= 0xff), frame.priorityWeight);
   buffer.writeUInt8(frame.priorityWeight, 4);
 
   buffers.push(buffer);
 };
 
 Deserializer.PRIORITY = function readPriority(buffer, frame) {
   if (buffer.length < 5) {
     // PRIORITY frames are 5 bytes long. Bad peer!
     return 'FRAME_SIZE_ERROR';
   }
-  var dependencyData = new Buffer(4);
+  var dependencyData = Buffer.alloc(4);
   buffer.copy(dependencyData, 0, 0, 4);
   frame.exclusiveDependency = !!(dependencyData[0] & 0x80);
   dependencyData[0] &= 0x7f;
   frame.priorityDependency = dependencyData.readUInt32BE(0);
   frame.priorityWeight = buffer.readUInt8(4);
 };
 
 // [RST_STREAM](https://tools.ietf.org/html/rfc7540#section-6.4)
@@ -485,17 +485,17 @@ typeSpecificAttributes.RST_STREAM = ['er
 //     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 //     |                         Error Code (32)                       |
 //     +---------------------------------------------------------------+
 //
 // The RST_STREAM frame contains a single unsigned, 32-bit integer identifying the error
 // code (see Error Codes). The error code indicates why the stream is being terminated.
 
 Serializer.RST_STREAM = function writeRstStream(frame, buffers) {
-  var buffer = new Buffer(4);
+  var buffer = Buffer.alloc(4);
   var code = errorCodes.indexOf(frame.error);
   assert((0 <= code) && (code <= 0xffffffff), code);
   buffer.writeUInt32BE(code, 0);
   buffers.push(buffer);
 };
 
 Deserializer.RST_STREAM = function readRstStream(buffer, frame) {
   if (buffer.length < 4) {
@@ -550,17 +550,17 @@ Serializer.SETTINGS = function writeSett
     if (setting.name in frame.settings) {
       settingsLeft.splice(settingsLeft.indexOf(setting.name), 1);
       var value = frame.settings[setting.name];
       settings.push({ id: id, value: setting.flag ? Boolean(value) : value });
     }
   });
   assert(settingsLeft.length === 0, 'Unknown settings: ' + settingsLeft.join(', '));
 
-  var buffer = new Buffer(settings.length * 6);
+  var buffer = Buffer.alloc(settings.length * 6);
   for (var i = 0; i < settings.length; i++) {
     buffer.writeUInt16BE(settings[i].id & 0xffff, i*6);
     buffer.writeUInt32BE(settings[i].value, i*6 + 2);
   }
 
   buffers.push(buffer);
 };
 
@@ -646,17 +646,17 @@ typeSpecificAttributes.PUSH_PROMISE = ['
 //     |                         Padding (*)                         ...
 //     +---------------------------------------------------------------+
 //
 // The PUSH_PROMISE frame includes the unsigned 31-bit identifier of
 // the stream the endpoint plans to create along with a minimal set of headers that provide
 // additional context for the stream.
 
 Serializer.PUSH_PROMISE = function writePushPromise(frame, buffers) {
-  var buffer = new Buffer(4);
+  var buffer = Buffer.alloc(4);
 
   var promised_stream = frame.promised_stream;
   assert((0 <= promised_stream) && (promised_stream <= 0x7fffffff), promised_stream);
   buffer.writeUInt32BE(promised_stream, 0);
 
   buffers.push(buffer);
   buffers.push(frame.data);
 };
@@ -740,17 +740,17 @@ typeSpecificAttributes.GOAWAY = ['last_s
 // The last stream identifier in the GOAWAY frame contains the highest numbered stream identifier
 // for which the sender of the GOAWAY frame has received frames on and might have taken some action
 // on.
 //
 // The GOAWAY frame also contains a 32-bit error code (see Error Codes) that contains the reason for
 // closing the connection.
 
 Serializer.GOAWAY = function writeGoaway(frame, buffers) {
-  var buffer = new Buffer(8);
+  var buffer = Buffer.alloc(8);
 
   var last_stream = frame.last_stream;
   assert((0 <= last_stream) && (last_stream <= 0x7fffffff), last_stream);
   buffer.writeUInt32BE(last_stream, 0);
 
   var code = errorCodes.indexOf(frame.error);
   assert((0 <= code) && (code <= 0xffffffff), code);
   buffer.writeUInt32BE(code, 4);
@@ -785,17 +785,17 @@ frameFlags.WINDOW_UPDATE = [];
 typeSpecificAttributes.WINDOW_UPDATE = ['window_size'];
 
 // The payload of a WINDOW_UPDATE frame is a 32-bit value indicating the additional number of bytes
 // that the sender can transmit in addition to the existing flow control window. The legal range
 // for this field is 1 to 2^31 - 1 (0x7fffffff) bytes; the most significant bit of this value is
 // reserved.
 
 Serializer.WINDOW_UPDATE = function writeWindowUpdate(frame, buffers) {
-  var buffer = new Buffer(4);
+  var buffer = Buffer.alloc(4);
 
   var window_size = frame.window_size;
   assert((0 < window_size) && (window_size <= 0x7fffffff), window_size);
   buffer.writeUInt32BE(window_size, 0);
 
   buffers.push(buffer);
 };
 
@@ -875,36 +875,36 @@ function istchar(c) {
   return ('!#$&\'*+-.^_`|~1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.indexOf(c) > -1);
 }
 
 function hexencode(s) {
   var t = '';
   for (var i = 0; i < s.length; i++) {
     if (!istchar(s[i])) {
       t += '%';
-      t += new Buffer(s[i]).toString('hex');
+      t += Buffer.from(s[i]).toString('hex');
     } else {
       t += s[i];
     }
   }
   return t;
 }
 
 Serializer.ALTSVC = function writeAltSvc(frame, buffers) {
-  var buffer = new Buffer(2);
+  var buffer = Buffer.alloc(2);
   buffer.writeUInt16BE(frame.origin.length, 0);
   buffers.push(buffer);
-  buffers.push(new Buffer(frame.origin, 'ascii'));
+  buffers.push(Buffer.from(frame.origin, 'ascii'));
 
   var fieldValue = hexencode(frame.protocolID) + '="' + frame.host + ':' + frame.port + '"';
   if (frame.maxAge !== 86400) { // 86400 is the default
     fieldValue += "; ma=" + frame.maxAge;
   }
 
-  buffers.push(new Buffer(fieldValue, 'ascii'));
+  buffers.push(Buffer.from(fieldValue, 'ascii'));
 };
 
 function stripquotes(s) {
   var start = 0;
   var end = s.length;
   while ((start < end) && (s[start] === '"')) {
     start++;
   }
@@ -1018,17 +1018,17 @@ function unescape(s) {
       if (i < s.length) {
         hexvalue += s[i];
         ++i;
       }
       if (i < s.length) {
         hexvalue += s[i];
       }
       if (hexvalue.length > 0) {
-        t += new Buffer(hexvalue, 'hex').toString();
+        t += Buffer.from(hexvalue, 'hex').toString();
       } else {
         t += '%';
       }
     }
 
     ++i;
   }
   return t;
@@ -1073,20 +1073,20 @@ Deserializer.ALTSVC = function readAltSv
 // throw PROTOCOL_ERROR upon seeing it with non 0 payload
 
 frameTypes[0xC] = 'ORIGIN';
 frameFlags.ORIGIN = [];
 typeSpecificAttributes.ORIGIN = ['originList'];
 
 Serializer.ORIGIN = function writeOrigin(frame, buffers) {
   for (var i = 0; i < frame.originList.length; i++) {
-    var buffer = new Buffer(2);
+    var buffer = Buffer.alloc(2);
     buffer.writeUInt16BE(frame.originList[i].length, 0);
     buffers.push(buffer);
-    buffers.push(new Buffer(frame.originList[i], 'ascii'));
+    buffers.push(Buffer.from(frame.originList[i], 'ascii'));
   }
 };
 
 Deserializer.ORIGIN = function readOrigin(buffer, frame) {
     // ignored
 };
 
 
--- a/testing/xpcshell/node-http2/lib/protocol/stream.js
+++ b/testing/xpcshell/node-http2/lib/protocol/stream.js
@@ -340,17 +340,17 @@ Stream.prototype._send = function _send(
     sendMore();
   }
 };
 
 // When the stream is finishing (the user calls `end()` on it), then we have to set the `END_STREAM`
 // flag on the last frame. If there's no frame in the queue, or if it doesn't support this flag,
 // then we create a 0 length DATA frame. We could do this all the time, but putting the flag on an
 // existing frame is a nice optimization.
-var emptyBuffer = new Buffer(0);
+var emptyBuffer = Buffer.alloc(0);
 Stream.prototype._finishing = function _finishing() {
   var endFrame = {
     type: 'DATA',
     flags: { END_STREAM: true },
     stream: this.id,
     data: emptyBuffer
   };
 
--- a/testing/xpcshell/node-http2/test/compressor.js
+++ b/testing/xpcshell/node-http2/test/compressor.js
@@ -7,37 +7,37 @@ var HuffmanTable = compressor.HuffmanTab
 var HeaderSetCompressor = compressor.HeaderSetCompressor;
 var HeaderSetDecompressor = compressor.HeaderSetDecompressor;
 var Compressor = compressor.Compressor;
 var Decompressor = compressor.Decompressor;
 
 var test_integers = [{
   N: 5,
   I: 10,
-  buffer: new Buffer([10])
+  buffer: Buffer.from([10])
 }, {
   N: 0,
   I: 10,
-  buffer: new Buffer([10])
+  buffer: Buffer.from([10])
 }, {
   N: 5,
   I: 1337,
-  buffer: new Buffer([31, 128 + 26, 10])
+  buffer: Buffer.from([31, 128 + 26, 10])
 }, {
   N: 0,
   I: 1337,
-  buffer: new Buffer([128 + 57, 10])
+  buffer: Buffer.from([128 + 57, 10])
 }];
 
 var test_strings = [{
   string: 'www.foo.com',
-  buffer: new Buffer('89f1e3c2f29ceb90f4ff', 'hex')
+  buffer: Buffer.from('89f1e3c2f29ceb90f4ff', 'hex')
 }, {
   string: 'éáűőúöüó€',
-  buffer: new Buffer('13c3a9c3a1c5b1c591c3bac3b6c3bcc3b3e282ac', 'hex')
+  buffer: Buffer.from('13c3a9c3a1c5b1c591c3bac3b6c3bcc3b3e282ac', 'hex')
 }];
 
 test_huffman_request = {
   'GET': 'c5837f',
   'http': '9d29af',
   '/': '63',
   'www.foo.com': 'f1e3c2f29ceb90f4ff',
   'https': '9d29ad1f',
@@ -80,280 +80,280 @@ var test_headers = [{
   header: {
     name: 1,
     value: 1,
     index: false,
     mustNeverIndex: false,
     contextUpdate: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('82', 'hex')
+  buffer: Buffer.from('82', 'hex')
 }, {
   // index
   header: {
     name: 5,
     value: 5,
     index: false,
     mustNeverIndex: false,
     contextUpdate: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('86', 'hex')
+  buffer: Buffer.from('86', 'hex')
 }, {
   // index
   header: {
     name: 3,
     value: 3,
     index: false,
     mustNeverIndex: false,
     contextUpdate: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('84', 'hex')
+  buffer: Buffer.from('84', 'hex')
 }, {
   // literal w/index, name index
   header: {
     name: 0,
     value: 'www.foo.com',
     index: true,
     mustNeverIndex: false,
     contextUpdate: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('41' + '89f1e3c2f29ceb90f4ff', 'hex')
+  buffer: Buffer.from('41' + '89f1e3c2f29ceb90f4ff', 'hex')
 }, {
   // indexed
   header: {
     name: 1,
     value: 1,
     index: false,
     mustNeverIndex: false,
     contextUpdate: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('82', 'hex')
+  buffer: Buffer.from('82', 'hex')
 }, {
   // indexed
   header: {
     name: 6,
     value: 6,
     index: false,
     mustNeverIndex: false,
     contextUpdate: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('87', 'hex')
+  buffer: Buffer.from('87', 'hex')
 }, {
   // indexed
   header: {
     name: 3,
     value: 3,
     index: false,
     mustNeverIndex: false,
     contextUpdate: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('84', 'hex')
+  buffer: Buffer.from('84', 'hex')
 }, {
   // literal w/index, name index
   header: {
     name: 0,
     value: 'www.bar.com',
     index: true,
     mustNeverIndex: false,
     contextUpdate: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('41' + '89f1e3c2f18ec5c87a7f', 'hex')
+  buffer: Buffer.from('41' + '89f1e3c2f18ec5c87a7f', 'hex')
 }, {
   // literal w/index, name index
   header: {
     name: 23,
     value: 'no-cache',
     index: true,
     mustNeverIndex: false,
     contextUpdate: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('58' + '86a8eb10649cbf', 'hex')
+  buffer: Buffer.from('58' + '86a8eb10649cbf', 'hex')
 }, {
   // index
   header: {
     name: 1,
     value: 1,
     index: false,
     mustNeverIndex: false,
     contextUpdate: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('82', 'hex')
+  buffer: Buffer.from('82', 'hex')
 }, {
   // index
   header: {
     name: 6,
     value: 6,
     index: false,
     mustNeverIndex: false,
     contextUpdate: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('87', 'hex')
+  buffer: Buffer.from('87', 'hex')
 }, {
   // literal w/index, name index
   header: {
     name: 3,
     value: '/custom-path.css',
     index: true,
     mustNeverIndex: false,
     contextUpdate: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('44' + '8b6096a127a56ac699d72211', 'hex')
+  buffer: Buffer.from('44' + '8b6096a127a56ac699d72211', 'hex')
 }, {
   // index
   header: {
     name: 63,
     value: 63,
     index: false,
     mustNeverIndex: false,
     contextUpdate: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('C0', 'hex')
+  buffer: Buffer.from('C0', 'hex')
 }, {
   // literal w/index, new name & value
   header: {
     name: 'custom-key',
     value: 'custom-value',
     index: true,
     mustNeverIndex: false,
     contextUpdate: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('40' + '8825a849e95ba97d7f' + '8925a849e95bb8e8b4bf', 'hex')
+  buffer: Buffer.from('40' + '8825a849e95ba97d7f' + '8925a849e95bb8e8b4bf', 'hex')
 }, {
   // index
   header: {
     name: 1,
     value: 1,
     index: false,
     mustNeverIndex: false,
     contextUpdate: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('82', 'hex')
+  buffer: Buffer.from('82', 'hex')
 }, {
   // index
   header: {
     name: 6,
     value: 6,
     index: false,
     mustNeverIndex: false,
     contextUpdate: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('87', 'hex')
+  buffer: Buffer.from('87', 'hex')
 }, {
   // index
   header: {
     name: 62,
     value: 62,
     index: false,
     mustNeverIndex: false,
     contextUpdate: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('BF', 'hex')
+  buffer: Buffer.from('BF', 'hex')
 }, {
   // index
   header: {
     name: 65,
     value: 65,
     index: false,
     mustNeverIndex: false,
     contextUpdate: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('C2', 'hex')
+  buffer: Buffer.from('C2', 'hex')
 }, {
   // index
   header: {
     name: 64,
     value: 64,
     index: false,
     mustNeverIndex: false,
     contextUpdate: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('C1', 'hex')
+  buffer: Buffer.from('C1', 'hex')
 }, {
   // index
   header: {
     name: 61,
     value: 61,
     index: false,
     mustNeverIndex: false,
     contextUpdate: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('BE', 'hex')
+  buffer: Buffer.from('BE', 'hex')
 }, {
   // Literal w/o index, name index
   header: {
     name: 6,
     value: "whatever",
     index: false,
     mustNeverIndex: false,
     contextUpdate: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('07' + '86f138d25ee5b3', 'hex')
+  buffer: Buffer.from('07' + '86f138d25ee5b3', 'hex')
 }, {
   // Literal w/o index, new name & value
   header: {
     name: "foo",
     value: "bar",
     index: false,
     mustNeverIndex: false,
     contextUpdate: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('00' + '8294e7' + '03626172', 'hex')
+  buffer: Buffer.from('00' + '8294e7' + '03626172', 'hex')
 }, {
   // Literal never indexed, name index
   header: {
     name: 6,
     value: "whatever",
     index: false,
     mustNeverIndex: true,
     contextUpdate: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('17' + '86f138d25ee5b3', 'hex')
+  buffer: Buffer.from('17' + '86f138d25ee5b3', 'hex')
 }, {
   // Literal never indexed, new name & value
   header: {
     name: "foo",
     value: "bar",
     index: false,
     mustNeverIndex: true,
     contextUpdate: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('10' + '8294e7' + '03626172', 'hex')
+  buffer: Buffer.from('10' + '8294e7' + '03626172', 'hex')
 }, {
   header: {
     name: -1,
     value: -1,
     index: false,
     mustNeverIndex: false,
     contextUpdate: true,
     newMaxSize: 100
   },
-  buffer: new Buffer('3F45', 'hex')
+  buffer: Buffer.from('3F45', 'hex')
 }];
 
 var test_header_sets = [{
   headers: {
     ':method': 'GET',
     ':scheme': 'http',
     ':path': '/',
     ':authority': 'www.foo.com'
@@ -393,36 +393,36 @@ describe('compressor.js', function() {
   });
 
   describe('HuffmanTable', function() {
     describe('method encode(buffer)', function() {
       it('should return the Huffman encoded version of the input buffer', function() {
         var table = HuffmanTable.huffmanTable;
         for (var decoded in test_huffman_request) {
           var encoded = test_huffman_request[decoded];
-          expect(table.encode(new Buffer(decoded)).toString('hex')).to.equal(encoded);
+          expect(table.encode(Buffer.from(decoded)).toString('hex')).to.equal(encoded);
         }
         table = HuffmanTable.huffmanTable;
         for (decoded in test_huffman_response) {
           encoded = test_huffman_response[decoded];
-          expect(table.encode(new Buffer(decoded)).toString('hex')).to.equal(encoded);
+          expect(table.encode(Buffer.from(decoded)).toString('hex')).to.equal(encoded);
         }
       });
     });
     describe('method decode(buffer)', function() {
       it('should return the Huffman decoded version of the input buffer', function() {
         var table = HuffmanTable.huffmanTable;
         for (var decoded in test_huffman_request) {
           var encoded = test_huffman_request[decoded];
-          expect(table.decode(new Buffer(encoded, 'hex')).toString()).to.equal(decoded);
+          expect(table.decode(Buffer.from(encoded, 'hex')).toString()).to.equal(decoded);
         }
         table = HuffmanTable.huffmanTable;
         for (decoded in test_huffman_response) {
           encoded = test_huffman_response[decoded];
-          expect(table.decode(new Buffer(encoded, 'hex')).toString()).to.equal(decoded);
+          expect(table.decode(Buffer.from(encoded, 'hex')).toString()).to.equal(decoded);
         }
       });
     });
   });
 
   describe('HeaderSetCompressor', function() {
     describe('static method .integer(I, N)', function() {
       it('should return an array of buffers that represent the N-prefix coded form of the integer I', function() {
@@ -504,22 +504,22 @@ describe('compressor.js', function() {
         decompressor.on('error', function() {
           error_occured = true;
         });
         decompressor.write({
           type: 'HEADERS',
           flags: {
             END_HEADERS: false
           },
-          data: new Buffer(5)
+          data: Buffer.alloc(5)
         });
         decompressor.write({
           type: 'DATA',
           flags: {},
-          data: new Buffer(5)
+          data: Buffer.alloc(5)
         });
         expect(error_occured).to.be.equal(true);
       });
     });
   });
 
   describe('invariant', function() {
     describe('decompressor.decompress(compressor.compress(headerset)) === headerset', function() {
@@ -559,17 +559,17 @@ describe('compressor.js', function() {
     });
     describe('huffmanTable.decompress(huffmanTable.compress(buffer)) === buffer', function() {
       it('should be true for any buffer', function() {
         for (var i = 0; i < 10; i++) {
           var buffer = [];
           while (Math.random() > 0.1) {
             buffer.push(Math.floor(Math.random() * 256))
           }
-          buffer = new Buffer(buffer);
+          buffer = Buffer.from(buffer);
           var table = HuffmanTable.huffmanTable;
           var result = table.decode(table.encode(buffer));
           expect(result).to.deep.equal(buffer);
         }
       });
     });
   });
 });
--- a/testing/xpcshell/node-http2/test/connection.js
+++ b/testing/xpcshell/node-http2/test/connection.js
@@ -68,17 +68,17 @@ describe('connection.js', function() {
           var connection = new Connection(util.log, 1, settings);
 
           connection._receivePing({
             stream: 0,
             type: 'PING',
             flags: {
               'PONG': true
             },
-            data: new Buffer(8)
+            data: Buffer.alloc(8)
           });
         });
       });
     });
   });
   describe('test scenario', function() {
     var c, s;
     beforeEach(function() {
@@ -97,21 +97,21 @@ describe('connection.js', function() {
     });
     describe('sending/receiving a request', function() {
       it('should work as expected', function(done) {
         // Request and response data
         var request_headers = {
           ':method': 'GET',
           ':path': '/'
         };
-        var request_data = new Buffer(0);
+        var request_data = Buffer.alloc(0);
         var response_headers = {
           ':status': '200'
         };
-        var response_data = new Buffer('12345678', 'hex');
+        var response_data = Buffer.from('12345678', 'hex');
 
         // Setting up server
         s.on('stream', function(server_stream) {
           server_stream.on('headers', function(headers) {
             expect(headers).to.deep.equal(request_headers);
             server_stream.headers(response_headers);
             server_stream.end(response_data);
           });
@@ -135,18 +135,18 @@ describe('connection.js', function() {
       });
     });
     describe('server push', function() {
       it('should work as expected', function(done) {
         var request_headers = { ':method': 'get', ':path': '/' };
         var response_headers = { ':status': '200' };
         var push_request_headers = { ':method': 'get', ':path': '/x' };
         var push_response_headers = { ':status': '200' };
-        var response_content = new Buffer(10);
-        var push_content = new Buffer(10);
+        var response_content = Buffer.alloc(10);
+        var push_content = Buffer.alloc(10);
 
         done = util.callNTimes(5, done);
 
         s.on('stream', function(response) {
           response.headers(response_headers);
 
           var pushed = response.promise(push_request_headers);
           pushed.headers(push_response_headers);
--- a/testing/xpcshell/node-http2/test/flow.js
+++ b/testing/xpcshell/node-http2/test/flow.js
@@ -91,17 +91,17 @@ describe('flow.js', function() {
           flow._send = util.noop;
           flow._window = 10;
           flow._queue = [priorityFrame, dataFrame];
 
           expect(flow.read()).to.equal(priorityFrame);
           expect(flow.read()).to.equal(dataFrame);
         });
         it('should also split DATA frames when needed', function() {
-          var buffer = new Buffer(10);
+          var buffer = Buffer.alloc(10);
           var dataFrame = { type: 'DATA', flags: {}, stream: util.random(0, 100), data: buffer };
           flow._send = util.noop;
           flow._window = 5;
           flow._queue = [dataFrame];
 
           var expectedFragment = { flags: {}, type: 'DATA', stream: dataFrame.stream, data: buffer.slice(0,5) };
           expect(flow.read()).to.deep.equal(expectedFragment);
           expect(dataFrame.data).to.deep.equal(buffer.slice(5));
@@ -127,17 +127,17 @@ describe('flow.js', function() {
       it('call with a DATA frame should trigger sending WINDOW_UPDATE if remote flow control is not' +
          'disabled', function(done) {
         flow._window = 100;
         flow._send = util.noop;
         flow._receive = function(frame, callback) {
           callback();
         };
 
-        var buffer = new Buffer(util.random(10, 100));
+        var buffer = Buffer.alloc(util.random(10, 100));
         flow.write({ type: 'DATA', flags: {}, data: buffer });
         flow.once('readable', function() {
           expect(flow.read()).to.be.deep.equal({
             type: 'WINDOW_UPDATE',
             flags: {},
             stream: flow._flowControlId,
             window_size: buffer.length
           });
@@ -158,20 +158,20 @@ describe('flow.js', function() {
 
     describe('sending a large data stream', function() {
       it('should work as expected', function(done) {
         // Sender side
         var frameNumber = util.random(5, 8);
         var input = [];
         flow1._send = function _send() {
           if (input.length >= frameNumber) {
-            this.push({ type: 'DATA', flags: { END_STREAM: true }, data: new Buffer(0) });
+            this.push({ type: 'DATA', flags: { END_STREAM: true }, data: Buffer.alloc(0) });
             this.push(null);
           } else {
-            var buffer = new Buffer(util.random(1000, 100000));
+            var buffer = Buffer.allocUnsafe(util.random(1000, 100000));
             input.push(buffer);
             this.push({ type: 'DATA', flags: {}, data: buffer });
           }
         };
 
         // Receiver side
         var output = [];
         flow2._receive = function _receive(frame, callback) {
@@ -202,20 +202,20 @@ describe('flow.js', function() {
 
     describe('when running out of window', function() {
       it('should send a BLOCKED frame', function(done) {
         // Sender side
         var frameNumber = util.random(5, 8);
         var input = [];
         flow1._send = function _send() {
           if (input.length >= frameNumber) {
-            this.push({ type: 'DATA', flags: { END_STREAM: true }, data: new Buffer(0) });
+            this.push({ type: 'DATA', flags: { END_STREAM: true }, data: Buffer.alloc(0) });
             this.push(null);
           } else {
-            var buffer = new Buffer(util.random(1000, 100000));
+            var buffer = Buffer.allocUnsafe(util.random(1000, 100000));
             input.push(buffer);
             this.push({ type: 'DATA', flags: {}, data: buffer });
           }
         };
 
         // Receiver side
         // Do not send WINDOW_UPDATESs except when the other side sends BLOCKED
         var output = [];
--- a/testing/xpcshell/node-http2/test/framer.js
+++ b/testing/xpcshell/node-http2/test/framer.js
@@ -21,269 +21,269 @@ var frame_types = {
 
 var test_frames = [{
   frame: {
     type: 'DATA',
     flags: { END_STREAM: false, RESERVED2: false, RESERVED4: false,
              PADDED: false },
     stream: 10,
 
-    data: new Buffer('12345678', 'hex')
+    data: Buffer.from('12345678', 'hex')
   },
   // length + type + flags + stream +   content
-  buffer: new Buffer('000004' + '00' + '00' + '0000000A' +   '12345678', 'hex')
+  buffer: Buffer.from('000004' + '00' + '00' + '0000000A' +   '12345678', 'hex')
 
 }, {
   frame: {
     type: 'HEADERS',
     flags: { END_STREAM: false, RESERVED2: false, END_HEADERS: false,
              PADDED: false, RESERVED5: false, PRIORITY: false },
     stream: 15,
 
-    data: new Buffer('12345678', 'hex')
+    data: Buffer.from('12345678', 'hex')
   },
-  buffer: new Buffer('000004' + '01' + '00' + '0000000F' +   '12345678', 'hex')
+  buffer: Buffer.from('000004' + '01' + '00' + '0000000F' +   '12345678', 'hex')
 
 }, {
   frame: {
     type: 'HEADERS',
     flags: { END_STREAM: false, RESERVED2: false, END_HEADERS: false,
              PADDED: false, RESERVED5: false, PRIORITY: true },
     stream: 15,
     priorityDependency: 10,
     priorityWeight: 5,
     exclusiveDependency: false,
 
-    data: new Buffer('12345678', 'hex')
+    data: Buffer.from('12345678', 'hex')
   },
-  buffer: new Buffer('000009' + '01' + '20' + '0000000F' + '0000000A' + '05' + '12345678', 'hex')
+  buffer: Buffer.from('000009' + '01' + '20' + '0000000F' + '0000000A' + '05' + '12345678', 'hex')
 
 
 }, {
   frame: {
     type: 'HEADERS',
     flags: { END_STREAM: false, RESERVED2: false, END_HEADERS: false,
              PADDED: false, RESERVED5: false, PRIORITY: true },
     stream: 15,
     priorityDependency: 10,
     priorityWeight: 5,
     exclusiveDependency: true,
 
-    data: new Buffer('12345678', 'hex')
+    data: Buffer.from('12345678', 'hex')
   },
-  buffer: new Buffer('000009' + '01' + '20' + '0000000F' + '8000000A' + '05' + '12345678', 'hex')
+  buffer: Buffer.from('000009' + '01' + '20' + '0000000F' + '8000000A' + '05' + '12345678', 'hex')
 
 }, {
   frame: {
     type: 'PRIORITY',
     flags: { },
     stream: 10,
 
     priorityDependency: 9,
     priorityWeight: 5,
     exclusiveDependency: false
   },
-  buffer: new Buffer('000005' + '02' + '00' + '0000000A' + '00000009' + '05', 'hex')
+  buffer: Buffer.from('000005' + '02' + '00' + '0000000A' + '00000009' + '05', 'hex')
 
 }, {
   frame: {
     type: 'PRIORITY',
     flags: { },
     stream: 10,
 
     priorityDependency: 9,
     priorityWeight: 5,
     exclusiveDependency: true
   },
-  buffer: new Buffer('000005' + '02' + '00' + '0000000A' + '80000009' + '05', 'hex')
+  buffer: Buffer.from('000005' + '02' + '00' + '0000000A' + '80000009' + '05', 'hex')
 
 }, {
   frame: {
     type: 'RST_STREAM',
     flags: { },
     stream: 10,
 
     error: 'INTERNAL_ERROR'
   },
-  buffer: new Buffer('000004' + '03' + '00' + '0000000A' +   '00000002', 'hex')
+  buffer: Buffer.from('000004' + '03' + '00' + '0000000A' +   '00000002', 'hex')
 
 }, {
   frame: {
     type: 'SETTINGS',
     flags: { ACK: false },
     stream: 10,
 
     settings: {
       SETTINGS_HEADER_TABLE_SIZE: 0x12345678,
       SETTINGS_ENABLE_PUSH: true,
       SETTINGS_MAX_CONCURRENT_STREAMS: 0x01234567,
       SETTINGS_INITIAL_WINDOW_SIZE:    0x89ABCDEF,
       SETTINGS_MAX_FRAME_SIZE:         0x00010000
     }
   },
-  buffer: new Buffer('00001E' + '04' + '00' + '0000000A' +   '0001' + '12345678' +
+  buffer: Buffer.from('00001E' + '04' + '00' + '0000000A' +   '0001' + '12345678' +
                                                              '0002' + '00000001' +
                                                              '0003' + '01234567' +
                                                              '0004' + '89ABCDEF' +
                                                              '0005' + '00010000', 'hex')
 
 }, {
   frame: {
     type: 'PUSH_PROMISE',
     flags: { RESERVED1: false, RESERVED2: false, END_PUSH_PROMISE: false,
              PADDED: false },
     stream: 15,
 
     promised_stream: 3,
-    data: new Buffer('12345678', 'hex')
+    data: Buffer.from('12345678', 'hex')
   },
-  buffer: new Buffer('000008' + '05' + '00' + '0000000F' +   '00000003' + '12345678', 'hex')
+  buffer: Buffer.from('000008' + '05' + '00' + '0000000F' +   '00000003' + '12345678', 'hex')
 
 }, {
   frame: {
     type: 'PING',
     flags: { ACK: false },
     stream: 15,
 
-    data: new Buffer('1234567887654321', 'hex')
+    data: Buffer.from('1234567887654321', 'hex')
   },
-  buffer: new Buffer('000008' + '06' + '00' + '0000000F' +   '1234567887654321', 'hex')
+  buffer: Buffer.from('000008' + '06' + '00' + '0000000F' +   '1234567887654321', 'hex')
 
 }, {
   frame: {
     type: 'GOAWAY',
     flags: { },
     stream: 10,
 
     last_stream: 0x12345678,
     error: 'PROTOCOL_ERROR'
   },
-  buffer: new Buffer('000008' + '07' + '00' + '0000000A' +   '12345678' + '00000001', 'hex')
+  buffer: Buffer.from('000008' + '07' + '00' + '0000000A' +   '12345678' + '00000001', 'hex')
 
 }, {
   frame: {
     type: 'WINDOW_UPDATE',
     flags: { },
     stream: 10,
 
     window_size: 0x12345678
   },
-  buffer: new Buffer('000004' + '08' + '00' + '0000000A' +   '12345678', 'hex')
+  buffer: Buffer.from('000004' + '08' + '00' + '0000000A' +   '12345678', 'hex')
 }, {
   frame: {
     type: 'CONTINUATION',
     flags: { RESERVED1: false, RESERVED2: false, END_HEADERS: true },
     stream: 10,
 
-    data: new Buffer('12345678', 'hex')
+    data: Buffer.from('12345678', 'hex')
   },
   // length + type + flags + stream +   content
-  buffer: new Buffer('000004' + '09' + '04' + '0000000A' +   '12345678', 'hex')
+  buffer: Buffer.from('000004' + '09' + '04' + '0000000A' +   '12345678', 'hex')
 }, {
   frame: {
     type: 'ALTSVC',
     flags: { },
     stream: 0,
 
     maxAge: 31536000,
     port: 4443,
     protocolID: "h2",
     host: "altsvc.example.com",
     origin: ""
   },
-  buffer: new Buffer(new Buffer('00002B' + '0A' + '00' + '00000000' + '0000', 'hex') + new Buffer('h2="altsvc.example.com:4443"; ma=31536000', 'ascii'))
+  buffer: Buffer.from(Buffer.from('00002B' + '0A' + '00' + '00000000' + '0000', 'hex') + Buffer.from('h2="altsvc.example.com:4443"; ma=31536000', 'ascii'))
 }, {
   frame: {
     type: 'ALTSVC',
     flags: { },
     stream: 0,
 
     maxAge: 31536000,
     port: 4443,
     protocolID: "h2",
     host: "altsvc.example.com",
     origin: "https://onlyme.example.com"
   },
-  buffer: new Buffer(new Buffer('000045' + '0A' + '00' + '00000000' + '001A', 'hex') + new Buffer('https://onlyme.example.comh2="altsvc.example.com:4443"; ma=31536000', 'ascii'))
+  buffer: Buffer.from(Buffer.from('000045' + '0A' + '00' + '00000000' + '001A', 'hex') + Buffer.from('https://onlyme.example.comh2="altsvc.example.com:4443"; ma=31536000', 'ascii'))
 
 }, {
   frame: {
     type: 'BLOCKED',
     flags: { },
     stream: 10
   },
-  buffer: new Buffer('000000' + '0B' + '00' + '0000000A', 'hex')
+  buffer: Buffer.from('000000' + '0B' + '00' + '0000000A', 'hex')
 }];
 
 var deserializer_test_frames = test_frames.slice(0);
 var padded_test_frames = [{
   frame: {
     type: 'DATA',
     flags: { END_STREAM: false, RESERVED2: false, RESERVED4: false,
              PADDED: true },
     stream: 10,
-    data: new Buffer('12345678', 'hex')
+    data: Buffer.from('12345678', 'hex')
   },
   // length + type + flags + stream + pad length + content + padding
-  buffer: new Buffer('00000B' + '00' + '08' + '0000000A' + '06' + '12345678' + '000000000000', 'hex')
+  buffer: Buffer.from('00000B' + '00' + '08' + '0000000A' + '06' + '12345678' + '000000000000', 'hex')
 
 }, {
   frame: {
     type: 'HEADERS',
     flags: { END_STREAM: false, RESERVED2: false, END_HEADERS: false,
              PADDED: true, RESERVED5: false, PRIORITY: false },
     stream: 15,
 
-    data: new Buffer('12345678', 'hex')
+    data: Buffer.from('12345678', 'hex')
   },
   // length + type + flags + stream + pad length + data + padding
-  buffer: new Buffer('00000B' + '01' + '08' + '0000000F' + '06' + '12345678' + '000000000000', 'hex')
+  buffer: Buffer.from('00000B' + '01' + '08' + '0000000F' + '06' + '12345678' + '000000000000', 'hex')
 
 }, {
   frame: {
     type: 'HEADERS',
     flags: { END_STREAM: false, RESERVED2: false, END_HEADERS: false,
              PADDED: true, RESERVED5: false, PRIORITY: true },
     stream: 15,
     priorityDependency: 10,
     priorityWeight: 5,
     exclusiveDependency: false,
 
-    data: new Buffer('12345678', 'hex')
+    data: Buffer.from('12345678', 'hex')
   },
   // length + type + flags + stream + pad length + priority dependency + priority weight + data + padding
-  buffer: new Buffer('000010' + '01' + '28' + '0000000F' + '06' + '0000000A' + '05' + '12345678' + '000000000000', 'hex')
+  buffer: Buffer.from('000010' + '01' + '28' + '0000000F' + '06' + '0000000A' + '05' + '12345678' + '000000000000', 'hex')
 
 }, {
   frame: {
     type: 'HEADERS',
     flags: { END_STREAM: false, RESERVED2: false, END_HEADERS: false,
              PADDED: true, RESERVED5: false, PRIORITY: true },
     stream: 15,
     priorityDependency: 10,
     priorityWeight: 5,
     exclusiveDependency: true,
 
-    data: new Buffer('12345678', 'hex')
+    data: Buffer.from('12345678', 'hex')
   },
   // length + type + flags + stream + pad length + priority dependency + priority weight + data + padding
-  buffer: new Buffer('000010' + '01' + '28' + '0000000F' + '06' + '8000000A' + '05' + '12345678' + '000000000000', 'hex')
+  buffer: Buffer.from('000010' + '01' + '28' + '0000000F' + '06' + '8000000A' + '05' + '12345678' + '000000000000', 'hex')
 
 }, {
   frame: {
     type: 'PUSH_PROMISE',
     flags: { RESERVED1: false, RESERVED2: false, END_PUSH_PROMISE: false,
              PADDED: true },
     stream: 15,
 
     promised_stream: 3,
-    data: new Buffer('12345678', 'hex')
+    data: Buffer.from('12345678', 'hex')
   },
   // length + type + flags + stream + pad length + promised stream + data + padding
-  buffer: new Buffer('00000F' + '05' + '08' + '0000000F' + '06' + '00000003' + '12345678' + '000000000000', 'hex')
+  buffer: Buffer.from('00000F' + '05' + '08' + '0000000F' + '06' + '00000003' + '12345678' + '000000000000', 'hex')
 
 }];
 for (var idx = 0; idx < padded_test_frames.length; idx++) {
   deserializer_test_frames.push(padded_test_frames[idx]);
 }
 
 
 describe('framer.js', function() {
@@ -317,17 +317,17 @@ describe('framer.js', function() {
 
     describe('transform stream', function() {
       it('should transform frame objects to appropriate buffers', function() {
         var stream = new Serializer(util.log);
 
         for (var i = 0; i < test_frames.length; i++) {
           var test = test_frames[i];
           stream.write(test.frame);
-          var chunk, buffer = new Buffer(0);
+          var chunk, buffer = Buffer.alloc(0);
           while (chunk = stream.read()) {
             buffer = util.concat([buffer, chunk]);
           }
           expect(buffer).to.be.deep.equal(test.buffer);
         }
       });
     });
   });
@@ -379,17 +379,17 @@ describe('framer.js', function() {
       });
     });
   });
 
   describe('bunyan formatter', function() {
     describe('`frame`', function() {
       var format = framer.serializers.frame;
       it('should assign a unique ID to each frame', function() {
-        var frame1 = { type: 'DATA', data: new Buffer(10) };
+        var frame1 = { type: 'DATA', data: Buffer.alloc(10) };
         var frame2 = { type: 'PRIORITY', priority: 1 };
         expect(format(frame1).id).to.be.equal(format(frame1));
         expect(format(frame2).id).to.be.equal(format(frame2));
         expect(format(frame1)).to.not.be.equal(format(frame2));
       });
     });
   });
 });
--- a/testing/xpcshell/node-http2/test/stream.js
+++ b/testing/xpcshell/node-http2/test/stream.js
@@ -100,79 +100,79 @@ function execute_sequence(stream, sequen
   setImmediate(execute.bind(null, check));
 }
 
 var example_frames = [
   { type: 'PRIORITY', flags: {}, priority: 1 },
   { type: 'WINDOW_UPDATE', flags: {}, settings: {} },
   { type: 'RST_STREAM', flags: {}, error: 'CANCEL' },
   { type: 'HEADERS', flags: {}, headers: {}, priority: undefined },
-  { type: 'DATA', flags: {}, data: new Buffer(5) },
+  { type: 'DATA', flags: {}, data: Buffer.alloc(5) },
   { type: 'PUSH_PROMISE', flags: {}, headers: {}, promised_stream: new Stream(util.log, null) }
 ];
 
 var invalid_incoming_frames = {
   IDLE: [
-    { type: 'DATA', flags: {}, data: new Buffer(5) },
+    { type: 'DATA', flags: {}, data: Buffer.alloc(5) },
     { type: 'WINDOW_UPDATE', flags: {}, settings: {} },
     { type: 'PUSH_PROMISE', flags: {}, headers: {} },
     { type: 'RST_STREAM', flags: {}, error: 'CANCEL' }
   ],
   RESERVED_LOCAL: [
-    { type: 'DATA', flags: {}, data: new Buffer(5) },
+    { type: 'DATA', flags: {}, data: Buffer.alloc(5) },
     { type: 'HEADERS', flags: {}, headers: {}, priority: undefined },
     { type: 'PUSH_PROMISE', flags: {}, headers: {} },
     { type: 'WINDOW_UPDATE', flags: {}, settings: {} }
   ],
   RESERVED_REMOTE: [
-    { type: 'DATA', flags: {}, data: new Buffer(5) },
+    { type: 'DATA', flags: {}, data: Buffer.alloc(5) },
     { type: 'PUSH_PROMISE', flags: {}, headers: {} },
     { type: 'WINDOW_UPDATE', flags: {}, settings: {} }
   ],
   OPEN: [
   ],
   HALF_CLOSED_LOCAL: [
   ],
   HALF_CLOSED_REMOTE: [
-    { type: 'DATA', flags: {}, data: new Buffer(5) },
+    { type: 'DATA', flags: {}, data: Buffer.alloc(5) },
     { type: 'HEADERS', flags: {}, headers: {}, priority: undefined },
     { type: 'PUSH_PROMISE', flags: {}, headers: {} }
   ]
 };
 
 var invalid_outgoing_frames = {
   IDLE: [
-    { type: 'DATA', flags: {}, data: new Buffer(5) },
+    { type: 'DATA', flags: {}, data: Buffer.alloc(5) },
     { type: 'WINDOW_UPDATE', flags: {}, settings: {} },
     { type: 'PUSH_PROMISE', flags: {}, headers: {} }
   ],
   RESERVED_LOCAL: [
-    { type: 'DATA', flags: {}, data: new Buffer(5) },
+    { type: 'DATA', flags: {}, data: Buffer.alloc(5) },
     { type: 'PUSH_PROMISE', flags: {}, headers: {} },
     { type: 'WINDOW_UPDATE', flags: {}, settings: {} }
   ],
   RESERVED_REMOTE: [
-    { type: 'DATA', flags: {}, data: new Buffer(5) },
+    { type: 'DATA', flags: {}, data: Buffer.alloc(5) },
     { type: 'HEADERS', flags: {}, headers: {}, priority: undefined },
     { type: 'PUSH_PROMISE', flags: {}, headers: {} },
     { type: 'WINDOW_UPDATE', flags: {}, settings: {} }
   ],
   OPEN: [
   ],
   HALF_CLOSED_LOCAL: [
-    { type: 'DATA', flags: {}, data: new Buffer(5) },
+    { type: 'DATA', flags: {}, data: Buffer.alloc(5) },
     { type: 'HEADERS', flags: {}, headers: {}, priority: undefined },
     { type: 'PUSH_PROMISE', flags: {}, headers: {} }
   ],
   HALF_CLOSED_REMOTE: [
   ],
   CLOSED: [
     { type: 'WINDOW_UPDATE', flags: {}, settings: {} },
     { type: 'HEADERS', flags: {}, headers: {}, priority: undefined },
-    { type: 'DATA', flags: {}, data: new Buffer(5) },
+    { type: 'DATA', flags: {}, data: Buffer.alloc(5) },
     { type: 'PUSH_PROMISE', flags: {}, headers: {}, promised_stream: new Stream(util.log, null) }
   ]
 };
 
 describe('stream.js', function() {
   describe('Stream class', function() {
     describe('._transition(sending, frame) method', function() {
       it('should emit error, and answer RST_STREAM for invalid incoming frames', function() {
@@ -255,39 +255,39 @@ describe('stream.js', function() {
         execute_sequence([
           { method  : { name: 'headers', arguments: [{ ':path': '/' }] } },
           { outgoing: { type: 'HEADERS', flags: { }, headers: { ':path': '/' } } },
           { event   : { name: 'state', data: ['OPEN'] } },
 
           { wait    : 5 },
           { method  : { name: 'end', arguments: [] } },
           { event   : { name: 'state', data: ['HALF_CLOSED_LOCAL'] } },
-          { outgoing: { type: 'DATA', flags: { END_STREAM: true  }, data: new Buffer(0) } },
+          { outgoing: { type: 'DATA', flags: { END_STREAM: true  }, data: Buffer.alloc(0) } },
 
           { wait    : 10 },
           { incoming: { type: 'HEADERS', flags: { }, headers: { ':status': 200 } } },
-          { incoming: { type: 'DATA'   , flags: { END_STREAM: true  }, data: new Buffer(5) } },
+          { incoming: { type: 'DATA'   , flags: { END_STREAM: true  }, data: Buffer.alloc(5) } },
           { event   : { name: 'headers', data: [{ ':status': 200 }] } },
           { event   : { name: 'state', data: ['CLOSED'] } },
 
           { active  : 0 }
         ], done);
       });
     });
     describe('answering request', function() {
       it('should trigger the appropriate state transitions and outgoing frames', function(done) {
-        var payload = new Buffer(5);
+        var payload = Buffer.alloc(5);
         execute_sequence([
           { incoming: { type: 'HEADERS', flags: { }, headers: { ':path': '/' } } },
           { event   : { name: 'state', data: ['OPEN'] } },
           { event   : { name: 'headers', data: [{ ':path': '/' }] } },
 
           { wait    : 5 },
-          { incoming: { type: 'DATA', flags: { }, data: new Buffer(5) } },
-          { incoming: { type: 'DATA', flags: { END_STREAM: true  }, data: new Buffer(10) } },
+          { incoming: { type: 'DATA', flags: { }, data: Buffer.alloc(5) } },
+          { incoming: { type: 'DATA', flags: { END_STREAM: true  }, data: Buffer.alloc(10) } },
           { event   : { name: 'state', data: ['HALF_CLOSED_REMOTE'] } },
 
           { wait    : 5 },
           { method  : { name: 'headers', arguments: [{ ':status': 200 }] } },
           { outgoing: { type: 'HEADERS', flags: { }, headers: { ':status': 200 } } },
 
           { wait    : 5 },
           { method  : { name: 'end', arguments: [payload] } },
@@ -295,17 +295,17 @@ describe('stream.js', function() {
           { event   : { name: 'state', data: ['CLOSED'] } },
 
           { active  : 0 }
         ], done);
       });
     });
     describe('sending push stream', function() {
       it('should trigger the appropriate state transitions and outgoing frames', function(done) {
-        var payload = new Buffer(5);
+        var payload = Buffer.alloc(5);
         var pushStream;
 
         execute_sequence([
           // receiving request
           { incoming: { type: 'HEADERS', flags: { END_STREAM: true }, headers: { ':path': '/' } } },
           { event   : { name: 'state', data: ['OPEN'] } },
           { event   : { name: 'state', data: ['HALF_CLOSED_REMOTE'] } },
           { event   : { name: 'headers', data: [{ ':path': '/' }] } },
@@ -343,17 +343,17 @@ describe('stream.js', function() {
 
             { active  : 1 }
           ], done);
         });
       });
     });
     describe('receiving push stream', function() {
       it('should trigger the appropriate state transitions and outgoing frames', function(done) {
-        var payload = new Buffer(5);
+        var payload = Buffer.alloc(5);
         var original_stream = createStream();
         var promised_stream = createStream();
 
         done = util.callNTimes(2, done);
 
         execute_sequence(original_stream, [
           // sending request headers
           { method  : { name: 'headers', arguments: [{ ':path': '/' }] } },
--- a/testing/xpcshell/node-http2/test/util.js
+++ b/testing/xpcshell/node-http2/test/util.js
@@ -58,17 +58,17 @@ exports.callNTimes = function callNTimes
 
 // Concatenate an array of buffers into a new buffer
 exports.concat = function concat(buffers) {
   var size = 0;
   for (var i = 0; i < buffers.length; i++) {
     size += buffers[i].length;
   }
 
-  var concatenated = new Buffer(size);
+  var concatenated = Buffer.alloc(size);
   for (var cursor = 0, j = 0; j < buffers.length; cursor += buffers[j].length, j++) {
     buffers[j].copy(concatenated, cursor);
   }
 
   return concatenated;
 };
 
 exports.random = function random(min, max) {
--- a/testing/xpcshell/node-ip/lib/ip.js
+++ b/testing/xpcshell/node-ip/lib/ip.js
@@ -5,17 +5,17 @@ var Buffer = require('buffer').Buffer;
 var os = require('os');
 
 ip.toBuffer = function(ip, buff, offset) {
   offset = ~~offset;
 
   var result;
 
   if (this.isV4Format(ip)) {
-    result = buff || new Buffer(offset + 4);
+    result = buff || Buffer.alloc(offset + 4);
     ip.split(/\./g).map(function(byte) {
       result[offset++] = parseInt(byte, 10) & 0xff;
     });
   } else if (this.isV6Format(ip)) {
     var sections = ip.split(':', 8);
 
     var i;
     for (i = 0; i < sections.length; i++) {
@@ -40,17 +40,17 @@ ip.toBuffer = function(ip, buff, offset)
       for (i = 0; i < sections.length && sections[i] !== ''; i++);
       var argv = [ i, 1 ];
       for (i = 9 - sections.length; i > 0; i--) {
         argv.push('0');
       }
       sections.splice.apply(sections, argv);
     }
 
-    result = buff || new Buffer(offset + 16);
+    result = buff || Buffer.alloc(offset + 16);
     for (i = 0; i < sections.length; i++) {
       var word = parseInt(sections[i], 16);
       result[offset++] = (word >> 8) & 0xff;
       result[offset++] = word & 0xff;
     }
   }
 
   if (!result) {
@@ -105,17 +105,17 @@ ip.fromPrefixLen = function(prefixlen, f
   } else {
     family = _normalizeFamily(family);
   }
 
   var len = 4;
   if (family === 'ipv6') {
     len = 16;
   }
-  var buff = new Buffer(len);
+  var buff = Buffer.alloc(len);
 
   for (var i = 0, n = buff.length; i < n; ++i) {
     var bits = 8;
     if (prefixlen < 8) {
       bits = prefixlen;
     }
     prefixlen -= bits;
 
@@ -124,17 +124,17 @@ ip.fromPrefixLen = function(prefixlen, f
 
   return ip.toString(buff);
 };
 
 ip.mask = function(addr, mask) {
   addr = ip.toBuffer(addr);
   mask = ip.toBuffer(mask);
 
-  var result = new Buffer(Math.max(addr.length, mask.length));
+  var result = Buffer.alloc(Math.max(addr.length, mask.length));
 
   var i = 0;
   // Same protocol - do bitwise and
   if (addr.length === mask.length) {
     for (i = 0; i < addr.length; i++) {
       result[i] = addr[i] & mask[i];
     }
   } else if (mask.length === 4) {
--- a/testing/xpcshell/node-ip/test/api-test.js
+++ b/testing/xpcshell/node-ip/test/api-test.js
@@ -9,33 +9,33 @@ describe('IP library for node.js', funct
   describe('toBuffer()/toString() methods', function() {
     it('should convert to buffer IPv4 address', function() {
       var buf = ip.toBuffer('127.0.0.1');
       assert.equal(buf.toString('hex'), '7f000001');
       assert.equal(ip.toString(buf), '127.0.0.1');
     });
 
     it('should convert to buffer IPv4 address in-place', function() {
-      var buf = new Buffer(128);
+      var buf = Buffer.alloc(128);
       var offset = 64;
       ip.toBuffer('127.0.0.1', buf, offset);
       assert.equal(buf.toString('hex', offset, offset + 4), '7f000001');
       assert.equal(ip.toString(buf, offset, 4), '127.0.0.1');
     });
 
     it('should convert to buffer IPv6 address', function() {
       var buf = ip.toBuffer('::1');
       assert(/(00){15,15}01/.test(buf.toString('hex')));
       assert.equal(ip.toString(buf), '::1');
       assert.equal(ip.toString(ip.toBuffer('1::')), '1::');
       assert.equal(ip.toString(ip.toBuffer('abcd::dcba')), 'abcd::dcba');
     });
 
     it('should convert to buffer IPv6 address in-place', function() {
-      var buf = new Buffer(128);
+      var buf = Buffer.alloc(128);
       var offset = 64;
       ip.toBuffer('::1', buf, offset);
       assert(/(00){15,15}01/.test(buf.toString('hex', offset, offset + 16)));
       assert.equal(ip.toString(buf, offset, 16), '::1');
       assert.equal(ip.toString(ip.toBuffer('1::', buf, offset),
                                offset, 16), '1::');
       assert.equal(ip.toString(ip.toBuffer('abcd::dcba', buf, offset),
                                offset, 16), 'abcd::dcba');
--- a/toolkit/components/search/SearchService.jsm
+++ b/toolkit/components/search/SearchService.jsm
@@ -1132,17 +1132,18 @@ SearchService.prototype = {
       let enginesFromDir = await this._loadEnginesFromDir(loadDir);
       enginesFromDir.forEach(this._addEngineToStore, this);
     }
     if (AppConstants.platform == "android") {
       let enginesFromURLs = await this._loadFromChromeURLs(engines, isReload);
       enginesFromURLs.forEach(this._addEngineToStore, this);
     } else {
       if (gModernConfig) {
-        await this._loadEnginesFromConfig(engines);
+        let newEngines = await this._loadEnginesFromConfig(engines, isReload);
+        newEngines.forEach(this._addEngineToStore, this);
       } else {
         let engineList = this._enginesToLocales(engines);
         for (let [id, locales] of engineList) {
           await this.ensureBuiltinExtension(id, locales, isReload);
         }
       }
       SearchUtils.log(
         "_loadEngines: loading " +
@@ -1159,25 +1160,24 @@ SearchService.prototype = {
     );
     this._loadEnginesFromCache(cache, true);
 
     this._loadEnginesMetadataFromCache(cache);
 
     SearchUtils.log("_loadEngines: done using rebuilt cache");
   },
 
-  async _loadEnginesFromConfig(engineConfigs) {
+  async _loadEnginesFromConfig(engineConfigs, isReload = false) {
+    SearchUtils.log("_loadEnginesFromConfig");
+    let engines = [];
     for (let config of engineConfigs) {
-      SearchUtils.log("_loadEnginesFromConfig: " + JSON.stringify(config));
-      let locales =
-        "webExtensionLocales" in config
-          ? config.webExtensionLocales
-          : [DEFAULT_TAG];
-      await this.ensureBuiltinExtension(config.webExtensionId, locales);
+      let newEngines = await this.makeEnginesFromConfig(config, isReload);
+      engines = engines.concat(newEngines);
     }
+    return engines;
   },
 
   /**
    * Ensures a built in search WebExtension is installed, installing
    * it if necessary.
    *
    * @param {string} id
    *   The WebExtension ID.
@@ -2333,16 +2333,89 @@ SearchService.prototype = {
     // to install in SearchService.init().
     if (!gInitialized) {
       this._startupExtensions.add(extension);
       return [];
     }
     return this._installExtensionEngine(extension, [DEFAULT_TAG]);
   },
 
+  /**
+   * Create an engine object from the search configuration details.
+   *
+   * @param {object} config
+   *   The configuration object that defines the details of the engine
+   *   webExtensionId etc.
+   * @param {boolean} isReload (optional)
+   *   Is being called as part of maybeReloadEngines.
+   * @returns {Array}
+   *   Returns an array of nsISearchEngine objects.
+   */
+  async makeEnginesFromConfig(config, isReload = false) {
+    if (SearchUtils.loggingEnabled) {
+      SearchUtils.log("makeEnginesFromConfig: " + JSON.stringify(config));
+    }
+    let id = config.webExtensionId;
+    let policy = WebExtensionPolicy.getByID(id);
+    if (!policy) {
+      let idPrefix = id.split("@")[0];
+      let path = `resource://search-extensions/${idPrefix}/`;
+      await AddonManager.installBuiltinAddon(path);
+      policy = WebExtensionPolicy.getByID(id);
+    }
+    // On startup the extension may have not finished parsing the
+    // manifest, wait for that here.
+    await policy.readyPromise;
+
+    let params = {
+      code: config.searchUrlGetExtraCodes,
+      telemetryId: config.telemetryId,
+    };
+
+    if ("telemetryId" in config) {
+      params.telemetryId = config.telemetryId;
+    }
+
+    let locales =
+      "webExtensionLocales" in config
+        ? config.webExtensionLocales
+        : [DEFAULT_TAG];
+
+    let engines = [];
+    for (let locale of locales) {
+      let manifest = policy.extension.manifest;
+      if (locale != "default") {
+        manifest = await policy.extension.getLocalizedManifest(locale);
+      }
+
+      let engineParams = await Services.search.getEngineParams(
+        policy.extension,
+        manifest,
+        locale,
+        params
+      );
+
+      let engine = new SearchEngine({
+        name: engineParams.name,
+        readOnly: engineParams.isBuiltin,
+        sanitizeName: true,
+      });
+      engine._initFromMetadata(engineParams.name, engineParams);
+      engine._loadPath = "[other]addEngineWithDetails";
+      if (engineParams.extensionID) {
+        engine._loadPath += ":" + engineParams.extensionID;
+      }
+      if (isReload && this._engines.has(engine.name)) {
+        engine._engineToUpdate = this._engines.get(engine.name);
+      }
+      engines.push(engine);
+    }
+    return engines;
+  },
+
   async _installExtensionEngine(extension, locales, initEngine, isReload) {
     SearchUtils.log("installExtensionEngine: " + extension.id);
 
     let installLocale = async locale => {
       let manifest =
         locale === DEFAULT_TAG
           ? extension.manifest
           : await extension.getLocalizedManifest(locale);
@@ -2409,16 +2482,19 @@ SearchService.prototype = {
         return !(param.value && param.value.startsWith("__MSG_"));
       });
     }
 
     let shortName = extension.id.split("@")[0];
     if (locale != DEFAULT_TAG) {
       shortName += "-" + locale;
     }
+    if ("telemetryId" in extraParams) {
+      shortName = extraParams.telemetryId;
+    }
 
     let searchUrlGetParams = searchProvider.search_url_get_params;
     if (extraParams.code) {
       searchUrlGetParams = "?" + extraParams.code;
     }
 
     let params = {
       name: searchProvider.name.trim(),
--- a/toolkit/components/search/nsISearchService.idl
+++ b/toolkit/components/search/nsISearchService.idl
@@ -221,16 +221,17 @@ interface nsISearchService : nsISupports
    */
   Promise init([optional] in boolean skipRegionCheck);
 
   /**
    * Exposed for testing.
    */
   void reInit([optional] in boolean skipRegionCheck);
   void reset();
+  Promise makeEnginesFromConfig(in jsval config);
   Promise ensureBuiltinExtension(in AString id,
                                 [optional] in jsval locales);
 
   /**
    * Determine whether initialization has been completed.
    *
    * Clients of the service can use this attribute to quickly determine whether
    * initialization is complete, and decide to trigger some immediate treatment,
--- a/toolkit/components/search/tests/xpcshell/searchconfigs/head_searchconfig.js
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/head_searchconfig.js
@@ -162,73 +162,27 @@ class SearchConfigTest {
   async _getEngines(useEngineSelector, region, locale) {
     if (useEngineSelector) {
       let engines = [];
       let configs = await engineSelector.fetchEngineConfiguration(
         locale,
         region
       );
       for (let config of configs.engines) {
-        let engine = await this._getExtensionEngine(config);
-        engines.push(engine);
+        let engine = await Services.search.makeEnginesFromConfig(config);
+        // Currently wikipedia is the only engine that uses multiple
+        // locales and that isn't a tested engine so for now pick
+        // the first (only) locale.
+        engines.push(engine[0]);
       }
       return engines;
     }
     return Services.search.getVisibleEngines();
   }
 
-  async _getExtensionEngine(config) {
-    let id = config.webExtensionId;
-    let policy = WebExtensionPolicy.getByID(id);
-    if (!policy) {
-      let idPrefix = id.split("@")[0];
-      let path = `resource://search-extensions/${idPrefix}/`;
-      await AddonManager.installBuiltinAddon(path);
-      policy = WebExtensionPolicy.getByID(id);
-    }
-    let params = {
-      code: config.searchUrlGetExtraCodes,
-    };
-
-    // Currently wikipedia is the only engine that uses multiple
-    // locales and that isn't a tested engine so for now pick
-    // the first (only) locale.
-    let locale =
-      "webExtensionLocales" in config
-        ? config.webExtensionLocales[0]
-        : "default";
-    // On startup the extension may have not finished parsing the
-    // manifest, wait for that here.
-    await policy.readyPromise;
-
-    let manifest = policy.extension.manifest;
-    if (locale != "default") {
-      manifest = await policy.extension.getLocalizedManifest(locale);
-    }
-
-    let engineParams = await Services.search.getEngineParams(
-      policy.extension,
-      manifest,
-      locale,
-      params
-    );
-
-    let engine = new SearchEngine({
-      name: engineParams.name,
-      readOnly: engineParams.isBuiltin,
-      sanitizeName: true,
-    });
-    engine._initFromMetadata(engineParams.name, engineParams);
-    engine._loadPath = "[other]addEngineWithDetails";
-    if (engineParams.extensionID) {
-      engine._loadPath += ":" + engineParams.extensionID;
-    }
-    return engine;
-  }
-
   /**
    * Causes re-initialization of the SearchService with the new region and locale.
    *
    * @param {string} region
    *   The two-letter region code.
    * @param {string} locale
    *   The two-letter locale code.
    */
@@ -486,16 +440,23 @@ class SearchConfigTest {
       }
       if (rule.aliases) {
         this.assertDeepEqual(
           engine._internalAliases,
           rule.aliases,
           "Should have the correct aliases for the engine"
         );
       }
+      if (rule.telemetryId) {
+        this.assertEqual(
+          engine._shortName,
+          rule.telemetryId,
+          `Should have the correct shortName ${location}.`
+        );
+      }
     }
   }
 
   /**
    * Asserts whether the engine is using the correct domains or not.
    *
    * @param {string} location
    *   Debug string with locale + region information.
--- a/toolkit/components/search/tests/xpcshell/searchconfigs/test_amazon.js
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_amazon.js
@@ -89,16 +89,17 @@ const test = new SearchConfigTest({
     ],
   },
   details: [
     {
       // Note: These should be based on region, but we don't currently enforce that.
       // Note: the order here is important. A region/locale match higher up in the
       // list will override a region/locale match lower down.
       domain: "amazon.com.au",
+      telemetryId: "amazon-au",
       aliases: ["@amazon"],
       included: [
         {
           regions: ["au"],
           locales: {
             matches: [
               "ach",
               "af",
@@ -153,16 +154,17 @@ const test = new SearchConfigTest({
             ],
           },
         },
       ],
       noSuggestionsURL: true,
     },
     {
       domain: "amazon.ca",
+      telemetryId: "amazon-ca",
       aliases: ["@amazon"],
       included: [
         {
           locales: {
             matches: ["en-CA"],
           },
         },
         {
@@ -207,16 +209,17 @@ const test = new SearchConfigTest({
             matches: ["br", "fr", "ff", "son", "wo"],
           },
         },
       ],
       noSuggestionsURL: true,
     },
     {
       domain: "amazon.fr",
+      telemetryId: "amazon-france",
       aliases: ["@amazon"],
       included: [
         {
           locales: {
             matches: ["br", "fr", "ff", "son", "wo"],
           },
         },
         {
@@ -257,16 +260,17 @@ const test = new SearchConfigTest({
         },
       ],
       excluded: [{ regions: ["ca"] }],
       searchUrlCode: "tag=firefox-fr-21",
       noSuggestionsURL: true,
     },
     {
       domain: "amazon.co.uk",
+      telemetryId: "amazon-en-GB",
       aliases: ["@amazon"],
       included: [
         {
           locales: {
             matches: [
               "cy",
               "da",
               "el",
@@ -322,16 +326,17 @@ const test = new SearchConfigTest({
         },
       ],
       excluded: [{ regions: ["au"] }],
       searchUrlCode: "tag=firefox-uk-21",
       noSuggestionsURL: true,
     },
     {
       domain: "amazon.com",
+      telemetryId: "amazondotcom",
       aliases: ["@amazon"],
       included: [
         {
           locales: {
             matches: [
               "ach",
               "af",
               "ar",
@@ -365,54 +370,58 @@ const test = new SearchConfigTest({
           },
         },
       ],
       excluded: [{ regions: ["au", "ca", "fr", "gb"] }],
       searchUrlCode: "tag=mozilla-20",
     },
     {
       domain: "amazon.cn",
+      telemetryId: "amazondotcn",
       included: [
         {
           locales: {
             matches: ["zh-CN"],
           },
         },
       ],
       searchUrlCode: "ix=sunray",
       noSuggestionsURL: true,
     },
     {
       domain: "amazon.co.jp",
+      telemetryId: "amazon-jp",
       aliases: ["@amazon"],
       included: [
         {
           locales: {
             startsWith: ["ja"],
           },
         },
       ],
       searchUrlCode: "tag=mozillajapan-fx-22",
       noSuggestionsURL: true,
     },
     {
       domain: "amazon.de",
+      telemetryId: "amazon-de",
       aliases: ["@amazon"],
       included: [
         {
           locales: {
             matches: ["de", "dsb", "hsb"],
           },
         },
       ],
       searchUrlCode: "tag=firefox-de-21",
       noSuggestionsURL: true,
     },
     {
       domain: "amazon.in",
+      telemetryId: "amazon-in",
       aliases: ["@amazon"],
       included: [
         {
           locales: {
             matches: [
               "bn",
               "gu-IN",
               "kn",
@@ -427,16 +436,17 @@ const test = new SearchConfigTest({
             ],
           },
         },
       ],
       noSuggestionsURL: true,
     },
     {
       domain: "amazon.it",
+      telemetryId: "amazon-it",
       aliases: ["@amazon"],
       included: [
         {
           locales: {
             matches: ["it", "lij"],
           },
         },
       ],
--- a/toolkit/components/search/tests/xpcshell/searchconfigs/test_baidu.js
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_baidu.js
@@ -24,16 +24,17 @@ const test = new SearchConfigTest({
         },
       },
     ],
   },
   details: [
     {
       included: [{}],
       domain: "baidu.com",
+      telemetryId: "baidu",
       searchUrlCode: "tn=monline_7_dg",
       suggestUrlCode: "tn=monline_7_dg",
     },
   ],
 });
 
 add_task(async function setup() {
   await test.setup();
--- a/toolkit/components/search/tests/xpcshell/searchconfigs/test_bing.js
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_bing.js
@@ -93,16 +93,17 @@ const test = new SearchConfigTest({
         },
       },
     ],
   },
   details: [
     {
       included: [{}],
       domain: "bing.com",
+      telemetryId: "bing",
       codes: {
         searchbar: "form=MOZSBR",
         keyword: "form=MOZLBR",
         contextmenu: "form=MOZCON",
         homepage: "form=MOZSPG",
         newtab: "form=MOZTSB",
       },
       searchUrlCode: "pc=MOZI",
--- a/toolkit/components/search/tests/xpcshell/searchconfigs/test_duckduckgo.js
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_duckduckgo.js
@@ -13,16 +13,17 @@ const test = new SearchConfigTest({
     excluded: [
       // Should be available everywhere.
     ],
   },
   details: [
     {
       included: [{}],
       domain: "duckduckgo.com",
+      telemetryId: "ddg",
       codes: {
         searchbar: "t=ffsb",
         keyword: "t=ffab",
         contextmenu: "t=ffcm",
         homepage: "t=ffhp",
         newtab: "t=ffnt",
       },
     },
--- a/toolkit/components/search/tests/xpcshell/searchconfigs/test_ebay.js
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_ebay.js
@@ -59,145 +59,158 @@ const test = new SearchConfigTest({
   },
   searchUrlBase: "https://rover.ebay.com/rover/1/",
   details: [
     {
       // Note: These should be based on region, but we don't currently enforce that.
       // Note: the order here is important. A region/locale match higher up in the
       // list will override a region/locale match lower down.
       domain: "befr.ebay.be",
+      telemetryId: "ebay-be",