Merge inbound to mozilla-central. a=merge
authorshindli <shindli@mozilla.com>
Fri, 23 Feb 2018 11:40:37 +0200
changeset 457445 6661c077325c35af028f1cdaa660f673cbea39be
parent 457415 9fa8ab27780461ce89f6c67d3ab63cbe4097dbe8 (current diff)
parent 457444 a6801ade61b29eb501ab87d470753928d307a03a (diff)
child 457459 c7ffbaaf8c2e3ac13577da6413aa14fcf73ee151
child 457472 7fa45832949d8b3e2b7fefa075f06ba550e8eb7a
push id8799
push usermtabara@mozilla.com
push dateThu, 01 Mar 2018 16:46:23 +0000
treeherdermozilla-beta@15334014dc67 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone60.0a1
first release with
nightly linux32
6661c077325c / 60.0a1 / 20180223100113 / files
nightly linux64
6661c077325c / 60.0a1 / 20180223100113 / files
nightly mac
6661c077325c / 60.0a1 / 20180223100113 / files
nightly win32
6661c077325c / 60.0a1 / 20180223100113 / files
nightly win64
6661c077325c / 60.0a1 / 20180223100113 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
--- a/browser/base/content/test/performance/browser_appmenu_reflows.js
+++ b/browser/base/content/test/performance/browser_appmenu_reflows.js
@@ -35,17 +35,17 @@ const EXPECTED_APPMENU_OPEN_REFLOWS = [
   },
 
   {
     stack: [
       "_calculateMaxHeight@resource:///modules/PanelMultiView.jsm",
       "handleEvent@resource:///modules/PanelMultiView.jsm",
     ],
 
-    times: 6, // This number should only ever go down - never up.
+    maxCount: 6, // This number should only ever go down - never up.
   },
 ];
 
 const EXPECTED_APPMENU_SUBVIEW_REFLOWS = [
   /**
    * The synced tabs view has labels that are multiline. Because of bugs in
    * XUL layout relating to multiline text in scrollable containers, we need
    * to manually read their height in order to ensure container heights are
@@ -53,30 +53,20 @@ const EXPECTED_APPMENU_SUBVIEW_REFLOWS =
    *
    * If we add more views where this is necessary, we may need to duplicate
    * these expected reflows further. Bug 1392340 is on file to remove the
    * reflows completely when opening subviews.
    */
   {
     stack: [
       "descriptionHeightWorkaround@resource:///modules/PanelMultiView.jsm",
-      "set current@resource:///modules/PanelMultiView.jsm",
-      "hideAllViewsExcept@resource:///modules/PanelMultiView.jsm",
-    ],
-
-    times: 1, // This number should only ever go down - never up.
-  },
-
-  {
-    stack: [
-      "descriptionHeightWorkaround@resource:///modules/PanelMultiView.jsm",
       "_transitionViews@resource:///modules/PanelMultiView.jsm",
     ],
 
-    times: 3, // This number should only ever go down - never up.
+    maxCount: 4, // This number should only ever go down - never up.
   },
 
   /**
    * Please don't add anything new!
    */
 ];
 
 add_task(async function() {
--- a/browser/base/content/test/performance/browser_urlbar_keyed_search_reflows.js
+++ b/browser/base/content/test/performance/browser_urlbar_keyed_search_reflows.js
@@ -24,58 +24,56 @@ const EXPECTED_REFLOWS_FIRST_OPEN = [
       "_enableOrDisableOneOffSearches@chrome://browser/content/urlbarBindings.xml",
       "urlbar_XBL_Constructor/<@chrome://browser/content/urlbarBindings.xml",
       "openPopup@chrome://global/content/bindings/popup.xml",
       "_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
       "openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
       "openPopup@chrome://global/content/bindings/autocomplete.xml",
       "set_popupOpen@chrome://global/content/bindings/autocomplete.xml"
     ],
-    times: 1, // This number should only ever go down - never up.
   },
 
   {
     stack: [
       "adjustHeight@chrome://global/content/bindings/autocomplete.xml",
       "onxblpopupshown@chrome://global/content/bindings/autocomplete.xml"
     ],
-    times: 5, // This number should only ever go down - never up.
+    maxCount: 5, // This number should only ever go down - never up.
   },
 
   {
     stack: [
       "adjustHeight@chrome://global/content/bindings/autocomplete.xml",
       "_invalidate/this._adjustHeightTimeout<@chrome://global/content/bindings/autocomplete.xml",
     ],
-    minTimes: 39, // This number should only ever go down - never up.
-    times: 51, // This number should only ever go down - never up.
+    maxCount: 51, // This number should only ever go down - never up.
   },
 
   {
     stack: [
       "_handleOverflow@chrome://global/content/bindings/autocomplete.xml",
       "handleOverUnderflow@chrome://global/content/bindings/autocomplete.xml",
       "_reuseAcItem@chrome://global/content/bindings/autocomplete.xml",
       "_appendCurrentResult@chrome://global/content/bindings/autocomplete.xml",
       "_invalidate@chrome://global/content/bindings/autocomplete.xml",
       "invalidate@chrome://global/content/bindings/autocomplete.xml"
     ],
-    times: 60, // This number should only ever go down - never up.
+    maxCount: 60, // This number should only ever go down - never up.
   },
 
   {
     stack: [
       "_handleOverflow@chrome://global/content/bindings/autocomplete.xml",
       "handleOverUnderflow@chrome://global/content/bindings/autocomplete.xml",
       "_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
       "openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
       "openPopup@chrome://global/content/bindings/autocomplete.xml",
       "set_popupOpen@chrome://global/content/bindings/autocomplete.xml",
     ],
-    times: 6, // This number should only ever go down - never up.
+    maxCount: 6, // This number should only ever go down - never up.
   },
 
   // Bug 1359989
   {
     stack: [
       "openPopup@chrome://global/content/bindings/popup.xml",
       "_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
       "openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
--- a/browser/base/content/test/performance/browser_urlbar_search_reflows.js
+++ b/browser/base/content/test/performance/browser_urlbar_search_reflows.js
@@ -24,57 +24,56 @@ const EXPECTED_REFLOWS_FIRST_OPEN = [
       "_enableOrDisableOneOffSearches@chrome://browser/content/urlbarBindings.xml",
       "urlbar_XBL_Constructor/<@chrome://browser/content/urlbarBindings.xml",
       "openPopup@chrome://global/content/bindings/popup.xml",
       "_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
       "openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
       "openPopup@chrome://global/content/bindings/autocomplete.xml",
       "set_popupOpen@chrome://global/content/bindings/autocomplete.xml"
     ],
-    times: 1, // This number should only ever go down - never up.
   },
 
   {
     stack: [
       "adjustHeight@chrome://global/content/bindings/autocomplete.xml",
       "onxblpopupshown@chrome://global/content/bindings/autocomplete.xml"
     ],
-    times: 5, // This number should only ever go down - never up.
+    maxCount: 5, // This number should only ever go down - never up.
   },
 
   {
     stack: [
       "adjustHeight@chrome://global/content/bindings/autocomplete.xml",
       "_invalidate/this._adjustHeightTimeout<@chrome://global/content/bindings/autocomplete.xml",
     ],
-    times: 3, // This number should only ever go down - never up.
+    maxCount: 3, // This number should only ever go down - never up.
   },
 
   {
     stack: [
       "_handleOverflow@chrome://global/content/bindings/autocomplete.xml",
       "handleOverUnderflow@chrome://global/content/bindings/autocomplete.xml",
       "_reuseAcItem@chrome://global/content/bindings/autocomplete.xml",
       "_appendCurrentResult@chrome://global/content/bindings/autocomplete.xml",
       "_invalidate@chrome://global/content/bindings/autocomplete.xml",
       "invalidate@chrome://global/content/bindings/autocomplete.xml"
     ],
-    times: 36, // This number should only ever go down - never up.
+    maxCount: 36, // This number should only ever go down - never up.
   },
 
   {
     stack: [
       "_handleOverflow@chrome://global/content/bindings/autocomplete.xml",
       "handleOverUnderflow@chrome://global/content/bindings/autocomplete.xml",
       "_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
       "openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
       "openPopup@chrome://global/content/bindings/autocomplete.xml",
       "set_popupOpen@chrome://global/content/bindings/autocomplete.xml",
     ],
-    times: 6, // This number should only ever go down - never up.
+    maxCount: 6, // This number should only ever go down - never up.
   },
 
   // Bug 1359989
   {
     stack: [
       "openPopup@chrome://global/content/bindings/popup.xml",
       "_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
       "openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
@@ -86,37 +85,37 @@ const EXPECTED_REFLOWS_FIRST_OPEN = [
 
 /* These reflows happen everytime the awesomebar panel opens. */
 const EXPECTED_REFLOWS_SECOND_OPEN = [
   {
     stack: [
       "adjustHeight@chrome://global/content/bindings/autocomplete.xml",
       "onxblpopupshown@chrome://global/content/bindings/autocomplete.xml"
     ],
-    times: 3, // This number should only ever go down - never up.
+    maxCount: 3, // This number should only ever go down - never up.
   },
 
   {
     stack: [
       "adjustHeight@chrome://global/content/bindings/autocomplete.xml",
       "_invalidate/this._adjustHeightTimeout<@chrome://global/content/bindings/autocomplete.xml",
     ],
-    times: 3, // This number should only ever go down - never up.
+    maxCount: 3, // This number should only ever go down - never up.
   },
 
   {
     stack: [
       "_handleOverflow@chrome://global/content/bindings/autocomplete.xml",
       "handleOverUnderflow@chrome://global/content/bindings/autocomplete.xml",
       "_reuseAcItem@chrome://global/content/bindings/autocomplete.xml",
       "_appendCurrentResult@chrome://global/content/bindings/autocomplete.xml",
       "_invalidate@chrome://global/content/bindings/autocomplete.xml",
       "invalidate@chrome://global/content/bindings/autocomplete.xml"
     ],
-    times: 24, // This number should only ever go down - never up.
+    maxCount: 24, // This number should only ever go down - never up.
   },
 
   // Bug 1359989
   {
     stack: [
       "openPopup@chrome://global/content/bindings/popup.xml",
       "_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
       "openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
--- a/browser/base/content/test/performance/browser_windowopen_reflows.js
+++ b/browser/base/content/test/performance/browser_windowopen_reflows.js
@@ -22,32 +22,32 @@ if (Services.appinfo.OS == "WINNT") {
   EXPECTED_REFLOWS.push(
     {
       stack: [
         "verticalMargins@chrome://browser/content/browser-tabsintitlebar.js",
         "_update@chrome://browser/content/browser-tabsintitlebar.js",
         "init@chrome://browser/content/browser-tabsintitlebar.js",
         "handleEvent@chrome://browser/content/tabbrowser.xml",
       ],
-      times: 2, // This number should only ever go down - never up.
+      maxCount: 2, // This number should only ever go down - never up.
     },
   );
 }
 
 if (Services.appinfo.OS == "WINNT" || Services.appinfo.OS == "Darwin") {
   EXPECTED_REFLOWS.push(
     {
       stack: [
         "rect@chrome://browser/content/browser-tabsintitlebar.js",
         "_update@chrome://browser/content/browser-tabsintitlebar.js",
         "init@chrome://browser/content/browser-tabsintitlebar.js",
         "handleEvent@chrome://browser/content/tabbrowser.xml",
       ],
       // These numbers should only ever go down - never up.
-      times: Services.appinfo.OS == "WINNT" ? 5 : 4,
+      maxCount: Services.appinfo.OS == "WINNT" ? 5 : 4,
     },
   );
 }
 
 /*
  * This test ensures that there are no unexpected
  * uninterruptible reflows when opening new windows.
  */
--- a/browser/base/content/test/performance/head.js
+++ b/browser/base/content/test/performance/head.js
@@ -20,34 +20,31 @@ ChromeUtils.defineModuleGetter(this, "Pl
  *        An Array of Objects representing reflows.
  *
  *        Example:
  *
  *        [
  *          {
  *            // This reflow is caused by lorem ipsum.
  *            // Sometimes, due to unpredictable timings, the reflow may be hit
- *            // less times, or not hit at all; in such a case a minTimes
- *            // property can be provided to avoid intermittent failures.
+ *            // less times.
  *            stack: [
  *              "select@chrome://global/content/bindings/textbox.xml",
  *              "focusAndSelectUrlBar@chrome://browser/content/browser.js",
  *              "openLinkIn@chrome://browser/content/utilityOverlay.js",
  *              "openUILinkIn@chrome://browser/content/utilityOverlay.js",
  *              "BrowserOpenTab@chrome://browser/content/browser.js",
  *            ],
- *            // We expect this particular reflow to happen 2 times
- *            times: 2,
- *            // Sometimes this is not hit.
- *            minTimes: 0
+ *            // We expect this particular reflow to happen up to 2 times.
+ *            maxCount: 2,
  *          },
  *
  *          {
  *            // This reflow is caused by lorem ipsum. We expect this reflow
- *            // to only happen once, so we can omit the "times" property.
+ *            // to only happen once, so we can omit the "maxCount" property.
  *            stack: [
  *              "get_scrollPosition@chrome://global/content/bindings/scrollbox.xml",
  *              "_fillTrailingGap@chrome://browser/content/tabbrowser.xml",
  *              "_handleNewTab@chrome://browser/content/tabbrowser.xml",
  *              "onxbltransitionend@chrome://browser/content/tabbrowser.xml",
  *            ],
  *          }
  *        ]
@@ -67,65 +64,26 @@ async function withReflowObserver(testFn
     try {
       dwu.ensureDirtyRootFrame();
     } catch (e) {
       // If this fails, we should probably make note of it, but it's not fatal.
       info("Note: ensureDirtyRootFrame threw an exception.");
     }
   };
 
-  // We're going to remove the reflows one by one as we see them so that
-  // we can check for expected, unseen reflows, so let's clone the array.
-  // While we're at it, for reflows that omit the "times" property, default
-  // it to 1.
-  expectedReflows = expectedReflows.slice(0);
-  expectedReflows.forEach(r => {
-    r.times = r.times || 1;
-  });
+  // Collect all reflow stacks, we'll process them later.
+  let reflows = [];
 
   let observer = {
     reflow(start, end) {
-      // Gather information about the current code path, slicing out the current
-      // frame.
-      let path = (new Error().stack).split("\n").slice(1).map(line => {
-        return line.replace(/:\d+:\d+$/, "");
-      }).join("|");
-
-      let pathWithLineNumbers = (new Error().stack).split("\n").slice(1);
+      // Gather information about the current code path.
+      reflows.push(new Error().stack);
 
       // Just in case, dirty the frame now that we've reflowed.
       dirtyFrameFn();
-
-      // Stack trace is empty. Reflow was triggered by native code, which
-      // we ignore.
-      if (path === "") {
-        return;
-      }
-
-      // synthesizeKey from EventUtils.js causes us to reflow. That's the test
-      // harness and we don't care about that, so we'll filter that out.
-      if (path.startsWith("synthesizeKey@chrome://mochikit/content/tests/SimpleTest/EventUtils.js")) {
-        return;
-      }
-
-      let index = expectedReflows.findIndex(reflow => path.startsWith(reflow.stack.join("|")));
-
-      if (index != -1) {
-        Assert.ok(true, "expected uninterruptible reflow: '" +
-                  JSON.stringify(pathWithLineNumbers, null, "\t") + "'");
-        if (expectedReflows[index].minTimes) {
-          expectedReflows[index].minTimes--;
-        }
-        if (--expectedReflows[index].times == 0) {
-          expectedReflows.splice(index, 1);
-        }
-      } else {
-        Assert.ok(false, "unexpected uninterruptible reflow \n" +
-                         JSON.stringify(pathWithLineNumbers, null, "\t") + "\n");
-      }
     },
 
     reflowInterruptible(start, end) {
       // We're not interested in interruptible reflows, but might as well take the
       // opportuntiy to dirty the root frame.
       dirtyFrameFn();
     },
 
@@ -139,30 +97,85 @@ async function withReflowObserver(testFn
   docShell.addWeakReflowObserver(observer);
 
   Services.els.addListenerForAllEvents(win, dirtyFrameFn, true);
 
   try {
     dirtyFrameFn();
     await testFn(dirtyFrameFn);
   } finally {
-    if (expectedReflows.length != 0) {
-      for (let remainder of expectedReflows) {
-        if (!Number.isInteger(remainder.minTimes) || remainder.minTimes > 0) {
+    let knownReflows = expectedReflows.map(r => {
+      return {stack: r.stack, path: r.stack.join("|"),
+              count: 0, maxCount: r.maxCount || 1,
+              actualStacks: new Map()};
+    });
+    let unexpectedReflows = new Map();
+    for (let stack of reflows) {
+      let path =
+        stack.split("\n").slice(1) // the first frame which is our test code.
+             .map(line => line.replace(/:\d+:\d+$/, "")) // strip line numbers.
+             .join("|");
+
+      // Stack trace is empty. Reflow was triggered by native code, which
+      // we ignore.
+      if (path === "") {
+        continue;
+      }
+
+      // synthesizeKey from EventUtils.js causes us to reflow. That's the test
+      // harness and we don't care about that, so we'll filter that out.
+      if (path.startsWith("synthesizeKey@chrome://mochikit/content/tests/SimpleTest/EventUtils.js")) {
+        continue;
+      }
+
+      let index = knownReflows.findIndex(reflow => path.startsWith(reflow.path));
+      if (index != -1) {
+        let reflow = knownReflows[index];
+        ++reflow.count;
+        reflow.actualStacks.set(stack, (reflow.actualStacks.get(stack) || 0) + 1);
+      } else {
+        unexpectedReflows.set(stack, (unexpectedReflows.get(stack) || 0) + 1);
+      }
+    }
+
+    let formatStack = stack =>
+      stack.split("\n").slice(1).map(frame => "  " + frame).join("\n");
+    for (let reflow of knownReflows) {
+      let firstFrame = reflow.stack[0];
+      if (!reflow.count) {
+        Assert.ok(false,
+                  `Unused expected reflow at ${firstFrame}:\nStack:\n` +
+                  reflow.stack.map(frame => "  " + frame).join("\n") + "\n" +
+                  "This is probably a good thing - just remove it from the whitelist.");
+      } else {
+        if (reflow.count > reflow.maxCount) {
           Assert.ok(false,
-                    `Unused expected reflow: ${JSON.stringify(remainder.stack, null, "\t")}\n` +
-                    `This reflow was supposed to be hit ${remainder.minTimes || remainder.times} more time(s).\n` +
-                    "This is probably a good thing - just remove it from the " +
-                    "expected list.");
+                    `reflow at ${firstFrame} was encountered ${reflow.count} times,\n` +
+                    `it was expected to happen up to ${reflow.maxCount} times.`);
+
+        } else {
+          todo(false, `known reflow at ${firstFrame} was encountered ${reflow.count} times`);
+        }
+        for (let [stack, count] of reflow.actualStacks) {
+          info("Full stack" + (count > 1 ? ` (hit ${count} times)` : "") + ":\n" +
+               formatStack(stack));
         }
       }
-    } else {
-      Assert.ok(true, "All expected reflows were observed");
     }
 
+    for (let [stack, count] of unexpectedReflows) {
+      let location = stack.split("\n")[1].replace(/:\d+:\d+$/, "");
+      Assert.ok(false,
+                `unexpected reflow at ${location} hit ${count} times\n` +
+                "Stack:\n" +
+                formatStack(stack));
+    }
+    Assert.ok(!unexpectedReflows.size,
+              unexpectedReflows.size + " unexpected reflows");
+
     Services.els.removeListenerForAllEvents(win, dirtyFrameFn, true);
     docShell.removeWeakReflowObserver(observer);
   }
 }
 
 async function ensureNoPreloadedBrowser(win = window) {
   // If we've got a preloaded browser, get rid of it so that it
   // doesn't interfere with the test if it's loading. We have to
--- a/browser/components/customizableui/PanelMultiView.jsm
+++ b/browser/components/customizableui/PanelMultiView.jsm
@@ -15,33 +15,75 @@
  * The panel should be opened asynchronously using the openPopup static method
  * on the PanelMultiView object. This will display the view specified using the
  * mainViewId attribute on the contained <panelmultiview> element.
  *
  * Specific subviews can slide in using the showSubView method, and backwards
  * navigation can be done using the goBack method or through a button in the
  * subview headers.
  *
+ * The process of displaying the main view or a new subview requires multiple
+ * steps to be completed, hence at any given time the <panelview> element may
+ * be in different states:
+ *
+ * -- Open or closed
+ *
+ *    All the <panelview> elements start "closed", meaning that they are not
+ *    associated to a <panelmultiview> element and can be located anywhere in
+ *    the document. When the openPopup or showSubView methods are called, the
+ *    relevant view becomes "open" and the <panelview> element may be moved to
+ *    ensure it is a descendant of the <panelmultiview> element.
+ *
+ *    The "ViewShowing" event is fired at this point, when the view is not
+ *    visible yet. The event is allowed to cancel the operation, in which case
+ *    the view is closed immediately.
+ *
+ *    Closing the view does not move the node back to its original position.
+ *
+ * -- Visible or invisible
+ *
+ *    This indicates whether the view is visible in the document from a layout
+ *    perspective, regardless of whether it is currently scrolled into view. In
+ *    fact, all subviews are already visible before they start sliding in.
+ *
+ *    Before scrolling into view, a view may become visible but be placed in a
+ *    special off-screen area of the document where layout and measurements can
+ *    take place asyncronously.
+ *
+ *    When navigating forward, an open view may become invisible but stay open
+ *    after sliding out of view. The last known size of these views is still
+ *    taken into account for determining the overall panel size.
+ *
+ *    When navigating backwards, an open subview will first become invisible and
+ *    then will be closed.
+ *
+ * -- Navigating with the keyboard
+ *
+ *    An open view may keep state related to keyboard navigation, even if it is
+ *    invisible. When a view is closed, keyboard navigation state is cleared.
+ *
  * This diagram shows how <panelview> nodes move during navigation:
  *
  *   In this <panelmultiview>     In other panels    Action
  *             ┌───┬───┬───┐        ┌───┬───┐
  *             │(A)│ B │ C │        │ D │ E │          Open panel
  *             └───┴───┴───┘        └───┴───┘
  *         ┌───┬───┬───┐            ┌───┬───┐
- *         │ A │(C)│ B │            │ D │ E │          Show subview C
+ *         │{A}│(C)│ B │            │ D │ E │          Show subview C
  *         └───┴───┴───┘            └───┴───┘
  *     ┌───┬───┬───┬───┐            ┌───┐
- *     │ A │ C │(D)│ B │            │ E │              Show subview D
+ *     │{A}│{C}│(D)│ B │            │ E │              Show subview D
  *     └───┴───┴───┴───┘            └───┘
- *         ┌───┬───┬───┬───┐        ┌───┐
- *         │ A │(C)│ D │ B │        │ E │              Go back
- *         └───┴───┴───┴───┘        └───┘
- *               │
- *               └── Currently visible view
+ *       │ ┌───┬───┬───┬───┐        ┌───┐
+ *       │ │{A}│(C)│ D │ B │        │ E │              Go back
+ *       │ └───┴───┴───┴───┘        └───┘
+ *       │   │   │
+ *       │   │   └── Currently visible view
+ *       │   │   │
+ *       └───┴───┴── Open views
  *
  * If the <panelmultiview> element is "ephemeral", imported subviews will be
  * moved out again to the element specified by the viewCacheId attribute, so
  * that the panel element can be removed safely.
  */
 
 "use strict";
 
@@ -282,39 +324,38 @@ this.PanelMultiView = class extends this
                                     .getService(Ci.nsIScreenManager);
   }
   /**
    * @return {panelview} the currently visible subview OR the subview that is
    *                     about to be shown whilst a 'ViewShowing' event is being
    *                     dispatched.
    */
   get current() {
-    return this.node && (this._viewShowing || this._currentSubView);
+    return this.node && this._currentSubView;
   }
   get _currentSubView() {
     // Peek the top of the stack, but fall back to the main view if the list of
     // opened views is currently empty.
     let panelView = this.openViews[this.openViews.length - 1];
     return (panelView && panelView.node) || this._mainView;
   }
+  get showingSubView() {
+    return this.openViews.length > 1;
+  }
 
   constructor(node) {
     super(node);
     this._openPopupPromise = Promise.resolve(false);
     this._openPopupCancelCallback = () => {};
   }
 
   connect() {
     this.connected = true;
-    this.knownViews = new Set(Array.from(
-      this.node.getElementsByTagName("panelview"),
-      node => PanelView.forNode(node)));
     this.openViews = [];
     this.__transitioning = false;
-    this.showingSubView = false;
 
     const {document, window} = this;
 
     this._viewContainer =
       document.getAnonymousElementByAttribute(this.node, "anonid", "viewContainer");
     this._viewStack =
       document.getAnonymousElementByAttribute(this.node, "anonid", "viewStack");
     this._offscreenViewStack =
@@ -331,17 +372,17 @@ this.PanelMultiView = class extends this
     this._panel.addEventListener("popupshown", this);
     let cs = window.getComputedStyle(document.documentElement);
     // Set CSS-determined attributes now to prevent a layout flush when we do
     // it when transitioning between panels.
     this._dir = cs.direction;
 
     // Proxy these public properties and methods, as used elsewhere by various
     // parts of the browser, to this instance.
-    ["goBack", "showMainView", "showSubView"].forEach(method => {
+    ["goBack", "showSubView"].forEach(method => {
       Object.defineProperty(this.node, method, {
         enumerable: true,
         value: (...args) => this[method](...args)
       });
     });
     ["current", "showingSubView"].forEach(property => {
       Object.defineProperty(this.node, property, {
         enumerable: true,
@@ -351,18 +392,16 @@ this.PanelMultiView = class extends this
   }
 
   destructor() {
     // Guard against re-entrancy.
     if (!this.node)
       return;
 
     this._cleanupTransitionPhase();
-    if (this._ephemeral)
-      this.hideAllViewsExcept(null);
     let mainView = this._mainView;
     if (mainView) {
       if (this._panelViewCache)
         this._panelViewCache.appendChild(mainView);
       mainView.removeAttribute("mainview");
     }
 
     this._moveOutKids(this._viewStack);
@@ -462,17 +501,17 @@ this.PanelMultiView = class extends this
           // case, the calling code should be updated to unhide the panel.
           if (!this.connected) {
             throw new Error("The binding for the panelmultiview element isn't" +
                             " connected. The containing panel may still have" +
                             " its display turned off by the hidden attribute.");
           }
         }
         // Allow any of the ViewShowing handlers to prevent showing the main view.
-        if (!(await this.showMainView())) {
+        if (!(await this._showMainView())) {
           cancelCallback();
         }
       } catch (ex) {
         cancelCallback();
         throw ex;
       }
       // If a cancellation request was received there is nothing more to do.
       if (!canCancel || !this.node) {
@@ -500,29 +539,34 @@ this.PanelMultiView = class extends this
    * yet, the operation is canceled and the panel will not be displayed, but the
    * "popuphidden" event is fired synchronously anyways.
    *
    * This means that by the time this method returns all the operations handled
    * by the "popuphidden" event are completed, for example resetting the "open"
    * state of the anchor, and the panel is already invisible.
    */
   hidePopup() {
-    if (!this.node) {
+    if (!this.node || !this.connected) {
       return;
     }
 
     // If we have already reached the _panel.openPopup call in the openPopup
     // method, we can call hidePopup. Otherwise, we have to cancel the latest
     // request to open the panel, which will have no effect if the request has
     // been canceled already.
     if (["open", "showing"].includes(this._panel.state)) {
       this._panel.hidePopup();
     } else {
       this._openPopupCancelCallback();
     }
+
+    // We close all the views synchronously, so that they are ready to be opened
+    // in other PanelMultiView instances. The "popuphidden" handler may also
+    // call this function, but the second time openViews will be empty.
+    this.closeAllViews();
   }
 
   /**
    * Remove any child subviews into the panelViewCache, to ensure
    * they remain usable even if this panelmultiview instance is removed
    * from the DOM.
    * @param viewNodeContainer the container from which to remove subviews
    */
@@ -535,135 +579,191 @@ this.PanelMultiView = class extends this
     let subviews = Array.from(viewNodeContainer.childNodes);
     for (let subview of subviews) {
       // XBL lists the 'children' XBL element explicitly. :-(
       if (subview.nodeName != "children")
         this._panelViewCache.appendChild(subview);
     }
   }
 
+  /**
+   * Slides in the specified view as a subview.
+   *
+   * @param viewIdOrNode
+   *        DOM element or string ID of the <panelview> to display.
+   * @param anchor
+   *        DOM element that triggered the subview, which will be highlighted
+   *        and whose "label" attribute will be used for the title of the
+   *        subview when a "title" attribute is not specified.
+   */
+  showSubView(viewIdOrNode, anchor) {
+    this._showSubView(viewIdOrNode, anchor).catch(Cu.reportError);
+  }
+  async _showSubView(viewIdOrNode, anchor) {
+    let viewNode = typeof viewIdOrNode == "string" ?
+                   this.document.getElementById(viewIdOrNode) : viewIdOrNode;
+    if (!viewNode) {
+      Cu.reportError(new Error(`Subview ${viewIdOrNode} doesn't exist.`));
+      return;
+    }
+
+    let prevPanelView = this.openViews[this.openViews.length - 1];
+    let nextPanelView = PanelView.forNode(viewNode);
+    if (this.openViews.includes(nextPanelView)) {
+      Cu.reportError(new Error(`Subview ${viewNode.id} is already open.`));
+      return;
+    }
+    if (!(await this._openView(nextPanelView))) {
+      return;
+    }
+
+    prevPanelView.captureKnownSize();
+
+    // The main view of a panel can be a subview in another one. Make sure to
+    // reset all the properties that may be set on a subview.
+    nextPanelView.mainview = false;
+    // The header may change based on how the subview was opened.
+    nextPanelView.headerText = viewNode.getAttribute("title") ||
+                               (anchor && anchor.getAttribute("label"));
+    // The constrained width of subviews may also vary between panels.
+    nextPanelView.minMaxWidth = prevPanelView.knownWidth;
+
+    if (anchor) {
+      viewNode.classList.add("PanelUI-subView");
+    }
+
+    await this._transitionViews(prevPanelView.node, viewNode, false, anchor);
+    this._viewShown(nextPanelView);
+  }
+
+  /**
+   * Navigates backwards by sliding out the most recent subview.
+   */
   goBack() {
+    this._goBack().catch(Cu.reportError);
+  }
+  async _goBack() {
     if (this.openViews.length < 2) {
       // This may be called by keyboard navigation or external code when only
       // the main view is open.
       return;
     }
 
-    let previous = this.openViews.pop().node;
-    let current = this._currentSubView;
-    this.showSubView(current, null, previous);
+    let prevPanelView = this.openViews[this.openViews.length - 1];
+    let nextPanelView = this.openViews[this.openViews.length - 2];
+
+    prevPanelView.captureKnownSize();
+    await this._transitionViews(prevPanelView.node, nextPanelView.node, true);
+
+    this._closeLatestView();
+
+    this._viewShown(nextPanelView);
   }
 
-  async showMainView() {
-    if (!this.node || !this._mainViewId)
+  /**
+   * Prepares the main view before showing the panel.
+   */
+  async _showMainView() {
+    if (!this.node || !this._mainViewId) {
       return false;
+    }
 
-    return this.showSubView(this._mainView);
+    let nextPanelView = PanelView.forNode(this._mainView);
+
+    // If the view is already open in another panel, close the panel first.
+    let oldPanelMultiViewNode = nextPanelView.node.panelMultiView;
+    if (oldPanelMultiViewNode) {
+      PanelMultiView.forNode(oldPanelMultiViewNode).hidePopup();
+    }
+
+    if (!(await this._openView(nextPanelView))) {
+      return false;
+    }
+
+    // The main view of a panel can be a subview in another one. Make sure to
+    // reset all the properties that may be set on a subview.
+    nextPanelView.mainview = true;
+    nextPanelView.headerText = "";
+    nextPanelView.minMaxWidth = 0;
+
+    await this._cleanupTransitionPhase();
+    nextPanelView.visible = true;
+    nextPanelView.descriptionHeightWorkaround();
+
+    this._viewShown(nextPanelView);
+    return true;
   }
 
   /**
-   * Ensures that all the panelviews, that are currently part of this instance,
-   * are hidden, except one specifically.
+   * Opens the specified PanelView and dispatches the ViewShowing event, which
+   * can be used to populate the subview or cancel the operation.
    *
-   * @param {panelview} [nextPanelView]
-   *        The PanelView object to ensure is visible. Optional.
+   * @resolves With true if the view was opened, false otherwise.
    */
-  hideAllViewsExcept(nextPanelView = null) {
-    for (let panelView of this.knownViews) {
-      // When the panelview was already reparented, don't interfere any more.
-      if (panelView == nextPanelView || !this.node || panelView.node.panelMultiView != this.node)
-        continue;
-      panelView.current = false;
+  async _openView(panelView) {
+    if (panelView.node.parentNode != this._viewStack) {
+      this._viewStack.appendChild(panelView.node);
     }
 
-    this._viewShowing = null;
+    panelView.node.panelMultiView = this.node;
+    this.openViews.push(panelView);
 
-    if (!this.node || !nextPanelView)
-      return;
+    let canceled = await panelView.dispatchAsyncEvent("ViewShowing");
 
-    if (!this.openViews.includes(nextPanelView))
-      this.openViews.push(nextPanelView);
+    // The panel can be hidden while we are processing the ViewShowing event.
+    // This results in all the views being closed synchronously, and at this
+    // point the ViewHiding event has already been dispatched for all of them.
+    if (!this.openViews.length) {
+      return false;
+    }
 
-    nextPanelView.current = true;
-    this.showingSubView = nextPanelView.node.id != this._mainViewId;
+    // Check if the event requested cancellation but the panel is still open.
+    if (canceled) {
+      // Handlers for ViewShowing can't know if a different handler requested
+      // cancellation, so this will dispatch a ViewHiding event to give a chance
+      // to clean up.
+      this._closeLatestView();
+      return false;
+    }
+
+    return true;
   }
 
-  async showSubView(aViewId, aAnchor, aPreviousView) {
-    try {
-      // Support passing in the node directly.
-      let viewNode = typeof aViewId == "string" ? this.node.querySelector("#" + aViewId) : aViewId;
-      if (!viewNode) {
-        viewNode = this.document.getElementById(aViewId);
-        if (viewNode) {
-          this._viewStack.appendChild(viewNode);
-        } else {
-          throw new Error(`Subview ${aViewId} doesn't exist!`);
-        }
-      } else if (viewNode.parentNode == this._panelViewCache) {
-        this._viewStack.appendChild(viewNode);
-      }
-
-      let nextPanelView = PanelView.forNode(viewNode);
-      this.knownViews.add(nextPanelView);
-
-      viewNode.panelMultiView = this.node;
-
-      let previousViewNode = aPreviousView || this._currentSubView;
-      // If the panelview to show is the same as the previous one, the 'ViewShowing'
-      // event has already been dispatched. Don't do it twice.
-      let showingSameView = viewNode == previousViewNode;
-
-      let prevPanelView = PanelView.forNode(previousViewNode);
-      prevPanelView.captureKnownSize();
-
-      this._viewShowing = viewNode;
+  /**
+   * Raises the ViewShown event if the specified view is still open.
+   */
+  _viewShown(panelView) {
+    if (panelView.node.panelMultiView == this.node) {
+      panelView.dispatchCustomEvent("ViewShown");
+    }
+  }
 
-      let reverse = !!aPreviousView;
-      if (!reverse) {
-        // We are opening a new view, either because we are navigating forward
-        // or because we are showing the main view. Some properties of the view
-        // may vary between panels, so we make sure to update them every time.
-        // Firstly, make sure that the header matches how the view was opened.
-        nextPanelView.headerText = viewNode.getAttribute("title") ||
-                                   (aAnchor && aAnchor.getAttribute("label"));
-        // The main view of a panel can be a subview in another one.
-        let isMainView = viewNode.id == this._mainViewId;
-        nextPanelView.mainview = isMainView;
-        // The constrained width of subviews may also vary between panels.
-        nextPanelView.minMaxWidth = isMainView ? 0 : prevPanelView.knownWidth;
-      }
-
-      if (aAnchor) {
-        viewNode.classList.add("PanelUI-subView");
-      }
+  /**
+   * Closes the most recent PanelView and raises the ViewHiding event.
+   *
+   * @note The ViewHiding event is not cancelable and should probably be renamed
+   *       to ViewHidden or ViewClosed instead, see bug 1438507.
+   */
+  _closeLatestView() {
+    let panelView = this.openViews.pop();
+    panelView.clearNavigation();
+    panelView.dispatchCustomEvent("ViewHiding");
+    panelView.node.panelMultiView = null;
+    // Views become invisible synchronously when they are closed, and they won't
+    // become visible again until they are opened.
+    panelView.visible = false;
+  }
 
-      if (!showingSameView || !viewNode.hasAttribute("current")) {
-        // Emit the ViewShowing event so that the widget definition has a chance
-        // to lazily populate the subview with things or perhaps even cancel this
-        // whole operation.
-        if (await nextPanelView.dispatchAsyncEvent("ViewShowing")) {
-          this._viewShowing = null;
-          return false;
-        }
-      }
-
-      // Now we have to transition the panel. If we've got an older transition
-      // still running, make sure to clean it up.
-      await this._cleanupTransitionPhase();
-      if (!showingSameView && this._panel.state == "open") {
-        await this._transitionViews(previousViewNode, viewNode, reverse, aAnchor);
-        nextPanelView.focusSelectedElement();
-      } else {
-        this.hideAllViewsExcept(nextPanelView);
-      }
-
-      return true;
-    } catch (ex) {
-      Cu.reportError(ex);
-      return false;
+  /**
+   * Closes all the views that are currently open.
+   */
+  closeAllViews() {
+    // Raise ViewHiding events for open views in reverse order.
+    while (this.openViews.length) {
+      this._closeLatestView();
     }
   }
 
   /**
    * Apply a transition to 'slide' from the currently active view to the next
    * one.
    * Sliding the next subview in means that the previous panelview stays where it
    * is and the active panelview slides in from the left in LTR mode, right in
@@ -674,16 +774,19 @@ this.PanelMultiView = class extends this
    * @param {panelview} viewNode         Node that will becode the active view,
    *                                     after the transition has finished.
    * @param {Boolean}   reverse          Whether we're navigation back to a
    *                                     previous view or forward to a next view.
    * @param {Element}   anchor           the anchor for which we're opening
    *                                     a new panelview, if any
    */
   async _transitionViews(previousViewNode, viewNode, reverse, anchor) {
+    // Clean up any previous transition that may be active at this point.
+    await this._cleanupTransitionPhase();
+
     // There's absolutely no need to show off our epic animation skillz when
     // the panel's not even open.
     if (this._panel.state != "open") {
       return;
     }
 
     const {window, document} = this;
 
@@ -815,17 +918,30 @@ this.PanelMultiView = class extends this
         this._viewContainer.removeEventListener("transitioncancel", details.cancelListener);
         delete details.cancelListener;
         resolve();
       });
     });
 
     details.phase = TRANSITION_PHASES.END;
 
+    // Apply the final visibility, unless the view was closed in the meantime.
+    if (nextPanelView.node.panelMultiView == this.node) {
+      prevPanelView.visible = false;
+      nextPanelView.visible = true;
+      nextPanelView.descriptionHeightWorkaround();
+    }
+
+    // This will complete the operation by removing any transition properties.
     await this._cleanupTransitionPhase(details);
+
+    // Focus the correct element, unless the view was closed in the meantime.
+    if (nextPanelView.node.panelMultiView == this.node) {
+      nextPanelView.focusSelectedElement();
+    }
   }
 
   /**
    * Attempt to clean up the attributes and properties set by `_transitionViews`
    * above. Which attributes and properties depends on the phase the transition
    * was left from - normally that'd be `TRANSITION_PHASES.END`.
    *
    * @param {Object} details Dictionary object containing details of the transition
@@ -835,26 +951,20 @@ this.PanelMultiView = class extends this
   async _cleanupTransitionPhase(details = this._transitionDetails) {
     if (!details || !this.node)
       return;
 
     let {phase, previousViewNode, viewNode, reverse, resolve, listener, cancelListener, anchor} = details;
     if (details == this._transitionDetails)
       this._transitionDetails = null;
 
-    let nextPanelView = PanelView.forNode(viewNode);
-    let prevPanelView = PanelView.forNode(previousViewNode);
-
     // Do the things we _always_ need to do whenever the transition ends or is
     // interrupted.
-    this.hideAllViewsExcept(nextPanelView);
     previousViewNode.removeAttribute("in-transition");
     viewNode.removeAttribute("in-transition");
-    if (reverse)
-      prevPanelView.clearNavigation();
 
     if (anchor)
       anchor.removeAttribute("open");
 
     if (phase >= TRANSITION_PHASES.START) {
       this._panel.removeAttribute("width");
       this._panel.removeAttribute("height");
       // Myeah, panel layout auto-resizing is a funky thing. We'll wait
@@ -977,26 +1087,22 @@ this.PanelMultiView = class extends this
       case "popupshown":
         // Now that the main view is visible, we can check the height of the
         // description elements it contains.
         PanelView.forNode(this._mainView).descriptionHeightWorkaround();
         break;
       case "popuphidden": {
         // WebExtensions consumers can hide the popup from viewshowing, or
         // mid-transition, which disrupts our state:
-        this._viewShowing = null;
         this._transitioning = false;
         this.node.removeAttribute("panelopen");
-        // Raise the ViewHiding event for the current view.
         this._cleanupTransitionPhase();
-        this.hideAllViewsExcept(null);
         this.window.removeEventListener("keydown", this);
         this._panel.removeEventListener("mousemove", this);
-        this.openViews.forEach(panelView => panelView.clearNavigation());
-        this.openViews = [];
+        this.closeAllViews();
 
         // Clear the main view size caches. The dimensions could be different
         // when the popup is opened again, e.g. through touch mode sizing.
         this._viewContainer.style.removeProperty("min-height");
         this._viewStack.style.removeProperty("max-height");
         this._viewContainer.style.removeProperty("width");
         this._viewContainer.style.removeProperty("height");
 
@@ -1020,25 +1126,20 @@ this.PanelView = class extends this.Asso
   set mainview(value) {
     if (value) {
       this.node.setAttribute("mainview", true);
     } else {
       this.node.removeAttribute("mainview");
     }
   }
 
-  set current(value) {
+  set visible(value) {
     if (value) {
-      if (!this.node.hasAttribute("current")) {
-        this.node.setAttribute("current", true);
-        this.descriptionHeightWorkaround();
-        this.dispatchCustomEvent("ViewShown");
-      }
-    } else if (this.node.hasAttribute("current")) {
-      this.dispatchCustomEvent("ViewHiding");
+      this.node.setAttribute("current", true);
+    } else {
       this.node.removeAttribute("current");
     }
   }
 
   /**
    * Constrains the width of this view using the "min-width" and "max-width"
    * styles. Setting this to zero removes the constraints.
    */
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -332,25 +332,16 @@ const PanelUI = {
 
     await window.delayedStartupPromise;
     this._ensureEventListenersAdded();
     this.panel.hidden = false;
     this._isReady = true;
   },
 
   /**
-   * Switch the panel to the main view if it's not already
-   * in that view.
-   */
-  showMainView() {
-    this._ensureEventListenersAdded();
-    this.multiView.showMainView();
-  },
-
-  /**
    * Switch the panel to the help view if it's not already
    * in that view.
    */
   showHelpView(aAnchor) {
     this._ensureEventListenersAdded();
     this.multiView.showSubView("PanelUI-helpView", aAnchor);
   },
 
@@ -410,29 +401,21 @@ const PanelUI = {
       }
       tempPanel.setAttribute("context", "");
       tempPanel.setAttribute("photon", true);
       document.getElementById(CustomizableUI.AREA_NAVBAR).appendChild(tempPanel);
       // If the view has a footer, set a convenience class on the panel.
       tempPanel.classList.toggle("cui-widget-panelWithFooter",
                                  viewNode.querySelector(".panel-subview-footer"));
 
-      // If the panelview is already selected in another PanelMultiView instance
-      // as a subview, make sure to properly hide it there.
-      let oldMultiView = viewNode.panelMultiView;
-      if (oldMultiView && oldMultiView.current == viewNode) {
-        await oldMultiView.showMainView();
-      }
-
       let multiView = document.createElement("panelmultiview");
       multiView.setAttribute("id", "customizationui-widget-multiview");
       multiView.setAttribute("viewCacheId", "appMenu-viewCache");
       multiView.setAttribute("mainViewId", viewNode.id);
       multiView.setAttribute("ephemeral", true);
-      document.getElementById("appMenu-viewCache").appendChild(viewNode);
       tempPanel.appendChild(multiView);
       viewNode.classList.add("cui-widget-panelview");
 
       let viewShown = false;
       let panelRemover = () => {
         viewNode.classList.remove("cui-widget-panelview");
         if (viewShown) {
           CustomizableUI.removePanelCloseListeners(tempPanel);
--- a/browser/components/downloads/content/downloads.js
+++ b/browser/components/downloads/content/downloads.js
@@ -1090,17 +1090,17 @@ var DownloadsViewController = {
       return false;
     }
     if (!(aCommand in this) &&
         !(aCommand in DownloadsViewItem.prototype)) {
       return false;
     }
     // The currently supported commands depend on whether the blocked subview is
     // showing.  If it is, then take the following path.
-    if (DownloadsBlockedSubview.view.showingSubView) {
+    if (DownloadsView.subViewOpen) {
       let blockedSubviewCmds = [
         "downloadsCmd_unblockAndOpen",
         "cmd_delete",
       ];
       return blockedSubviewCmds.includes(aCommand);
     }
     // If the blocked subview is not showing, then determine if focus is on a
     // control in the downloads list.
@@ -1405,23 +1405,16 @@ XPCOMUtils.defineConstant(this, "Downloa
 
 
 // DownloadsBlockedSubview
 
 /**
  * Manages the blocked subview that slides in when you click a blocked download.
  */
 var DownloadsBlockedSubview = {
-
-  get subview() {
-    let subview = document.getElementById("downloadsPanel-blockedSubview");
-    delete this.subview;
-    return this.subview = subview;
-  },
-
   /**
    * Elements in the subview.
    */
   get elements() {
     let idSuffixes = [
       "title",
       "details1",
       "details2",
@@ -1432,25 +1425,16 @@ var DownloadsBlockedSubview = {
       memo[s] = document.getElementById("downloadsPanel-blockedSubview-" + s);
       return memo;
     }, {});
     delete this.elements;
     return this.elements = elements;
   },
 
   /**
-   * The multiview that contains both the main view and the subview.
-   */
-  get view() {
-    let view = document.getElementById("downloadsPanel-multiView");
-    delete this.view;
-    return this.view = view;
-  },
-
-  /**
    * The blocked-download richlistitem element that was clicked to show the
    * subview.  If the subview is not showing, this is undefined.
    */
   element: undefined,
 
   /**
    * Slides in the blocked subview.
    *
@@ -1470,47 +1454,47 @@ var DownloadsBlockedSubview = {
     e.title.textContent = title;
     e.details1.textContent = details[0];
     e.details2.textContent = details[1];
     e.openButton.label = s.unblockButtonOpen;
     e.deleteButton.label = s.unblockButtonConfirmBlock;
 
     let verdict = element.getAttribute("verdict");
     this.subview.setAttribute("verdict", verdict);
-    this.subview.addEventListener("ViewHiding", this);
 
-    this.view.showSubView(this.subview.id);
+    this.mainView.addEventListener("ViewShown", this);
+    DownloadsPanel.panel.addEventListener("popuphidden", this);
+    this.panelMultiView.showSubView(this.subview);
 
     // Without this, the mainView is more narrow than the panel once all
     // downloads are removed from the panel.
-    document.getElementById("downloadsPanel-mainView").style.minWidth =
-      window.getComputedStyle(this.subview).width;
+    this.mainView.style.minWidth = window.getComputedStyle(this.subview).width;
   },
 
   handleEvent(event) {
-    switch (event.type) {
-      case "ViewHiding":
-        this.subview.removeEventListener(event.type, this);
-        DownloadsView.subViewOpen = false;
-        // If we're going back to the main panel, use showPanel to
-        // focus the proper element.
-        if (this.view.current !== this.subview) {
-          DownloadsPanel.showPanel();
-        }
-        break;
-      default:
-        DownloadsCommon.log("Unhandled DownloadsBlockedSubview event: " +
-                            event.type);
-        break;
+    // This is called when the main view is shown or the panel is hidden.
+    DownloadsView.subViewOpen = false;
+    this.mainView.removeEventListener("ViewShown", this);
+    DownloadsPanel.panel.removeEventListener("popuphidden", this);
+    // Focus the proper element if we're going back to the main panel.
+    if (event.type == "ViewShown") {
+      DownloadsPanel.showPanel();
     }
   },
 
   /**
    * Deletes the download and hides the entire panel.
    */
   confirmBlock() {
     goDoCommand("cmd_delete");
     DownloadsPanel.hidePanel();
   },
 };
 
+XPCOMUtils.defineLazyGetter(DownloadsBlockedSubview, "panelMultiView",
+  () => document.getElementById("downloadsPanel-multiView"));
+XPCOMUtils.defineLazyGetter(DownloadsBlockedSubview, "mainView",
+  () => document.getElementById("downloadsPanel-mainView"));
+XPCOMUtils.defineLazyGetter(DownloadsBlockedSubview, "subview",
+  () => document.getElementById("downloadsPanel-blockedSubview"));
+
 XPCOMUtils.defineConstant(this, "DownloadsBlockedSubview",
                           DownloadsBlockedSubview);
--- a/browser/components/downloads/test/browser/browser_downloads_panel_block.js
+++ b/browser/components/downloads/test/browser/browser_downloads_panel_block.js
@@ -15,32 +15,34 @@ add_task(async function mainTest() {
   for (let i = 0; i < verdicts.length; i++) {
     await openPanel();
 
     // The current item is always the first one in the listbox since each
     // iteration of this loop removes the item at the end.
     let item = DownloadsView.richListBox.firstChild;
 
     // Open the panel and click the item to show the subview.
+    let viewPromise = promiseViewShown(DownloadsBlockedSubview.subview);
     EventUtils.sendMouseEvent({ type: "click" }, item);
-    await promiseSubviewShown(true);
+    await viewPromise;
 
     // Items are listed in newest-to-oldest order, so e.g. the first item's
     // verdict is the last element in the verdicts array.
     Assert.ok(DownloadsBlockedSubview.subview.getAttribute("verdict"),
               verdicts[verdicts.count - i - 1]);
 
-    // Click the sliver of the main view that's still showing on the left to go
-    // back to it.
-    EventUtils.synthesizeMouse(DownloadsPanel.panel, 10, 10, {}, window);
-    await promiseSubviewShown(false);
+    // Go back to the main view.
+    viewPromise = promiseViewShown(DownloadsBlockedSubview.mainView);
+    DownloadsBlockedSubview.panelMultiView.goBack();
+    await viewPromise;
 
     // Show the subview again.
+    viewPromise = promiseViewShown(DownloadsBlockedSubview.subview);
     EventUtils.sendMouseEvent({ type: "click" }, item);
-    await promiseSubviewShown(true);
+    await viewPromise;
 
     // Click the Open button.  The download should be unblocked and then opened,
     // i.e., unblockAndOpenDownload() should be called on the item.  The panel
     // should also be closed as a result, so wait for that too.
     let unblockOpenPromise = promiseUnblockAndOpenDownloadCalled(item);
     let hidePromise = promisePanelHidden();
     EventUtils.synthesizeMouse(DownloadsBlockedSubview.elements.openButton,
                                10, 10, {}, window);
@@ -48,29 +50,33 @@ add_task(async function mainTest() {
     await hidePromise;
 
     window.focus();
     await SimpleTest.promiseFocus(window);
 
     // Reopen the panel and show the subview again.
     await openPanel();
 
+    viewPromise = promiseViewShown(DownloadsBlockedSubview.subview);
     EventUtils.sendMouseEvent({ type: "click" }, item);
-    await promiseSubviewShown(true);
+    await viewPromise;
 
     // Click the Remove button.  The panel should close and the item should be
     // removed from it.
+    hidePromise = promisePanelHidden();
     EventUtils.synthesizeMouse(DownloadsBlockedSubview.elements.deleteButton,
                                10, 10, {}, window);
-    await promisePanelHidden();
-    await openPanel();
+    await hidePromise;
 
+    await openPanel();
     Assert.ok(!item.parentNode);
+
+    hidePromise = promisePanelHidden();
     DownloadsPanel.hidePanel();
-    await promisePanelHidden();
+    await hidePromise;
   }
 
   await task_resetState();
 });
 
 async function openPanel() {
   // This function is insane but something intermittently causes the panel to be
   // closed as soon as it's opening on Linux ASAN.  Maybe it would also happen
@@ -122,53 +128,35 @@ async function openPanel() {
           }
           DownloadsPanel.showPanel();
       }
     }, 100);
   });
 }
 
 function promisePanelHidden() {
-  return new Promise(resolve => {
-    if (!DownloadsPanel.panel || DownloadsPanel.panel.state == "closed") {
-      resolve();
-      return;
-    }
-    DownloadsPanel.panel.addEventListener("popuphidden", function() {
-      setTimeout(resolve, 0);
-    }, {once: true});
-  });
+  return BrowserTestUtils.waitForEvent(DownloadsPanel.panel, "popuphidden");
 }
 
 function makeDownload(verdict) {
   return {
     state: DownloadsCommon.DOWNLOAD_DIRTY,
     hasBlockedData: true,
     errorObj: {
       result: Components.results.NS_ERROR_FAILURE,
       message: "Download blocked.",
       becauseBlocked: true,
       becauseBlockedByReputationCheck: true,
       reputationCheckVerdict:  verdict,
     },
   };
 }
 
-function promiseSubviewShown(shown) {
-  // More terribleness, but I'm tired of fighting intermittent timeouts on try.
-  // Just poll for the subview and wait a second before resolving the promise.
-  return new Promise(resolve => {
-    let interval = setInterval(() => {
-      if (shown == DownloadsBlockedSubview.view.showingSubView &&
-          !DownloadsBlockedSubview.view._transitioning) {
-        clearInterval(interval);
-        setTimeout(resolve, 1000);
-      }
-    }, 0);
-  });
+function promiseViewShown(view) {
+  return BrowserTestUtils.waitForEvent(view, "ViewShown");
 }
 
 function promiseUnblockAndOpenDownloadCalled(item) {
   return new Promise(resolve => {
     let realFn = item._shell.unblockAndOpenDownload;
     item._shell.unblockAndOpenDownload = () => {
       item._shell.unblockAndOpenDownload = realFn;
       resolve();
--- a/browser/components/places/content/browserPlacesViews.js
+++ b/browser/components/places/content/browserPlacesViews.js
@@ -2112,24 +2112,16 @@ this.PlacesPanelview = class extends Pla
   }
 
   get events() {
     if (this._events)
       return this._events;
     return this._events = ["click", "command", "dragend", "dragstart", "ViewHiding", "ViewShown"];
   }
 
-  get panel() {
-    return this.panelMultiView.parentNode;
-  }
-
-  get panelMultiView() {
-    return this._viewElt.panelMultiView;
-  }
-
   handleEvent(event) {
     switch (event.type) {
       case "click":
         // For middle clicks, fall through to the command handler.
         if (event.button != 1) {
           break;
         }
       case "command":
@@ -2191,16 +2183,17 @@ this.PlacesPanelview = class extends Pla
 
     this._controller.setDataTransfer(event);
     event.stopPropagation();
   }
 
   uninit(event) {
     this._removeEventListeners(this.panelMultiView, this.events);
     this._removeEventListeners(window, ["unload"]);
+    delete this.panelMultiView;
     super.uninit(event);
   }
 
   _createDOMNodeForPlacesNode(placesNode) {
     this._domNodes.delete(placesNode);
 
     let element;
     let type = placesNode.type;
@@ -2250,20 +2243,16 @@ this.PlacesPanelview = class extends Pla
     } else {
       panelview.removeAttribute("emptyplacesresult");
       try {
         panelview.removeChild(panelview._emptyMenuitem);
       } catch (ex) {}
     }
   }
 
-  _isPopupOpen() {
-    return this.panel.state == "open" && this.panelMultiView.current == this._viewElt;
-  }
-
   _onPopupHidden(event) {
     let panelview = event.originalTarget;
     let placesNode = panelview._placesNode;
     // Avoid handling ViewHiding of inner views
     if (placesNode && PlacesUIUtils.getViewForNode(panelview) == this) {
       // UI performance: folder queries are cheap, keep the resultnode open
       // so we don't rebuild its contents whenever the popup is reopened.
       // Though, we want to always close feed containers so their expiration
@@ -2275,16 +2264,17 @@ this.PlacesPanelview = class extends Pla
     }
   }
 
   _onPopupShowing(event) {
     // If the event came from the root element, this is the first time
     // we ever get here.
     if (event.originalTarget == this._rootElt) {
       // Start listening for events from all panels inside the panelmultiview.
+      this.panelMultiView = this._viewElt.panelMultiView;
       this._addEventListeners(this.panelMultiView, this.events);
     }
     super._onPopupShowing(event);
   }
 
   _onViewShown(event) {
     if (event.originalTarget != this._viewElt)
       return;
--- a/browser/extensions/activity-stream/test/functional/mochitest/browser.ini
+++ b/browser/extensions/activity-stream/test/functional/mochitest/browser.ini
@@ -2,9 +2,10 @@
 support-files =
   blue_page.html
   head.js
 
 [browser_as_load_location.js]
 [browser_as_render.js]
 [browser_getScreenshots.js]
 [browser_highlights_section.js]
+[browser_topsites_contextMenu_options.js]
 [browser_topsites_section.js]
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/test/functional/mochitest/browser_topsites_contextMenu_options.js
@@ -0,0 +1,27 @@
+/* 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";
+
+/**
+ * Test verifies the menu options for a default top site.
+ */
+
+test_newtab({
+  before: setDefaultTopSites,
+  test: async function defaultTopSites_menuOptions() {
+    await ContentTaskUtils.waitForCondition(() => content.document.querySelector(".top-site-icon"),
+      "Topsite tippytop icon not found");
+
+    let contextMenuItems = content.openContextMenuAndGetOptions(".top-sites-list li:first-child").map(v => v.textContent);
+
+    Assert.equal(contextMenuItems.length, 5, "Number of options is correct");
+
+    const expectedItemsText = ["Pin", "Edit", "Open in a New Window", "Open in a New Private Window", "Dismiss"];
+
+    for (let i = 0; i < contextMenuItems.length; i++) {
+      Assert.equal(contextMenuItems[i], expectedItemsText[i], "Name option is correct");
+    }
+  }
+});
--- a/browser/extensions/activity-stream/test/functional/mochitest/browser_topsites_section.js
+++ b/browser/extensions/activity-stream/test/functional/mochitest/browser_topsites_section.js
@@ -12,23 +12,17 @@ test_newtab(
 
     found = content.document.querySelector(".modal-overlay");
     ok(found && !found.hidden, "Should find a visible overlay");
   }
 );
 
 // Test pin/unpin context menu options.
 test_newtab({
-  async before({pushPrefs}) {
-    // The pref for TopSites is empty by default.
-    await pushPrefs(["browser.newtabpage.activity-stream.default.sites", "https://www.youtube.com/,https://www.facebook.com/,https://www.amazon.com/,https://www.reddit.com/,https://www.wikipedia.org/,https://twitter.com/"]);
-    // Toggle the feed off and on as a workaround to read the new prefs.
-    await pushPrefs(["browser.newtabpage.activity-stream.feeds.topsites", false]);
-    await pushPrefs(["browser.newtabpage.activity-stream.feeds.topsites", true]);
-  },
+  before: setDefaultTopSites,
   // it should pin the website when we click the first option of the topsite context menu.
   test: async function topsites_pin_unpin() {
     await ContentTaskUtils.waitForCondition(() => content.document.querySelector(".top-site-icon"),
       "Topsite tippytop icon not found");
     // There are only topsites on the page, the selector with find the first topsite menu button.
     const topsiteContextBtn = content.document.querySelector(".context-menu-button");
     topsiteContextBtn.click();
 
--- a/browser/extensions/activity-stream/test/functional/mochitest/head.js
+++ b/browser/extensions/activity-stream/test/functional/mochitest/head.js
@@ -5,16 +5,25 @@ ChromeUtils.defineModuleGetter(this, "Pl
 
 function popPrefs() {
   return SpecialPowers.popPrefEnv();
 }
 function pushPrefs(...prefs) {
   return SpecialPowers.pushPrefEnv({set: prefs});
 }
 
+async function setDefaultTopSites() {
+  // The pref for TopSites is empty by default.
+  await pushPrefs(["browser.newtabpage.activity-stream.default.sites",
+  "https://www.youtube.com/,https://www.facebook.com/,https://www.amazon.com/,https://www.reddit.com/,https://www.wikipedia.org/,https://twitter.com/"]);
+  // Toggle the feed off and on as a workaround to read the new prefs.
+  await pushPrefs(["browser.newtabpage.activity-stream.feeds.topsites", false]);
+  await pushPrefs(["browser.newtabpage.activity-stream.feeds.topsites", true]);
+}
+
 async function clearHistoryAndBookmarks() { // eslint-disable-line no-unused-vars
   await PlacesUtils.bookmarks.eraseEverything();
   await PlacesUtils.history.clear();
 }
 
 /**
  * Helper to wait for potentially preloaded browsers to "load" where a preloaded
  * page has already loaded and won't trigger "load", and a "load"ed page might
@@ -53,16 +62,41 @@ async function addHighlightsBookmarks(co
     await PlacesTestUtils.addVisits(placeInfo.url);
   }
 
   // Force HighlightsFeed to make a request for the new items.
   refreshHighlightsFeed();
 }
 
 /**
+ * Helper to add various helpers to the content process by injecting variables
+ * and functions to the `content` global.
+ */
+function addContentHelpers() {
+  const {document} = content;
+  Object.assign(content, {
+    /**
+     * Click the context menu button for an item and get its options list.
+     *
+     * @param selector {String} Selector to get an item (e.g., top site, card)
+     * @return {Array} The nodes for the options.
+     */
+    openContextMenuAndGetOptions(selector) {
+      const item = document.querySelector(selector);
+      const contextButton = item.querySelector(".context-menu-button");
+      contextButton.click();
+
+      const contextMenu = item.querySelector(".context-menu");
+      const contextMenuList = contextMenu.querySelector(".context-menu-list");
+      return [...contextMenuList.getElementsByClassName("context-menu-item")];
+    }
+  });
+}
+
+/**
  * Helper to run Activity Stream about:newtab test tasks in content.
  *
  * @param testInfo {Function|Object}
  *   {Function} This parameter will be used as if the function were called with
  *              an Object with this parameter as "test" key's value.
  *   {Object} The following keys are expected:
  *     before {Function} Optional. Runs before and returns an arg for "test"
  *     test   {Function} The test to run in the about:newtab content task taking
@@ -99,16 +133,19 @@ function test_newtab(testInfo) { // esli
   let testTask = async () => {
     // Open about:newtab without using the default load listener
     let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:newtab", false);
 
     // Specially wait for potentially preloaded browsers
     let browser = tab.linkedBrowser;
     await waitForPreloaded(browser);
 
+    // Add shared helpers to the content process
+    ContentTask.spawn(browser, {}, addContentHelpers);
+
     // Wait for React to render something
     await BrowserTestUtils.waitForCondition(() => ContentTask.spawn(browser, {},
       () => content.document.getElementById("root").children.length),
       "Should render activity stream content");
 
     // Chain together before -> contentTask -> after data passing
     try {
       let contentArg = await before({pushPrefs: scopedPushPrefs, tab});
--- a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/AppMenu.jsm
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/AppMenu.jsm
@@ -2,78 +2,72 @@
  * 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.EXPORTED_SYMBOLS = ["AppMenu"];
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
+ChromeUtils.import("resource://testing-common/BrowserTestUtils.jsm");
 
 this.AppMenu = {
 
   init(libDir) {},
 
   configurations: {
-    appMenuClosed: {
+    appMenuMainView: {
       selectors: ["#appMenu-popup"],
       async applyConfig() {
         let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
-        browserWindow.PanelUI.hide();
-      },
-    },
-
-    appMenuMainView: {
-      selectors: ["#appMenu-popup"],
-      applyConfig() {
-        let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
-        let promise = browserWindow.PanelUI.show();
-        browserWindow.PanelUI.showMainView();
-        return promise;
+        await reopenAppMenu(browserWindow);
       },
     },
 
     appMenuHistorySubview: {
       selectors: ["#appMenu-popup"],
       async applyConfig() {
-        // History has a footer
-        if (isCustomizing()) {
-          return "Can't show subviews while customizing";
-        }
         let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
-        await browserWindow.PanelUI.show();
-        browserWindow.PanelUI.showMainView();
-        browserWindow.document.getElementById("history-panelmenu").click();
+        await reopenAppMenu(browserWindow);
 
-        return undefined;
+        let view = browserWindow.document.getElementById("appMenu-libraryView");
+        let promiseViewShown = BrowserTestUtils.waitForEvent(view, "ViewShown");
+        browserWindow.document.getElementById("appMenu-library-button").click();
+        await promiseViewShown;
       },
 
       verifyConfig: verifyConfigHelper,
     },
 
     appMenuHelpSubview: {
       selectors: ["#appMenu-popup"],
       async applyConfig() {
-        if (isCustomizing()) {
-          return "Can't show subviews while customizing";
-        }
         let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
-        await browserWindow.PanelUI.show();
-        browserWindow.PanelUI.showMainView();
-        browserWindow.document.getElementById("PanelUI-help").click();
+        await reopenAppMenu(browserWindow);
 
-        return undefined;
+        let view = browserWindow.document.getElementById("PanelUI-helpView");
+        let promiseViewShown = BrowserTestUtils.waitForEvent(view, "ViewShown");
+        browserWindow.document.getElementById("appMenu-help-button").click();
+        await promiseViewShown;
       },
 
       verifyConfig: verifyConfigHelper,
     },
 
   },
 };
 
+async function reopenAppMenu(browserWindow) {
+  browserWindow.PanelUI.hide();
+  let view = browserWindow.document.getElementById("appMenu-mainView");
+  let promiseViewShown = BrowserTestUtils.waitForEvent(view, "ViewShown");
+  await browserWindow.PanelUI.show();
+  await promiseViewShown;
+}
+
 function verifyConfigHelper() {
   if (isCustomizing()) {
     return "navigator:browser has the customizing attribute";
   }
   return undefined;
 }
 
 function isCustomizing() {
--- a/devtools/client/netmonitor/src/connector/firefox-connector.js
+++ b/devtools/client/netmonitor/src/connector/firefox-connector.js
@@ -195,30 +195,30 @@ class FirefoxConnector {
   onDocLoadingMarker(marker) {
     // Translate marker into event similar to newer "docEvent" event sent by the console
     // actor
     let event = {
       name: marker.name == "document::DOMContentLoaded" ?
             "dom-interactive" : "dom-complete",
       time: marker.unixTime / 1000
     };
+    this.actions.addTimingMarker(event);
     window.emit(EVENTS.TIMELINE_EVENT, event);
-    this.actions.addTimingMarker(event);
   }
 
   /**
    * The "DOMContentLoaded" and "Load" events sent by the console actor.
    *
    * Only used by FF60+.
    *
    * @param {object} marker
    */
   onDocEvent(type, event) {
+    this.actions.addTimingMarker(event);
     window.emit(EVENTS.TIMELINE_EVENT, event);
-    this.actions.addTimingMarker(event);
   }
 
   /**
    * Send a HTTP request data payload
    *
    * @param {object} data data payload would like to sent to backend
    * @param {function} callback callback will be invoked after the request finished
    */
--- a/devtools/shared/webconsole/test/test_object_actor_native_getters_lenient_this.html
+++ b/devtools/shared/webconsole/test/test_object_actor_native_getters_lenient_this.html
@@ -61,17 +61,21 @@ function onConsoleCall(aState, aType, aP
 function onProperties(aState, aResponse)
 {
   let props = aResponse.ownProperties;
   let keys = Object.keys(props);
   info(keys.length + " ownProperties: " + keys);
 
   is(keys.length, 0, "number of properties");
   keys = Object.keys(aResponse.safeGetterValues);
-  is(keys.length, 0, "number of safe getters");
+  // There is one "safe getter" as far as the code in _findSafeGetterValues is
+  // concerned, because it treats any Promise-returning attribute as a "safe
+  // getter".  See bug 1438015.
+  is(keys.length, 1, "number of safe getters");
+  is(keys[0], "documentReadyForIdle", "Unexpected safe getter");
 
   closeDebugger(aState, function() {
     SimpleTest.finish();
   });
 }
 
 addEventListener("load", startTest);
 </script>
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -543,20 +543,17 @@ Navigator::GetPermissions(ErrorResult& a
 }
 
 StorageManager*
 Navigator::Storage()
 {
   MOZ_ASSERT(mWindow);
 
   if(!mStorageManager) {
-    nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
-    MOZ_ASSERT(global);
-
-    mStorageManager = new StorageManager(global);
+    mStorageManager = new StorageManager(mWindow->AsGlobal());
   }
 
   return mStorageManager;
 }
 
 // Values for the network.cookie.cookieBehavior pref are documented in
 // nsCookieService.cpp.
 #define COOKIE_BEHAVIOR_REJECT 2
@@ -1312,18 +1309,17 @@ Navigator::GetBattery(ErrorResult& aRv)
     return mBatteryPromise;
   }
 
   if (!mWindow || !mWindow->GetDocShell()) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
-  nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
-  RefPtr<Promise> batteryPromise = Promise::Create(go, aRv);
+  RefPtr<Promise> batteryPromise = Promise::Create(mWindow->AsGlobal(), aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
   mBatteryPromise = batteryPromise;
 
   if (!mBatteryManager) {
     mBatteryManager = new battery::BatteryManager(mWindow);
     mBatteryManager->Init();
@@ -1370,18 +1366,17 @@ Navigator::GetVRDisplays(ErrorResult& aR
   if (!mWindow || !mWindow->GetDocShell()) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(mWindow);
   win->NotifyVREventListenerAdded();
 
-  nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
-  RefPtr<Promise> p = Promise::Create(go, aRv);
+  RefPtr<Promise> p = Promise::Create(mWindow->AsGlobal(), aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
   // We pass mWindow's id to RefreshVRDisplays, so NotifyVRDisplaysUpdated will
   // be called asynchronously, resolving the promises in mVRGetDisplaysPromises.
   if (!VRDisplay::RefreshVRDisplays(win->WindowID())) {
     p->MaybeReject(NS_ERROR_FAILURE);
@@ -1845,19 +1840,18 @@ Navigator::RequestMediaKeySystemAccess(c
                                     NS_LITERAL_CSTRING("Media"),
                                     doc,
                                     nsContentUtils::eDOM_PROPERTIES,
                                     "MediaEMEInsecureContextDeprecatedWarning",
                                     params,
                                     ArrayLength(params));
   }
 
-  nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
   RefPtr<DetailedPromise> promise =
-    DetailedPromise::Create(go, aRv,
+    DetailedPromise::Create(mWindow->AsGlobal(), aRv,
       NS_LITERAL_CSTRING("navigator.requestMediaKeySystemAccess"),
       Telemetry::VIDEO_EME_REQUEST_SUCCESS_LATENCY_MS,
       Telemetry::VIDEO_EME_REQUEST_FAILURE_LATENCY_MS);
   if (aRv.Failed()) {
     return nullptr;
   }
 
   if (!mMediaKeySystemAccessManager) {
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -1919,16 +1919,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mChildren[i]");
     cb.NoteXPCOMChild(tmp->mChildren.ChildAt(indx - 1));
   }
 
   // Traverse all nsIDocument pointer members.
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityInfo)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDisplayDocument)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReadyForIdle)
 
   // Traverse all nsDocument nsCOMPtrs.
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptGlobalObject)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMStyleSheets)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheetSetList)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader)
@@ -2060,16 +2061,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mImageMaps)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentTimeline)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingAnimationTracker)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mTemplateContentsOwner)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildrenCollection)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOrientationPendingPromise)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadyForIdle);
 
   tmp->mParentDocument = nullptr;
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages)
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntersectionObservers)
 
   tmp->ClearAllBoxObjects();
@@ -2730,16 +2732,17 @@ nsDocument::StartDocumentLoad(const char
     // styles
     CSSLoader()->SetEnabled(false); // Do not load/process styles when loading as data
   } else if (nsCRT::strcmp("external-resource", aCommand) == 0) {
     // Allow CSS, but not scripts
     ScriptLoader()->SetEnabled(false);
   }
 
   mMayStartLayout = false;
+  MOZ_ASSERT(!mReadyForIdle, "We should never hit DOMContentLoaded before this point");
 
   if (aReset) {
     Reset(aChannel, aLoadGroup);
   }
 
   nsAutoCString contentType;
   nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel);
   if ((bag && NS_SUCCEEDED(bag->GetPropertyAsACString(
@@ -5261,16 +5264,20 @@ nsDocument::DispatchContentLoadedEvents(
 
   // Fire a DOM event notifying listeners that this document has been
   // loaded (excluding images and other loads initiated by this
   // document).
   nsContentUtils::DispatchTrustedEvent(this, static_cast<nsIDocument*>(this),
                                        NS_LITERAL_STRING("DOMContentLoaded"),
                                        true, false);
 
+  if (MayStartLayout()) {
+    MaybeResolveReadyForIdle();
+  }
+
   RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
   nsIDocShell* docShell = this->GetDocShell();
 
   if (timelines && timelines->HasConsumer(docShell)) {
     timelines->AddMarkerForDocShell(docShell,
       MakeUnique<DocLoadingTimelineMarker>("document::DOMContentLoaded"));
   }
 
@@ -6587,16 +6594,29 @@ nsIDocument::MatchMedia(const nsAString&
 }
 
 void
 nsDocument::FlushSkinBindings()
 {
   BindingManager()->FlushSkinBindings();
 }
 
+void
+nsIDocument::SetMayStartLayout(bool aMayStartLayout)
+{
+  mMayStartLayout = aMayStartLayout;
+  if (MayStartLayout()) {
+    ReadyState state = GetReadyStateEnum();
+    if (state >= READYSTATE_INTERACTIVE) {
+      // DOMContentLoaded has fired already.
+      MaybeResolveReadyForIdle();
+    }
+  }
+}
+
 nsresult
 nsDocument::InitializeFrameLoader(nsFrameLoader* aLoader)
 {
   mInitializableFrameLoaders.RemoveElement(aLoader);
   // Don't even try to initialize.
   if (mInDestructor) {
     NS_WARNING("Trying to initialize a frame loader while"
                "document is being deleted");
@@ -10312,16 +10332,45 @@ nsIDocument::GetMozDocumentURIIfNotForEr
   nsCOMPtr<nsIURI> uri = GetDocumentURIObject();
   if (!uri) {
     return nullptr;
   }
 
   return uri.forget();
 }
 
+Promise*
+nsIDocument::GetDocumentReadyForIdle(ErrorResult& aRv)
+{
+  if (!mReadyForIdle) {
+    nsIGlobalObject* global = GetScopeObject();
+    if (!global) {
+      aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+      return nullptr;
+    }
+
+    mReadyForIdle = Promise::Create(global, aRv);
+    if (aRv.Failed()) {
+      return nullptr;
+    }
+  }
+
+  return mReadyForIdle;
+}
+
+void
+nsIDocument::MaybeResolveReadyForIdle()
+{
+  IgnoredErrorResult rv;
+  Promise* readyPromise = GetDocumentReadyForIdle(rv);
+  if (readyPromise) {
+    readyPromise->MaybeResolve(this);
+  }
+}
+
 nsIHTMLCollection*
 nsIDocument::Children()
 {
   if (!mChildrenCollection) {
     mChildrenCollection = new nsContentList(this, kNameSpaceID_Wildcard,
                                             nsGkAtoms::_asterisk,
                                             nsGkAtoms::_asterisk,
                                             false);
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -7932,16 +7932,28 @@ nsPIDOMWindowInner::GetDocGroup() const
 {
   nsIDocument* doc = GetExtantDoc();
   if (doc) {
     return doc->GetDocGroup();
   }
   return nullptr;
 }
 
+nsIGlobalObject*
+nsPIDOMWindowInner::AsGlobal()
+{
+  return nsGlobalWindowInner::Cast(this);
+}
+
+const nsIGlobalObject*
+nsPIDOMWindowInner::AsGlobal() const
+{
+  return nsGlobalWindowInner::Cast(this);
+}
+
 // XXX: Can we define this in a header instead of here?
 namespace mozilla {
 namespace dom {
 extern uint64_t
 NextWindowID();
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2183,20 +2183,17 @@ public:
     return mLoadedAsInteractiveData;
   }
 
   bool MayStartLayout()
   {
     return mMayStartLayout;
   }
 
-  virtual void SetMayStartLayout(bool aMayStartLayout)
-  {
-    mMayStartLayout = aMayStartLayout;
-  }
+  virtual void SetMayStartLayout(bool aMayStartLayout);
 
   already_AddRefed<nsIDocumentEncoder> GetCachedEncoder();
 
   void SetCachedEncoder(already_AddRefed<nsIDocumentEncoder> aEncoder);
 
   // In case of failure, the document really can't initialize the frame loader.
   virtual nsresult InitializeFrameLoader(nsFrameLoader* aLoader) = 0;
   // In case of failure, the caller must handle the error, for example by
@@ -3008,16 +3005,18 @@ public:
   void ObsoleteSheet(const nsAString& aSheetURI, mozilla::ErrorResult& rv);
 
   already_AddRefed<mozilla::dom::Promise> BlockParsing(mozilla::dom::Promise& aPromise,
                                                        const mozilla::dom::BlockParsingOptions& aOptions,
                                                        mozilla::ErrorResult& aRv);
 
   already_AddRefed<nsIURI> GetMozDocumentURIIfNotForErrorPages();
 
+  mozilla::dom::Promise* GetDocumentReadyForIdle(mozilla::ErrorResult& aRv);
+
   // ParentNode
   nsIHTMLCollection* Children();
   uint32_t ChildElementCount();
 
   virtual nsHTMLDocument* AsHTMLDocument() { return nullptr; }
   virtual mozilla::dom::SVGDocument* AsSVGDocument() { return nullptr; }
   virtual mozilla::dom::XULDocument* AsXULDocument() { return nullptr; }
 
@@ -3291,16 +3290,18 @@ protected:
   void MaybeActivateByUserGesture(nsIPrincipal* aPrincipal);
 
   // Helpers for GetElementsByName.
   static bool MatchNameAttribute(mozilla::dom::Element* aElement,
                                  int32_t aNamespaceID,
                                  nsAtom* aAtom, void* aData);
   static void* UseExistingNameString(nsINode* aRootNode, const nsString* aName);
 
+  void MaybeResolveReadyForIdle();
+
   nsCString mReferrer;
   nsString mLastModified;
 
   nsCOMPtr<nsIURI> mDocumentURI;
   nsCOMPtr<nsIURI> mOriginalURI;
   nsCOMPtr<nsIURI> mChromeXHRDocURI;
   nsCOMPtr<nsIURI> mDocumentBaseURI;
   nsCOMPtr<nsIURI> mChromeXHRDocBaseURI;
@@ -3372,16 +3373,18 @@ protected:
   RefPtr<mozilla::dom::FontFaceSet> mFontFaceSet;
 
   // Last time this document or a one of its sub-documents was focused.  If
   // focus has never occurred then mLastFocusTime.IsNull() will be true.
   mozilla::TimeStamp mLastFocusTime;
 
   mozilla::EventStates mDocumentState;
 
+  RefPtr<mozilla::dom::Promise> mReadyForIdle;
+
   // True if BIDI is enabled.
   bool mBidiEnabled : 1;
   // True if a MathML element has ever been owned by this document.
   bool mMathMLEnabled : 1;
 
   // True if this document is the initial document for a window.  This should
   // basically be true only for documents that exist in newly-opened windows or
   // documents created to satisfy a GetDocument() on a window when there's no
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -150,16 +150,19 @@ public:
 
   nsPIDOMWindowInner* AsInner() {
     return this;
   }
   const nsPIDOMWindowInner* AsInner() const {
     return this;
   }
 
+  nsIGlobalObject* AsGlobal();
+  const nsIGlobalObject* AsGlobal() const;
+
   nsPIDOMWindowOuter* GetOuterWindow() const {
     return mOuterWindow;
   }
 
   static nsPIDOMWindowInner* From(mozIDOMWindow* aFrom) {
     return static_cast<nsPIDOMWindowInner*>(aFrom);
   }
 
--- a/dom/events/DOMEventTargetHelper.cpp
+++ b/dom/events/DOMEventTargetHelper.cpp
@@ -96,18 +96,17 @@ DOMEventTargetHelper::~DOMEventTargetHel
     mListenerManager->Disconnect();
   }
   ReleaseWrapper(this);
 }
 
 void
 DOMEventTargetHelper::BindToOwner(nsPIDOMWindowInner* aOwner)
 {
-  nsCOMPtr<nsIGlobalObject> glob = do_QueryInterface(aOwner);
-  BindToOwner(glob);
+  BindToOwner(aOwner ? aOwner->AsGlobal() : nullptr);
 }
 
 void
 DOMEventTargetHelper::BindToOwner(nsIGlobalObject* aOwner)
 {
   if (mParentObject) {
     mParentObject->RemoveEventTargetObject(this);
     if (mOwnerWindow) {
--- a/dom/gamepad/GamepadServiceTest.cpp
+++ b/dom/gamepad/GamepadServiceTest.cpp
@@ -108,19 +108,18 @@ GamepadServiceTest::AddGamepad(const nsA
   }
 
   // Only VR controllers has displayID, we give 0 to the general gamepads.
   GamepadAdded a(nsString(aID),
                  aMapping, aHand, 0,
                  aNumButtons, aNumAxes, aNumHaptics);
   GamepadChangeEventBody body(a);
   GamepadChangeEvent e(0, GamepadServiceType::Standard, body);
-  nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
 
-  RefPtr<Promise> p = Promise::Create(go, aRv);
+  RefPtr<Promise> p = Promise::Create(mWindow->AsGlobal(), aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
   uint32_t id = ++mEventNumber;
 
   mChild->AddPromise(id, p);
   mChild->SendGamepadTestEvent(id, e);
--- a/dom/html/HTMLCanvasElement.cpp
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -915,19 +915,23 @@ HTMLCanvasElement::TransferControlToOffs
   }
 
   if (!mOffscreenCanvas) {
     nsIntSize sz = GetWidthHeight();
     RefPtr<AsyncCanvasRenderer> renderer = GetAsyncCanvasRenderer();
     renderer->SetWidth(sz.width);
     renderer->SetHeight(sz.height);
 
-    nsCOMPtr<nsIGlobalObject> global =
-      do_QueryInterface(OwnerDoc()->GetInnerWindow());
-    mOffscreenCanvas = new OffscreenCanvas(global,
+    nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow();
+    if (!win) {
+      aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+      return nullptr;
+    }
+
+    mOffscreenCanvas = new OffscreenCanvas(win->AsGlobal(),
                                            sz.width,
                                            sz.height,
                                            GetCompositorBackendType(),
                                            renderer);
     if (mWriteOnly) {
       mOffscreenCanvas->SetWriteOnly();
     }
 
@@ -1124,21 +1128,20 @@ HTMLCanvasElement::InvalidateCanvasConte
     }
   }
 
   /*
    * Treat canvas invalidations as animation activity for JS. Frequently
    * invalidating a canvas will feed into heuristics and cause JIT code to be
    * kept around longer, for smoother animations.
    */
-  nsCOMPtr<nsIGlobalObject> global =
-    do_QueryInterface(OwnerDoc()->GetInnerWindow());
+  nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow();
 
-  if (global) {
-    if (JSObject *obj = global->GetGlobalJSObject()) {
+  if (win) {
+    if (JSObject *obj = win->AsGlobal()->GetGlobalJSObject()) {
       js::NotifyAnimationActivity(obj);
     }
   }
 }
 
 void
 HTMLCanvasElement::InvalidateCanvas()
 {
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -2699,21 +2699,20 @@ HTMLMediaElement::SeekToNextFrame(ErrorR
     // We can't perform NextFrameSeek while seek is already in action.
     // Just return the pending seek promise.
     return do_AddRef(mSeekDOMPromise);
   }
 
   /* This will cause JIT code to be kept around longer, to help performance
    * when using SeekToNextFrame to iterate through every frame of a video.
    */
-  nsCOMPtr<nsIGlobalObject> global =
-    do_QueryInterface(OwnerDoc()->GetInnerWindow());
-
-  if (global) {
-    if (JSObject *obj = global->GetGlobalJSObject()) {
+  nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow();
+
+  if (win) {
+    if (JSObject *obj = win->AsGlobal()->GetGlobalJSObject()) {
       js::NotifyAnimationActivity(obj);
     }
   }
 
   return Seek(CurrentTime(), SeekTarget::NextFrame, aRv);
 }
 
 void
@@ -7105,23 +7104,22 @@ HTMLMediaElement::SetMediaKeys(mozilla::
   LOG(LogLevel::Debug, ("%p SetMediaKeys(%p) mMediaKeys=%p mDecoder=%p",
     this, aMediaKeys, mMediaKeys.get(), mDecoder.get()));
 
   if (MozAudioCaptured()) {
     aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     return nullptr;
   }
 
-  nsCOMPtr<nsIGlobalObject> global =
-    do_QueryInterface(OwnerDoc()->GetInnerWindow());
-  if (!global) {
+  nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow();
+  if (!win) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
-  RefPtr<DetailedPromise> promise = DetailedPromise::Create(global, aRv,
+  RefPtr<DetailedPromise> promise = DetailedPromise::Create(win->AsGlobal(), aRv,
     NS_LITERAL_CSTRING("HTMLMediaElement.setMediaKeys"));
   if (aRv.Failed()) {
     return nullptr;
   }
 
   // 1. If mediaKeys and the mediaKeys attribute are the same object,
   // return a resolved promise.
   if (mMediaKeys == aMediaKeys) {
@@ -7602,24 +7600,24 @@ HTMLMediaElement::NotifyAboutPlaying()
   // Stick to the DispatchAsyncEvent() call path for now because we want to
   // trigger some telemetry-related codes in the DispatchAsyncEvent() method.
   DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
 }
 
 already_AddRefed<Promise>
 HTMLMediaElement::CreateDOMPromise(ErrorResult& aRv) const
 {
-  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(OwnerDoc()->GetInnerWindow());
-
-  if (!global) {
+  nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow();
+
+  if (!win) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
-  return Promise::Create(global, aRv);
+  return Promise::Create(win->AsGlobal(), aRv);
 }
 
 void
 HTMLMediaElement::AsyncResolvePendingPlayPromises()
 {
   if (mShuttingDown) {
     return;
   }
--- a/dom/media/MediaDevices.cpp
+++ b/dom/media/MediaDevices.cpp
@@ -178,42 +178,38 @@ NS_IMPL_ISUPPORTS(MediaDevices::GumResol
 NS_IMPL_ISUPPORTS(MediaDevices::EnumDevResolver, nsIGetUserMediaDevicesSuccessCallback)
 NS_IMPL_ISUPPORTS(MediaDevices::GumRejecter, nsIDOMGetUserMediaErrorCallback)
 
 already_AddRefed<Promise>
 MediaDevices::GetUserMedia(const MediaStreamConstraints& aConstraints,
 			   CallerType aCallerType,
                            ErrorResult &aRv)
 {
-  nsPIDOMWindowInner* window = GetOwner();
-  nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(window);
-  RefPtr<Promise> p = Promise::Create(go, aRv);
+  RefPtr<Promise> p = Promise::Create(GetParentObject(), aRv);
   NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
 
   RefPtr<GumResolver> resolver = new GumResolver(p);
   RefPtr<GumRejecter> rejecter = new GumRejecter(p);
 
-  aRv = MediaManager::Get()->GetUserMedia(window, aConstraints,
+  aRv = MediaManager::Get()->GetUserMedia(GetOwner(), aConstraints,
                                           resolver, rejecter,
 					  aCallerType);
   return p.forget();
 }
 
 already_AddRefed<Promise>
 MediaDevices::EnumerateDevices(CallerType aCallerType, ErrorResult &aRv)
 {
-  nsPIDOMWindowInner* window = GetOwner();
-  nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(window);
-  RefPtr<Promise> p = Promise::Create(go, aRv);
+  RefPtr<Promise> p = Promise::Create(GetParentObject(), aRv);
   NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
 
-  RefPtr<EnumDevResolver> resolver = new EnumDevResolver(p, window->WindowID());
+  RefPtr<EnumDevResolver> resolver = new EnumDevResolver(p, GetOwner()->WindowID());
   RefPtr<GumRejecter> rejecter = new GumRejecter(p);
 
-  aRv = MediaManager::Get()->EnumerateDevices(window, resolver, rejecter, aCallerType);
+  aRv = MediaManager::Get()->EnumerateDevices(GetOwner(), resolver, rejecter, aCallerType);
   return p.forget();
 }
 
 NS_IMPL_ADDREF_INHERITED(MediaDevices, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(MediaDevices, DOMEventTargetHelper)
 NS_INTERFACE_MAP_BEGIN(MediaDevices)
   NS_INTERFACE_MAP_ENTRY(MediaDevices)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -3696,18 +3696,17 @@ MediaStreamGraph::GetInstance(MediaStrea
           AddBlocker(gMediaStreamGraphShutdownBlocker,
                      NS_LITERAL_STRING(__FILE__), __LINE__,
                      NS_LITERAL_STRING("MediaStreamGraph shutdown"));
       MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
     }
 
     AbstractThread* mainThread;
     if (aWindow) {
-      nsCOMPtr<nsIGlobalObject> parentObject = do_QueryInterface(aWindow);
-      mainThread = parentObject->AbstractMainThreadFor(TaskCategory::Other);
+      mainThread = aWindow->AsGlobal()->AbstractMainThreadFor(TaskCategory::Other);
     } else {
       // Uncommon case, only for some old configuration of webspeech.
       mainThread = AbstractThread::MainThread();
     }
     graph = new MediaStreamGraphImpl(aGraphDriverRequested,
                                      CubebUtils::PreferredSampleRate(),
                                      mainThread);
 
@@ -3722,21 +3721,20 @@ MediaStreamGraph::GetInstance(MediaStrea
 }
 
 MediaStreamGraph*
 MediaStreamGraph::CreateNonRealtimeInstance(TrackRate aSampleRate,
                                             nsPIDOMWindowInner* aWindow)
 {
   MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
 
-  nsCOMPtr<nsIGlobalObject> parentObject = do_QueryInterface(aWindow);
   MediaStreamGraphImpl* graph = new MediaStreamGraphImpl(
     OFFLINE_THREAD_DRIVER,
     aSampleRate,
-    parentObject->AbstractMainThreadFor(TaskCategory::Other));
+    aWindow->AsGlobal()->AbstractMainThreadFor(TaskCategory::Other));
 
   LOG(LogLevel::Debug, ("Starting up Offline MediaStreamGraph %p", graph));
 
   return graph;
 }
 
 void
 MediaStreamGraph::DestroyNonRealtimeInstance(MediaStreamGraph* aGraph)
--- a/dom/media/MediaStreamTrack.cpp
+++ b/dom/media/MediaStreamTrack.cpp
@@ -289,19 +289,22 @@ MediaStreamTrack::ApplyConstraints(const
 
     LOG(LogLevel::Info, ("MediaStreamTrack %p ApplyConstraints() with "
                          "constraints %s", this, NS_ConvertUTF16toUTF8(str).get()));
   }
 
   typedef media::Pledge<bool, MediaStreamError*> PledgeVoid;
 
   nsPIDOMWindowInner* window = mOwningStream->GetParentObject();
+  nsIGlobalObject* go = window ? window->AsGlobal() : nullptr;
 
-  nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(window);
   RefPtr<Promise> promise = Promise::Create(go, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
 
   // Forward constraints to the source.
   //
   // After GetSource().ApplyConstraints succeeds (after it's been to media-thread
   // and back), and no sooner, do we set mConstraints to the newly applied values.
 
   // Keep a reference to this, to make sure it's still here when we get back.
   RefPtr<MediaStreamTrack> that = this;
--- a/dom/notification/Notification.cpp
+++ b/dom/notification/Notification.cpp
@@ -1788,30 +1788,29 @@ Notification::RequestPermission(const Gl
   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
   nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aGlobal.GetAsSupports());
   if (!sop) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
   nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
 
-  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(window);
-  RefPtr<Promise> promise = Promise::Create(global, aRv);
+  RefPtr<Promise> promise = Promise::Create(window->AsGlobal(), aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
   NotificationPermissionCallback* permissionCallback = nullptr;
   if (aCallback.WasPassed()) {
     permissionCallback = &aCallback.Value();
   }
   bool isHandlingUserInput = EventStateManager::IsHandlingUserInput();
   nsCOMPtr<nsIRunnable> request = new NotificationPermissionRequest(
     principal, isHandlingUserInput, window, promise, permissionCallback);
 
-  global->Dispatch(TaskCategory::Other, request.forget());
+  window->AsGlobal()->Dispatch(TaskCategory::Other, request.forget());
 
   return promise.forget();
 }
 
 // static
 NotificationPermission
 Notification::GetPermission(const GlobalObject& aGlobal, ErrorResult& aRv)
 {
@@ -1986,29 +1985,28 @@ Notification::Get(nsPIDOMWindowInner* aW
   }
 
   nsString origin;
   aRv = GetOrigin(doc->NodePrincipal(), origin);
   if (aRv.Failed()) {
     return nullptr;
   }
 
-  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aWindow);
-  RefPtr<Promise> promise = Promise::Create(global, aRv);
+  RefPtr<Promise> promise = Promise::Create(aWindow->AsGlobal(), aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
   nsCOMPtr<nsINotificationStorageCallback> callback =
-    new NotificationStorageCallback(global, aScope, promise);
+    new NotificationStorageCallback(aWindow->AsGlobal(), aScope, promise);
 
   RefPtr<NotificationGetRunnable> r =
     new NotificationGetRunnable(origin, aFilter.mTag, callback);
 
-  aRv = global->Dispatch(TaskCategory::Other, r.forget());
+  aRv = aWindow->AsGlobal()->Dispatch(TaskCategory::Other, r.forget());
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   return promise.forget();
 }
 
 already_AddRefed<Promise>
--- a/dom/permission/Permissions.cpp
+++ b/dom/permission/Permissions.cpp
@@ -72,31 +72,30 @@ CreatePermissionStatus(JSContext* aCx,
 
 } // namespace
 
 already_AddRefed<Promise>
 Permissions::Query(JSContext* aCx,
                    JS::Handle<JSObject*> aPermission,
                    ErrorResult& aRv)
 {
-  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
-  if (!global) {
+  if (!mWindow) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   RefPtr<PermissionStatus> status =
     CreatePermissionStatus(aCx, aPermission, mWindow, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     MOZ_ASSERT(!status);
     return nullptr;
   }
 
   MOZ_ASSERT(status);
-  RefPtr<Promise> promise = Promise::Create(global, aRv);
+  RefPtr<Promise> promise = Promise::Create(mWindow->AsGlobal(), aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   promise->MaybeResolve(status);
   return promise.forget();
 }
 
@@ -113,30 +112,29 @@ Permissions::RemovePermission(nsIPrincip
   return permMgr->RemoveFromPrincipal(aPrincipal, aPermissionType);
 }
 
 already_AddRefed<Promise>
 Permissions::Revoke(JSContext* aCx,
                     JS::Handle<JSObject*> aPermission,
                     ErrorResult& aRv)
 {
-  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
-  if (!global) {
+  if (!mWindow) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   PermissionDescriptor permission;
   JS::Rooted<JS::Value> value(aCx, JS::ObjectOrNullValue(aPermission));
   if (NS_WARN_IF(!permission.Init(aCx, value))) {
     aRv.NoteJSContextException(aCx);
     return nullptr;
   }
 
-  RefPtr<Promise> promise = Promise::Create(global, aRv);
+  RefPtr<Promise> promise = Promise::Create(mWindow->AsGlobal(), aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   nsCOMPtr<nsIDocument> document = mWindow->GetExtantDoc();
   if (!document) {
     promise->MaybeReject(NS_ERROR_UNEXPECTED);
     return promise.forget();
--- a/dom/serviceworkers/ServiceWorkerManager.cpp
+++ b/dom/serviceworkers/ServiceWorkerManager.cpp
@@ -897,19 +897,18 @@ ServiceWorkerManager::Register(mozIDOMWi
   }
 
   nsAutoCString spec;
   rv = aScriptURI->GetSpecIgnoringRef(spec);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  nsCOMPtr<nsIGlobalObject> sgo = do_QueryInterface(window);
   ErrorResult result;
-  RefPtr<Promise> promise = Promise::Create(sgo, result);
+  RefPtr<Promise> promise = Promise::Create(window->AsGlobal(), result);
   if (result.Failed()) {
     return result.StealNSResult();
   }
 
   nsAutoCString scopeKey;
   rv = PrincipalToScopeKey(documentPrincipal, scopeKey);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -1068,19 +1067,18 @@ ServiceWorkerManager::GetRegistrations(m
                                     "ServiceWorkerGetRegistrationStorageError");
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
   // Don't allow service workers to register when the *document* is chrome for
   // now.
   MOZ_ASSERT(!nsContentUtils::IsSystemPrincipal(window->GetExtantDoc()->NodePrincipal()));
 
-  nsCOMPtr<nsIGlobalObject> sgo = do_QueryInterface(window);
   ErrorResult result;
-  RefPtr<Promise> promise = Promise::Create(sgo, result);
+  RefPtr<Promise> promise = Promise::Create(window->AsGlobal(), result);
   if (result.Failed()) {
     return result.StealNSResult();
   }
 
   nsCOMPtr<nsIRunnable> runnable =
     new GetRegistrationsRunnable(window, promise);
   promise.forget(aPromise);
   return NS_DispatchToCurrentThread(runnable);
@@ -1190,19 +1188,18 @@ ServiceWorkerManager::GetRegistration(mo
                                     "ServiceWorkerGetRegistrationStorageError");
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
   // Don't allow service workers to register when the *document* is chrome for
   // now.
   MOZ_ASSERT(!nsContentUtils::IsSystemPrincipal(window->GetExtantDoc()->NodePrincipal()));
 
-  nsCOMPtr<nsIGlobalObject> sgo = do_QueryInterface(window);
   ErrorResult result;
-  RefPtr<Promise> promise = Promise::Create(sgo, result);
+  RefPtr<Promise> promise = Promise::Create(window->AsGlobal(), result);
   if (result.Failed()) {
     return result.StealNSResult();
   }
 
   nsCOMPtr<nsIRunnable> runnable =
     new GetRegistrationRunnable(window, promise, aDocumentURL);
   promise.forget(aPromise);
   return NS_DispatchToCurrentThread(runnable);
@@ -1392,19 +1389,18 @@ ServiceWorkerManager::GetReadyPromise(mo
   }
 
   // Don't allow service workers to register when the *document* is chrome for
   // now.
   MOZ_ASSERT(!nsContentUtils::IsSystemPrincipal(doc->NodePrincipal()));
 
   MOZ_ASSERT(!mPendingReadyPromises.Contains(window));
 
-  nsCOMPtr<nsIGlobalObject> sgo = do_QueryInterface(window);
   ErrorResult result;
-  RefPtr<Promise> promise = Promise::Create(sgo, result);
+  RefPtr<Promise> promise = Promise::Create(window->AsGlobal(), result);
   if (result.Failed()) {
     return result.StealNSResult();
   }
 
   nsCOMPtr<nsIRunnable> runnable =
     new GetReadyPromiseRunnable(window, promise);
   promise.forget(aPromise);
   return NS_DispatchToCurrentThread(runnable);
--- a/dom/serviceworkers/ServiceWorkerRegistration.cpp
+++ b/dom/serviceworkers/ServiceWorkerRegistration.cpp
@@ -60,22 +60,20 @@ ServiceWorkerRegistration::WrapObject(JS
 
 /* static */ already_AddRefed<ServiceWorkerRegistration>
 ServiceWorkerRegistration::CreateForMainThread(nsPIDOMWindowInner* aWindow,
                                                const ServiceWorkerRegistrationDescriptor& aDescriptor)
 {
   MOZ_ASSERT(aWindow);
   MOZ_ASSERT(NS_IsMainThread());
 
-  nsCOMPtr<nsIGlobalObject> global(do_QueryInterface(aWindow));
-
   RefPtr<Inner> inner = new ServiceWorkerRegistrationMainThread(aDescriptor);
 
   RefPtr<ServiceWorkerRegistration> registration =
-    new ServiceWorkerRegistration(global, aDescriptor, inner);
+    new ServiceWorkerRegistration(aWindow->AsGlobal(), aDescriptor, inner);
 
   return registration.forget();
 }
 
 /* static */ already_AddRefed<ServiceWorkerRegistration>
 ServiceWorkerRegistration::CreateForWorker(WorkerPrivate* aWorkerPrivate,
                                            const ServiceWorkerRegistrationDescriptor& aDescriptor)
 {
--- a/dom/serviceworkers/ServiceWorkerRegistrationImpl.cpp
+++ b/dom/serviceworkers/ServiceWorkerRegistrationImpl.cpp
@@ -512,17 +512,17 @@ public:
   }
 };
 } // namespace
 
 already_AddRefed<Promise>
 ServiceWorkerRegistrationMainThread::Update(ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mOuter->GetOwner());
+  nsCOMPtr<nsIGlobalObject> go = mOuter->GetParentObject();
   if (!go) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   RefPtr<Promise> promise = Promise::Create(go, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
@@ -537,17 +537,17 @@ ServiceWorkerRegistrationMainThread::Upd
 
   return promise.forget();
 }
 
 already_AddRefed<Promise>
 ServiceWorkerRegistrationMainThread::Unregister(ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mOuter->GetOwner());
+  nsCOMPtr<nsIGlobalObject> go = mOuter->GetParentObject();
   if (!go) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   // Although the spec says that the same-origin checks should also be done
   // asynchronously, we do them in sync because the Promise created by the
   // WebIDL infrastructure due to a returned error will be resolved
@@ -622,20 +622,19 @@ ServiceWorkerRegistrationMainThread::Sho
   }
 
   RefPtr<ServiceWorker> worker = mOuter->GetActive();
   if (!worker) {
     aRv.ThrowTypeError<MSG_NO_ACTIVE_WORKER>(mScope);
     return nullptr;
   }
 
-  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(window);
   RefPtr<Promise> p =
-    Notification::ShowPersistentNotification(aCx, global, mScope, aTitle,
-                                             aOptions, aRv);
+    Notification::ShowPersistentNotification(aCx, window->AsGlobal(), mScope,
+                                             aTitle, aOptions, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   return p.forget();
 }
 
 already_AddRefed<Promise>
@@ -651,17 +650,17 @@ ServiceWorkerRegistrationMainThread::Get
 }
 
 already_AddRefed<PushManager>
 ServiceWorkerRegistrationMainThread::GetPushManager(JSContext* aCx,
                                                     ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  nsCOMPtr<nsIGlobalObject> globalObject = do_QueryInterface(mOuter->GetOwner());
+  nsCOMPtr<nsIGlobalObject> globalObject = mOuter->GetParentObject();
 
   if (!globalObject) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   GlobalObject global(aCx, globalObject->GetGlobalJSObject());
   RefPtr<PushManager> ret = PushManager::Constructor(global, mScope, aRv);
--- a/dom/vr/VRServiceTest.cpp
+++ b/dom/vr/VRServiceTest.cpp
@@ -310,19 +310,17 @@ VRServiceTest::Shutdown()
 
 already_AddRefed<Promise>
 VRServiceTest::AttachVRDisplay(const nsAString& aID, ErrorResult& aRv)
 {
   if (mShuttingDown) {
     return nullptr;
   }
 
-  nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
-
-  RefPtr<Promise> p = Promise::Create(go, aRv);
+  RefPtr<Promise> p = Promise::Create(mWindow->AsGlobal(), aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
   vm->CreateVRServiceTestDisplay(NS_ConvertUTF16toUTF8(aID), p);
 
   return p.forget();
@@ -330,19 +328,17 @@ VRServiceTest::AttachVRDisplay(const nsA
 
 already_AddRefed<Promise>
 VRServiceTest::AttachVRController(const nsAString& aID, ErrorResult& aRv)
 {
   if (mShuttingDown) {
     return nullptr;
   }
 
-  nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
-
-  RefPtr<Promise> p = Promise::Create(go, aRv);
+  RefPtr<Promise> p = Promise::Create(mWindow->AsGlobal(), aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
   vm->CreateVRServiceTestController(NS_ConvertUTF16toUTF8(aID), p);
 
   return p.forget();
--- a/dom/webauthn/U2FHIDTokenManager.cpp
+++ b/dom/webauthn/U2FHIDTokenManager.cpp
@@ -100,48 +100,46 @@ U2FHIDTokenManager::Drop()
 // 1      0x05
 // 65     public key
 // 1      key handle length
 // *      key handle
 // ASN.1  attestation certificate
 // *      attestation signature
 //
 RefPtr<U2FRegisterPromise>
-U2FHIDTokenManager::Register(const nsTArray<WebAuthnScopedCredential>& aCredentials,
-                             const WebAuthnAuthenticatorSelection &aAuthenticatorSelection,
-                             const nsTArray<uint8_t>& aApplication,
-                             const nsTArray<uint8_t>& aChallenge,
-                             uint32_t aTimeoutMS)
+U2FHIDTokenManager::Register(const WebAuthnMakeCredentialInfo& aInfo)
 {
   mozilla::ipc::AssertIsOnBackgroundThread();
 
   uint64_t registerFlags = 0;
 
+  const WebAuthnAuthenticatorSelection& sel = aInfo.AuthenticatorSelection();
+
   // Set flags for credential creation.
-  if (aAuthenticatorSelection.requireResidentKey()) {
+  if (sel.requireResidentKey()) {
     registerFlags |= U2F_FLAG_REQUIRE_RESIDENT_KEY;
   }
-  if (aAuthenticatorSelection.requireUserVerification()) {
+  if (sel.requireUserVerification()) {
     registerFlags |= U2F_FLAG_REQUIRE_USER_VERIFICATION;
   }
-  if (aAuthenticatorSelection.requirePlatformAttachment()) {
+  if (sel.requirePlatformAttachment()) {
     registerFlags |= U2F_FLAG_REQUIRE_PLATFORM_ATTACHMENT;
   }
 
   ClearPromises();
-  mCurrentAppId = aApplication;
+  mCurrentAppId = aInfo.RpIdHash();
   mTransactionId = rust_u2f_mgr_register(mU2FManager,
                                          registerFlags,
-                                         (uint64_t)aTimeoutMS,
+                                         (uint64_t)aInfo.TimeoutMS(),
                                          u2f_register_callback,
-                                         aChallenge.Elements(),
-                                         aChallenge.Length(),
-                                         aApplication.Elements(),
-                                         aApplication.Length(),
-                                         U2FKeyHandles(aCredentials).Get());
+                                         aInfo.ClientDataHash().Elements(),
+                                         aInfo.ClientDataHash().Length(),
+                                         aInfo.RpIdHash().Elements(),
+                                         aInfo.RpIdHash().Length(),
+                                         U2FKeyHandles(aInfo.ExcludeList()).Get());
 
   if (mTransactionId == 0) {
     return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
   }
 
   return mRegisterPromise.Ensure(__func__);
 }
 
@@ -157,52 +155,47 @@ U2FHIDTokenManager::Register(const nsTAr
 //
 // The format of the signature data is as follows:
 //
 //  1     User presence
 //  4     Counter
 //  *     Signature
 //
 RefPtr<U2FSignPromise>
-U2FHIDTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
-                         const nsTArray<uint8_t>& aApplication,
-                         const nsTArray<uint8_t>& aChallenge,
-                         const nsTArray<WebAuthnExtension>& aExtensions,
-                         bool aRequireUserVerification,
-                         uint32_t aTimeoutMS)
+U2FHIDTokenManager::Sign(const WebAuthnGetAssertionInfo& aInfo)
 {
   mozilla::ipc::AssertIsOnBackgroundThread();
 
   uint64_t signFlags = 0;
 
   // Set flags for credential requests.
-  if (aRequireUserVerification) {
+  if (aInfo.RequireUserVerification()) {
     signFlags |= U2F_FLAG_REQUIRE_USER_VERIFICATION;
   }
 
+  mCurrentAppId = aInfo.RpIdHash();
   nsTArray<nsTArray<uint8_t>> appIds;
-  appIds.AppendElement(aApplication);
+  appIds.AppendElement(mCurrentAppId);
 
   // Process extensions.
-  for (const WebAuthnExtension& ext: aExtensions) {
+  for (const WebAuthnExtension& ext: aInfo.Extensions()) {
     if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) {
       appIds.AppendElement(ext.get_WebAuthnExtensionAppId().AppId());
     }
   }
 
   ClearPromises();
-  mCurrentAppId = aApplication;
   mTransactionId = rust_u2f_mgr_sign(mU2FManager,
                                      signFlags,
-                                     (uint64_t)aTimeoutMS,
+                                     (uint64_t)aInfo.TimeoutMS(),
                                      u2f_sign_callback,
-                                     aChallenge.Elements(),
-                                     aChallenge.Length(),
+                                     aInfo.ClientDataHash().Elements(),
+                                     aInfo.ClientDataHash().Length(),
                                      U2FAppIds(appIds).Get(),
-                                     U2FKeyHandles(aCredentials).Get());
+                                     U2FKeyHandles(aInfo.AllowList()).Get());
   if (mTransactionId == 0) {
     return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
   }
 
   return mSignPromise.Ensure(__func__);
 }
 
 void
--- a/dom/webauthn/U2FHIDTokenManager.h
+++ b/dom/webauthn/U2FHIDTokenManager.h
@@ -113,29 +113,20 @@ private:
 };
 
 class U2FHIDTokenManager final : public U2FTokenTransport
 {
 public:
   explicit U2FHIDTokenManager();
 
   RefPtr<U2FRegisterPromise>
-  Register(const nsTArray<WebAuthnScopedCredential>& aCredentials,
-           const WebAuthnAuthenticatorSelection &aAuthenticatorSelection,
-           const nsTArray<uint8_t>& aApplication,
-           const nsTArray<uint8_t>& aChallenge,
-           uint32_t aTimeoutMS) override;
+  Register(const WebAuthnMakeCredentialInfo& aInfo) override;
 
   RefPtr<U2FSignPromise>
-  Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
-       const nsTArray<uint8_t>& aApplication,
-       const nsTArray<uint8_t>& aChallenge,
-       const nsTArray<WebAuthnExtension>& aExtensions,
-       bool aRequireUserVerification,
-       uint32_t aTimeoutMS) override;
+  Sign(const WebAuthnGetAssertionInfo& aInfo) override;
 
   void Cancel() override;
   void Drop() override;
 
   void HandleRegisterResult(UniquePtr<U2FResult>&& aResult);
   void HandleSignResult(UniquePtr<U2FResult>&& aResult);
 
 private:
--- a/dom/webauthn/U2FSoftTokenManager.cpp
+++ b/dom/webauthn/U2FSoftTokenManager.cpp
@@ -578,41 +578,39 @@ U2FSoftTokenManager::IsRegistered(const 
 // 1      0x05
 // 65     public key
 // 1      key handle length
 // *      key handle
 // ASN.1  attestation certificate
 // *      attestation signature
 //
 RefPtr<U2FRegisterPromise>
-U2FSoftTokenManager::Register(const nsTArray<WebAuthnScopedCredential>& aCredentials,
-                              const WebAuthnAuthenticatorSelection &aAuthenticatorSelection,
-                              const nsTArray<uint8_t>& aApplication,
-                              const nsTArray<uint8_t>& aChallenge,
-                              uint32_t aTimeoutMS)
+U2FSoftTokenManager::Register(const WebAuthnMakeCredentialInfo& aInfo)
 {
   if (!mInitialized) {
     nsresult rv = Init();
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return U2FRegisterPromise::CreateAndReject(rv, __func__);
     }
   }
 
+  const WebAuthnAuthenticatorSelection& sel = aInfo.AuthenticatorSelection();
+
   // The U2F softtoken neither supports resident keys or
   // user verification, nor is it a platform authenticator.
-  if (aAuthenticatorSelection.requireResidentKey() ||
-      aAuthenticatorSelection.requireUserVerification() ||
-      aAuthenticatorSelection.requirePlatformAttachment()) {
+  if (sel.requireResidentKey() ||
+      sel.requireUserVerification() ||
+      sel.requirePlatformAttachment()) {
     return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
   }
 
   // Optional exclusion list.
-  for (auto cred: aCredentials) {
+  for (const WebAuthnScopedCredential& cred: aInfo.ExcludeList()) {
     bool isRegistered = false;
-    nsresult rv = IsRegistered(cred.id(), aApplication, isRegistered);
+    nsresult rv = IsRegistered(cred.id(), aInfo.RpIdHash(), isRegistered);
     if (NS_FAILED(rv)) {
       return U2FRegisterPromise::CreateAndReject(rv, __func__);
     }
     if (isRegistered) {
       return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
     }
   }
 
@@ -636,37 +634,38 @@ U2FSoftTokenManager::Register(const nsTA
   UniqueSECKEYPrivateKey privKey;
   UniqueSECKEYPublicKey pubKey;
   rv = GenEcKeypair(slot, privKey, pubKey);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return U2FRegisterPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   }
 
   // The key handle will be the result of keywrap(privKey, key=mWrappingKey)
-  UniqueSECItem keyHandleItem = KeyHandleFromPrivateKey(slot, mWrappingKey,
-                                                        const_cast<uint8_t*>(aApplication.Elements()),
-                                                        aApplication.Length(),
-                                                        privKey);
+  UniqueSECItem keyHandleItem =
+    KeyHandleFromPrivateKey(slot, mWrappingKey,
+                            const_cast<uint8_t*>(aInfo.RpIdHash().Elements()),
+                            aInfo.RpIdHash().Length(), privKey);
   if (NS_WARN_IF(!keyHandleItem.get())) {
     return U2FRegisterPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   }
 
   // Sign the challenge using the Attestation privkey (from attestCert)
   mozilla::dom::CryptoBuffer signedDataBuf;
-  if (NS_WARN_IF(!signedDataBuf.SetCapacity(1 + aApplication.Length() + aChallenge.Length() +
+  if (NS_WARN_IF(!signedDataBuf.SetCapacity(1 + aInfo.RpIdHash().Length() +
+                                            aInfo.ClientDataHash().Length() +
                                             keyHandleItem->len + kPublicKeyLen,
                                             mozilla::fallible))) {
     return U2FRegisterPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__);
   }
 
   // // It's OK to ignore the return values here because we're writing into
   // // pre-allocated space
   signedDataBuf.AppendElement(0x00, mozilla::fallible);
-  signedDataBuf.AppendElements(aApplication, mozilla::fallible);
-  signedDataBuf.AppendElements(aChallenge, mozilla::fallible);
+  signedDataBuf.AppendElements(aInfo.RpIdHash(), mozilla::fallible);
+  signedDataBuf.AppendElements(aInfo.ClientDataHash(), mozilla::fallible);
   signedDataBuf.AppendSECItem(keyHandleItem.get());
   signedDataBuf.AppendSECItem(pubKey->u.ec.publicValue);
 
   ScopedAutoSECItem signatureItem;
   SECStatus srv = SEC_SignData(&signatureItem, signedDataBuf.Elements(),
                                signedDataBuf.Length(), attestPrivKey.get(),
                                SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE);
   if (NS_WARN_IF(srv != SECSuccess)) {
@@ -726,72 +725,70 @@ U2FSoftTokenManager::FindRegisteredKeyHa
 //
 // The format of the signature data is as follows:
 //
 //  1     User presence
 //  4     Counter
 //  *     Signature
 //
 RefPtr<U2FSignPromise>
-U2FSoftTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
-                          const nsTArray<uint8_t>& aApplication,
-                          const nsTArray<uint8_t>& aChallenge,
-                          const nsTArray<WebAuthnExtension>& aExtensions,
-                          bool aRequireUserVerification,
-                          uint32_t aTimeoutMS)
+U2FSoftTokenManager::Sign(const WebAuthnGetAssertionInfo& aInfo)
 {
   if (!mInitialized) {
     nsresult rv = Init();
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return U2FSignPromise::CreateAndReject(rv, __func__);
     }
   }
 
   // The U2F softtoken doesn't support user verification.
-  if (aRequireUserVerification) {
+  if (aInfo.RequireUserVerification()) {
     return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
   }
 
   nsTArray<nsTArray<uint8_t>> appIds;
-  appIds.AppendElement(aApplication);
+  appIds.AppendElement(aInfo.RpIdHash());
 
   // Process extensions.
-  for (const WebAuthnExtension& ext: aExtensions) {
+  for (const WebAuthnExtension& ext: aInfo.Extensions()) {
     if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) {
       appIds.AppendElement(ext.get_WebAuthnExtensionAppId().AppId());
     }
   }
 
-  nsTArray<uint8_t> chosenAppId(aApplication);
+  nsTArray<uint8_t> chosenAppId;
   nsTArray<uint8_t> keyHandle;
 
   // Fail if we can't find a valid key handle.
-  if (!FindRegisteredKeyHandle(appIds, aCredentials, keyHandle, chosenAppId)) {
+  if (!FindRegisteredKeyHandle(appIds, aInfo.AllowList(), keyHandle, chosenAppId)) {
     return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
   }
 
   MOZ_ASSERT(mWrappingKey);
 
   UniquePK11SlotInfo slot(PK11_GetInternalSlot());
   MOZ_ASSERT(slot.get());
 
-  if (NS_WARN_IF((aChallenge.Length() != kParamLen) || (chosenAppId.Length() != kParamLen))) {
+  if (NS_WARN_IF((aInfo.ClientDataHash().Length() != kParamLen) ||
+                 (chosenAppId.Length() != kParamLen))) {
     MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
             ("Parameter lengths are wrong! challenge=%d app=%d expected=%d",
-             (uint32_t)aChallenge.Length(), (uint32_t)chosenAppId.Length(), kParamLen));
+             (uint32_t)aInfo.ClientDataHash().Length(),
+             (uint32_t)chosenAppId.Length(), kParamLen));
 
     return U2FSignPromise::CreateAndReject(NS_ERROR_ILLEGAL_VALUE, __func__);
   }
 
   // Decode the key handle
-  UniqueSECKEYPrivateKey privKey = PrivateKeyFromKeyHandle(slot, mWrappingKey,
-                                                           const_cast<uint8_t*>(keyHandle.Elements()),
-                                                           keyHandle.Length(),
-                                                           const_cast<uint8_t*>(chosenAppId.Elements()),
-                                                           chosenAppId.Length());
+  UniqueSECKEYPrivateKey privKey =
+    PrivateKeyFromKeyHandle(slot, mWrappingKey,
+                            const_cast<uint8_t*>(keyHandle.Elements()),
+                            keyHandle.Length(),
+                            const_cast<uint8_t*>(chosenAppId.Elements()),
+                            chosenAppId.Length());
   if (NS_WARN_IF(!privKey.get())) {
     MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Couldn't get the priv key!"));
     return U2FSignPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   }
 
   // Increment the counter and turn it into a SECItem
   mCounter += 1;
   ScopedAutoSECItem counterItem(4);
@@ -810,21 +807,23 @@ U2FSoftTokenManager::Sign(const nsTArray
   // Compute the signature
   mozilla::dom::CryptoBuffer signedDataBuf;
   if (NS_WARN_IF(!signedDataBuf.SetCapacity(1 + 4 + (2 * kParamLen), mozilla::fallible))) {
     return U2FSignPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__);
   }
 
   // It's OK to ignore the return values here because we're writing into
   // pre-allocated space
-  signedDataBuf.AppendElements(chosenAppId.Elements(), chosenAppId.Length(),
+  signedDataBuf.AppendElements(chosenAppId.Elements(),
+                               chosenAppId.Length(),
                                mozilla::fallible);
   signedDataBuf.AppendElement(0x01, mozilla::fallible);
   signedDataBuf.AppendSECItem(counterItem);
-  signedDataBuf.AppendElements(aChallenge.Elements(), aChallenge.Length(),
+  signedDataBuf.AppendElements(aInfo.ClientDataHash().Elements(),
+                               aInfo.ClientDataHash().Length(),
                                mozilla::fallible);
 
   if (MOZ_LOG_TEST(gNSSTokenLog, LogLevel::Debug)) {
     nsAutoCString base64;
     nsresult rv = Base64URLEncode(signedDataBuf.Length(), signedDataBuf.Elements(),
                                   Base64URLEncodePaddingPolicy::Omit, base64);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return U2FSignPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
@@ -855,17 +854,17 @@ U2FSoftTokenManager::Sign(const nsTArray
   // pre-allocated space
   signatureBuf.AppendElement(0x01, mozilla::fallible);
   signatureBuf.AppendSECItem(counterItem);
   signatureBuf.AppendSECItem(signatureItem);
 
   nsTArray<uint8_t> signature(signatureBuf);
   nsTArray<WebAuthnExtensionResult> extensions;
 
-  if (chosenAppId != aApplication) {
+  if (chosenAppId != aInfo.RpIdHash()) {
     // Indicate to the RP that we used the FIDO appId.
     extensions.AppendElement(WebAuthnExtensionResultAppId(true));
   }
 
   WebAuthnGetAssertionResult result(chosenAppId, keyHandle, signature, extensions);
   return U2FSignPromise::CreateAndResolve(Move(result), __func__);
 }
 
--- a/dom/webauthn/U2FSoftTokenManager.h
+++ b/dom/webauthn/U2FSoftTokenManager.h
@@ -19,29 +19,20 @@ namespace mozilla {
 namespace dom {
 
 class U2FSoftTokenManager final : public U2FTokenTransport
 {
 public:
   explicit U2FSoftTokenManager(uint32_t aCounter);
 
   RefPtr<U2FRegisterPromise>
-  Register(const nsTArray<WebAuthnScopedCredential>& aCredentials,
-           const WebAuthnAuthenticatorSelection &aAuthenticatorSelection,
-           const nsTArray<uint8_t>& aApplication,
-           const nsTArray<uint8_t>& aChallenge,
-           uint32_t aTimeoutMS) override;
+  Register(const WebAuthnMakeCredentialInfo& aInfo) override;
 
   RefPtr<U2FSignPromise>
-  Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
-       const nsTArray<uint8_t>& aApplication,
-       const nsTArray<uint8_t>& aChallenge,
-       const nsTArray<WebAuthnExtension>& aExtensions,
-       bool aRequireUserVerification,
-       uint32_t aTimeoutMS) override;
+  Sign(const WebAuthnGetAssertionInfo& aInfo) override;
 
   void Cancel() override;
 
 private:
   ~U2FSoftTokenManager() {}
   nsresult Init();
 
   nsresult IsRegistered(const nsTArray<uint8_t>& aKeyHandle,
--- a/dom/webauthn/U2FTokenManager.cpp
+++ b/dom/webauthn/U2FTokenManager.cpp
@@ -243,41 +243,38 @@ U2FTokenManager::Register(PWebAuthnTrans
   if ((aTransactionInfo.RpIdHash().Length() != SHA256_LENGTH) ||
       (aTransactionInfo.ClientDataHash().Length() != SHA256_LENGTH)) {
     AbortTransaction(aTransactionId, NS_ERROR_DOM_UNKNOWN_ERR);
     return;
   }
 
   uint64_t tid = mLastTransactionId = aTransactionId;
   mozilla::TimeStamp startTime = mozilla::TimeStamp::Now();
-  mTokenManagerImpl->Register(aTransactionInfo.ExcludeList(),
-                              aTransactionInfo.AuthenticatorSelection(),
-                              aTransactionInfo.RpIdHash(),
-                              aTransactionInfo.ClientDataHash(),
-                              aTransactionInfo.TimeoutMS())
-                   ->Then(GetCurrentThreadSerialEventTarget(), __func__,
-                         [tid, startTime](WebAuthnMakeCredentialResult&& aResult) {
-                           U2FTokenManager* mgr = U2FTokenManager::Get();
-                           mgr->MaybeConfirmRegister(tid, aResult);
-                           Telemetry::ScalarAdd(
-                             Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
-                             NS_LITERAL_STRING("U2FRegisterFinish"), 1);
-                           Telemetry::AccumulateTimeDelta(
-                             Telemetry::WEBAUTHN_CREATE_CREDENTIAL_MS,
-                             startTime);
-                         },
-                         [tid](nsresult rv) {
-                           MOZ_ASSERT(NS_FAILED(rv));
-                           U2FTokenManager* mgr = U2FTokenManager::Get();
-                           mgr->MaybeAbortRegister(tid, rv);
-                           Telemetry::ScalarAdd(
-                             Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
-                             NS_LITERAL_STRING("U2FRegisterAbort"), 1);
-                         })
-                   ->Track(mRegisterPromise);
+  mTokenManagerImpl
+    ->Register(aTransactionInfo)
+    ->Then(GetCurrentThreadSerialEventTarget(), __func__,
+          [tid, startTime](WebAuthnMakeCredentialResult&& aResult) {
+            U2FTokenManager* mgr = U2FTokenManager::Get();
+            mgr->MaybeConfirmRegister(tid, aResult);
+            Telemetry::ScalarAdd(
+              Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
+              NS_LITERAL_STRING("U2FRegisterFinish"), 1);
+            Telemetry::AccumulateTimeDelta(
+              Telemetry::WEBAUTHN_CREATE_CREDENTIAL_MS,
+              startTime);
+          },
+          [tid](nsresult rv) {
+            MOZ_ASSERT(NS_FAILED(rv));
+            U2FTokenManager* mgr = U2FTokenManager::Get();
+            mgr->MaybeAbortRegister(tid, rv);
+            Telemetry::ScalarAdd(
+              Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
+              NS_LITERAL_STRING("U2FRegisterAbort"), 1);
+          })
+    ->Track(mRegisterPromise);
 }
 
 void
 U2FTokenManager::MaybeConfirmRegister(const uint64_t& aTransactionId,
                                       const WebAuthnMakeCredentialResult& aResult)
 {
   MOZ_ASSERT(mLastTransactionId == aTransactionId);
   mRegisterPromise.Complete();
@@ -314,42 +311,38 @@ U2FTokenManager::Sign(PWebAuthnTransacti
   if ((aTransactionInfo.RpIdHash().Length() != SHA256_LENGTH) ||
       (aTransactionInfo.ClientDataHash().Length() != SHA256_LENGTH)) {
     AbortTransaction(aTransactionId, NS_ERROR_DOM_UNKNOWN_ERR);
     return;
   }
 
   uint64_t tid = mLastTransactionId = aTransactionId;
   mozilla::TimeStamp startTime = mozilla::TimeStamp::Now();
-  mTokenManagerImpl->Sign(aTransactionInfo.AllowList(),
-                          aTransactionInfo.RpIdHash(),
-                          aTransactionInfo.ClientDataHash(),
-                          aTransactionInfo.Extensions(),
-                          aTransactionInfo.RequireUserVerification(),
-                          aTransactionInfo.TimeoutMS())
-                   ->Then(GetCurrentThreadSerialEventTarget(), __func__,
-                     [tid, startTime](WebAuthnGetAssertionResult&& aResult) {
-                       U2FTokenManager* mgr = U2FTokenManager::Get();
-                       mgr->MaybeConfirmSign(tid, aResult);
-                       Telemetry::ScalarAdd(
-                         Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
-                         NS_LITERAL_STRING("U2FSignFinish"), 1);
-                       Telemetry::AccumulateTimeDelta(
-                         Telemetry::WEBAUTHN_GET_ASSERTION_MS,
-                         startTime);
-                     },
-                     [tid](nsresult rv) {
-                       MOZ_ASSERT(NS_FAILED(rv));
-                       U2FTokenManager* mgr = U2FTokenManager::Get();
-                       mgr->MaybeAbortSign(tid, rv);
-                       Telemetry::ScalarAdd(
-                         Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
-                         NS_LITERAL_STRING("U2FSignAbort"), 1);
-                     })
-                   ->Track(mSignPromise);
+  mTokenManagerImpl
+    ->Sign(aTransactionInfo)
+    ->Then(GetCurrentThreadSerialEventTarget(), __func__,
+      [tid, startTime](WebAuthnGetAssertionResult&& aResult) {
+        U2FTokenManager* mgr = U2FTokenManager::Get();
+        mgr->MaybeConfirmSign(tid, aResult);
+        Telemetry::ScalarAdd(
+          Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
+          NS_LITERAL_STRING("U2FSignFinish"), 1);
+        Telemetry::AccumulateTimeDelta(
+          Telemetry::WEBAUTHN_GET_ASSERTION_MS,
+          startTime);
+      },
+      [tid](nsresult rv) {
+        MOZ_ASSERT(NS_FAILED(rv));
+        U2FTokenManager* mgr = U2FTokenManager::Get();
+        mgr->MaybeAbortSign(tid, rv);
+        Telemetry::ScalarAdd(
+          Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
+          NS_LITERAL_STRING("U2FSignAbort"), 1);
+      })
+    ->Track(mSignPromise);
 }
 
 void
 U2FTokenManager::MaybeConfirmSign(const uint64_t& aTransactionId,
                                   const WebAuthnGetAssertionResult& aResult)
 {
   MOZ_ASSERT(mLastTransactionId == aTransactionId);
   mSignPromise.Complete();
--- a/dom/webauthn/U2FTokenTransport.h
+++ b/dom/webauthn/U2FTokenTransport.h
@@ -23,29 +23,20 @@ typedef MozPromise<WebAuthnGetAssertionR
 
 class U2FTokenTransport
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(U2FTokenTransport);
   U2FTokenTransport() {}
 
   virtual RefPtr<U2FRegisterPromise>
-  Register(const nsTArray<WebAuthnScopedCredential>& aCredentials,
-           const WebAuthnAuthenticatorSelection &aAuthenticatorSelection,
-           const nsTArray<uint8_t>& aApplication,
-           const nsTArray<uint8_t>& aChallenge,
-           uint32_t aTimeoutMS) = 0;
+  Register(const WebAuthnMakeCredentialInfo& aInfo) = 0;
 
   virtual RefPtr<U2FSignPromise>
-  Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
-       const nsTArray<uint8_t>& aApplication,
-       const nsTArray<uint8_t>& aChallenge,
-       const nsTArray<WebAuthnExtension>& aExtensions,
-       bool aRequireUserVerification,
-       uint32_t aTimeoutMS) = 0;
+  Sign(const WebAuthnGetAssertionInfo& aInfo) = 0;
 
   virtual void Cancel() = 0;
 
   virtual void Drop() { }
 
 protected:
   virtual ~U2FTokenTransport() = default;
 };
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -377,16 +377,22 @@ partial interface Document {
   // Blocks the initial document parser until the given promise is settled.
   [ChromeOnly, Throws]
   Promise<any> blockParsing(Promise<any> promise,
                             optional BlockParsingOptions options);
 
   // like documentURI, except that for error pages, it returns the URI we were
   // trying to load when we hit an error, rather than the error page's own URI.
   [ChromeOnly] readonly attribute URI? mozDocumentURIIfNotForErrorPages;
+
+  // A promise that is resolved, with this document itself, when we have both
+  // fired DOMContentLoaded and are ready to start layout.  This is used for the
+  // "document_idle" webextension script injection point.
+  [ChromeOnly, Throws]
+  readonly attribute Promise<Document> documentReadyForIdle;
 };
 
 dictionary BlockParsingOptions {
   /**
    * If true, blocks script-created parsers (created via document.open()) in
    * addition to network-created parsers.
    */
   boolean blockScriptCreated = true;
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -2388,18 +2388,18 @@ RuntimeService::CreateSharedWorkerFromLo
     // load group.
     if (shouldAttachToWorkerPrivate) {
       workerPrivate->UpdateOverridenLoadGroup(aLoadInfo->mLoadGroup);
     }
   }
 
   // We don't actually care about this MessageChannel, but we use it to 'steal'
   // its 2 connected ports.
-  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(window);
-  RefPtr<MessageChannel> channel = MessageChannel::Constructor(global, rv);
+  RefPtr<MessageChannel> channel =
+    MessageChannel::Constructor(window->AsGlobal(), rv);
   if (NS_WARN_IF(rv.Failed())) {
     return rv.StealNSResult();
   }
 
   RefPtr<SharedWorker> sharedWorker = new SharedWorker(window, workerPrivate,
                                                        channel->Port1());
 
   if (!shouldAttachToWorkerPrivate) {
--- a/dom/xhr/XMLHttpRequestWorker.cpp
+++ b/dom/xhr/XMLHttpRequestWorker.cpp
@@ -856,20 +856,19 @@ Proxy::Init()
   }
 
   nsPIDOMWindowInner* ownerWindow = mWorkerPrivate->GetWindow();
   if (ownerWindow && !ownerWindow->IsCurrentInnerWindow()) {
     NS_WARNING("Window has navigated, cannot create XHR here.");
     return false;
   }
 
-  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(ownerWindow);
-
   mXHR = new XMLHttpRequestMainThread();
-  mXHR->Construct(mWorkerPrivate->GetPrincipal(), global,
+  mXHR->Construct(mWorkerPrivate->GetPrincipal(),
+                  ownerWindow ? ownerWindow->AsGlobal() : nullptr,
                   mWorkerPrivate->GetBaseURI(),
                   mWorkerPrivate->GetLoadGroup(),
                   mWorkerPrivate->GetPerformanceStorage());
 
   mXHR->SetParameters(mMozAnon, mMozSystem);
   mXHR->SetClientInfoAndController(mClientInfo, mController);
 
   ErrorResult rv;
--- a/gfx/2d/2D.h
+++ b/gfx/2d/2D.h
@@ -555,17 +555,18 @@ public:
    */
   virtual already_AddRefed<DataSourceSurface> GetDataSurface() override;
 
   /**
    * Add the size of the underlying data buffer to the aggregate.
    */
   virtual void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
                                       size_t& aHeapSizeOut,
-                                      size_t& aNonHeapSizeOut) const
+                                      size_t& aNonHeapSizeOut,
+                                      size_t& aExtHandlesOut) const
   {
   }
 
   /**
    * Returns whether or not the data was allocated on the heap. This should
    * be used to determine if the memory needs to be cleared to 0.
    */
   virtual bool OnHeap() const
--- a/gfx/2d/SourceSurfaceRawData.cpp
+++ b/gfx/2d/SourceSurfaceRawData.cpp
@@ -35,16 +35,17 @@ SourceSurfaceRawData::InitWrappingData(u
 
 void
 SourceSurfaceRawData::GuaranteePersistance()
 {
   if (mOwnData) {
     return;
   }
 
+  MOZ_ASSERT(!mDeallocator);
   uint8_t* oldData = mRawData;
   mRawData = new uint8_t[mStride * mSize.height];
 
   memcpy(mRawData, oldData, mStride * mSize.height);
   mOwnData = true;
 }
 
 bool
@@ -73,10 +74,19 @@ SourceSurfaceAlignedRawData::Init(const 
   } else {
     mArray.Dealloc();
     mSize.SizeTo(0, 0);
   }
 
   return mArray != nullptr;
 }
 
+void
+SourceSurfaceAlignedRawData::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+                                                    size_t& aHeapSizeOut,
+                                                    size_t& aNonHeapSizeOut,
+                                                    size_t& aExtHandlesOut) const
+{
+  aHeapSizeOut += mArray.HeapSizeOfExcludingThis(aMallocSizeOf);
+}
+
 } // namespace gfx
 } // namespace mozilla
--- a/gfx/2d/SourceSurfaceRawData.h
+++ b/gfx/2d/SourceSurfaceRawData.h
@@ -110,23 +110,34 @@ public:
     , mFormat(SurfaceFormat::UNKNOWN)
     , mMapCount(0)
   {}
   ~SourceSurfaceAlignedRawData()
   {
     MOZ_ASSERT(mMapCount == 0);
   }
 
+  bool Init(const IntSize &aSize,
+            SurfaceFormat aFormat,
+            bool aClearMem,
+            uint8_t aClearValue,
+            int32_t aStride = 0);
+
   virtual uint8_t* GetData() override { return mArray; }
   virtual int32_t Stride() override { return mStride; }
 
   virtual SurfaceType GetType() const override { return SurfaceType::DATA; }
   virtual IntSize GetSize() const override { return mSize; }
   virtual SurfaceFormat GetFormat() const override { return mFormat; }
 
+  void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+                              size_t& aHeapSizeOut,
+                              size_t& aNonHeapSizeOut,
+                              size_t& aExtHandlesOut) const override;
+
   virtual bool Map(MapType, MappedSurface *aMappedSurface) override
   {
     aMappedSurface->mData = GetData();
     aMappedSurface->mStride = Stride();
     bool success = !!aMappedSurface->mData;
     if (success) {
       mMapCount++;
     }
@@ -137,22 +148,16 @@ public:
   {
     mMapCount--;
     MOZ_ASSERT(mMapCount >= 0);
   }
 
 private:
   friend class Factory;
 
-  bool Init(const IntSize &aSize,
-            SurfaceFormat aFormat,
-            bool aClearMem,
-            uint8_t aClearValue,
-            int32_t aStride = 0);
-
   AlignedArray<uint8_t> mArray;
   int32_t mStride;
   SurfaceFormat mFormat;
   IntSize mSize;
   Atomic<int32_t> mMapCount;
 };
 
 } // namespace gfx
--- a/gfx/2d/Tools.h
+++ b/gfx/2d/Tools.h
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MOZILLA_GFX_TOOLS_H_
 #define MOZILLA_GFX_TOOLS_H_
 
 #include "mozilla/CheckedInt.h"
+#include "mozilla/MemoryReporting.h" // for MallocSizeOf
 #include "mozilla/Move.h"
 #include "mozilla/TypeTraits.h"
 #include "Types.h"
 #include "Point.h"
 #include <math.h>
 
 namespace mozilla {
 namespace gfx {
@@ -225,16 +226,22 @@ struct AlignedArray
 
   void Swap(AlignedArray<T, alignment>& aOther)
   {
     mozilla::Swap(mPtr, aOther.mPtr);
     mozilla::Swap(mStorage, aOther.mStorage);
     mozilla::Swap(mCount, aOther.mCount);
   }
 
+  size_t
+  HeapSizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    return aMallocSizeOf(mStorage);
+  }
+
   MOZ_ALWAYS_INLINE operator T*()
   {
     return mPtr;
   }
 
   T *mPtr;
 
 private:
--- a/gfx/layers/SourceSurfaceSharedData.cpp
+++ b/gfx/layers/SourceSurfaceSharedData.cpp
@@ -89,21 +89,26 @@ void
 SourceSurfaceSharedData::GuaranteePersistance()
 {
   // Shared memory is not unmapped until we release SourceSurfaceSharedData.
 }
 
 void
 SourceSurfaceSharedData::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
                                                 size_t& aHeapSizeOut,
-                                                size_t& aNonHeapSizeOut) const
+                                                size_t& aNonHeapSizeOut,
+                                                size_t& aExtHandlesOut) const
 {
+  MutexAutoLock lock(mMutex);
   if (mBuf) {
     aNonHeapSizeOut += GetAlignedDataLength();
   }
+  if (!mClosed) {
+    ++aExtHandlesOut;
+  }
 }
 
 uint8_t*
 SourceSurfaceSharedData::GetDataInternal() const
 {
   mMutex.AssertCurrentThreadOwns();
 
   // If we have an old buffer lingering, it is because we get reallocated to
--- a/gfx/layers/SourceSurfaceSharedData.h
+++ b/gfx/layers/SourceSurfaceSharedData.h
@@ -158,17 +158,18 @@ public:
   SurfaceType GetType() const override { return SurfaceType::DATA_SHARED; }
   IntSize GetSize() const override { return mSize; }
   SurfaceFormat GetFormat() const override { return mFormat; }
 
   void GuaranteePersistance() override;
 
   void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
                               size_t& aHeapSizeOut,
-                              size_t& aNonHeapSizeOut) const override;
+                              size_t& aNonHeapSizeOut,
+                              size_t& aExtHandlesOut) const override;
 
   bool OnHeap() const override
   {
     return false;
   }
 
   /**
    * Although Map (and Moz2D in general) isn't normally threadsafe,
--- a/gfx/layers/SourceSurfaceVolatileData.cpp
+++ b/gfx/layers/SourceSurfaceVolatileData.cpp
@@ -36,18 +36,25 @@ void
 SourceSurfaceVolatileData::GuaranteePersistance()
 {
   MOZ_ASSERT_UNREACHABLE("Should use SourceSurfaceRawData wrapper!");
 }
 
 void
 SourceSurfaceVolatileData::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
                                                   size_t& aHeapSizeOut,
-                                                  size_t& aNonHeapSizeOut) const
+                                                  size_t& aNonHeapSizeOut,
+                                                  size_t& aExtHandlesOut) const
 {
   if (mVBuf) {
     aHeapSizeOut += mVBuf->HeapSizeOfExcludingThis(aMallocSizeOf);
     aNonHeapSizeOut += mVBuf->NonHeapSizeOfExcludingThis();
+#ifdef ANDROID
+    if (!mVBuf->OnHeap()) {
+      // Volatile buffers keep a file handle open on Android.
+      ++aExtHandlesOut;
+    }
+#endif
   }
 }
 
 } // namespace gfx
 } // namespace mozilla
--- a/gfx/layers/SourceSurfaceVolatileData.h
+++ b/gfx/layers/SourceSurfaceVolatileData.h
@@ -47,17 +47,18 @@ public:
   SurfaceType GetType() const override { return SurfaceType::DATA; }
   IntSize GetSize() const override { return mSize; }
   SurfaceFormat GetFormat() const override { return mFormat; }
 
   void GuaranteePersistance() override;
 
   void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
                               size_t& aHeapSizeOut,
-                              size_t& aNonHeapSizeOut) const override;
+                              size_t& aNonHeapSizeOut,
+                              size_t& aExtHandlesOut) const override;
 
   bool OnHeap() const override
   {
     return mVBuf->OnHeap();
   }
 
   // Althought Map (and Moz2D in general) isn't normally threadsafe,
   // we want to allow it for SourceSurfaceVolatileData since it should
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -2172,20 +2172,31 @@ gfxPlatform::FlushFontAndWordCaches()
     }
 
     gfxPlatform::PurgeSkiaFontCache();
 }
 
 /* static */ void
 gfxPlatform::ForceGlobalReflow()
 {
-    // modify a preference that will trigger reflow everywhere
-    static const char kPrefName[] = "font.internaluseonly.changed";
-    bool fontInternalChange = Preferences::GetBool(kPrefName, false);
-    Preferences::SetBool(kPrefName, !fontInternalChange);
+    MOZ_ASSERT(NS_IsMainThread());
+    if (XRE_IsParentProcess()) {
+        // Modify a preference that will trigger reflow everywhere (in all
+        // content processes, as well as the parent).
+        static const char kPrefName[] = "font.internaluseonly.changed";
+        bool fontInternalChange = Preferences::GetBool(kPrefName, false);
+        Preferences::SetBool(kPrefName, !fontInternalChange);
+    } else {
+        // Send a notification that will be observed by PresShells in this
+        // process only.
+        nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+        if (obs) {
+            obs->NotifyObservers(nullptr, "font-info-updated", nullptr);
+        }
+    }
 }
 
 void
 gfxPlatform::FontsPrefsChanged(const char *aPref)
 {
     NS_ASSERTION(aPref != nullptr, "null preference");
     if (!strcmp(GFX_DOWNLOADABLE_FONTS_ENABLED, aPref)) {
         mAllowDownloadableFonts = UNINITIALIZED_VALUE;
--- a/gfx/thebes/gfxPlatformFontList.cpp
+++ b/gfx/thebes/gfxPlatformFontList.cpp
@@ -90,17 +90,16 @@ const gfxFontEntry::ScriptRange gfxPlatf
     // Khmer Symbols (19e0..19ff) don't seem to need any special shaping
     { 0xaa60, 0xaa7f, { TRUETYPE_TAG('m','y','m','r'),
                         TRUETYPE_TAG('m','y','m','2'), 0 } },
     // Thai seems to be "renderable" without AAT morphing tables
     { 0, 0, { 0, 0, 0 } } // terminator
 };
 
 // prefs for the font info loader
-#define FONT_LOADER_FAMILIES_PER_SLICE_PREF "gfx.font_loader.families_per_slice"
 #define FONT_LOADER_DELAY_PREF              "gfx.font_loader.delay"
 #define FONT_LOADER_INTERVAL_PREF           "gfx.font_loader.interval"
 
 static const char* kObservedPrefs[] = {
     "font.",
     "font.name-list.",
     "intl.accept_languages",  // hmmmm...
     nullptr
@@ -182,17 +181,17 @@ gfxPlatformFontList::MemoryReporter::Col
     }
 
     return NS_OK;
 }
 
 gfxPlatformFontList::gfxPlatformFontList(bool aNeedFullnamePostscriptNames)
     : mFontFamiliesMutex("gfxPlatformFontList::mFontFamiliesMutex"), mFontFamilies(64),
       mOtherFamilyNames(16), mBadUnderlineFamilyNames(8), mSharedCmaps(8),
-      mStartIndex(0), mIncrement(1), mNumFamilies(0), mFontlistInitCount(0),
+      mStartIndex(0), mNumFamilies(0), mFontlistInitCount(0),
       mFontFamilyWhitelistActive(false)
 {
     mOtherFamilyNamesInitialized = false;
 
     if (aNeedFullnamePostscriptNames) {
         mExtraNames = MakeUnique<ExtraNames>();
     }
     mFaceNameListsInitialized = false;
@@ -1678,19 +1677,16 @@ gfxPlatformFontList::CleanupLoader()
     }
 
     gfxFontInfoLoader::CleanupLoader();
 }
 
 void
 gfxPlatformFontList::GetPrefsAndStartLoader()
 {
-    mIncrement =
-        std::max(1u, Preferences::GetUint(FONT_LOADER_FAMILIES_PER_SLICE_PREF));
-
     uint32_t delay =
         std::max(1u, Preferences::GetUint(FONT_LOADER_DELAY_PREF));
     uint32_t interval =
         std::max(1u, Preferences::GetUint(FONT_LOADER_INTERVAL_PREF));
 
     StartLoader(delay, interval);
 }
 
--- a/gfx/thebes/gfxPlatformFontList.h
+++ b/gfx/thebes/gfxPlatformFontList.h
@@ -584,17 +584,16 @@ protected:
 
     // character map data shared across families
     // contains weak ptrs to cmaps shared by font entry objects
     nsTHashtable<CharMapHashKey> mSharedCmaps;
 
     // data used as part of the font cmap loading process
     nsTArray<RefPtr<gfxFontFamily> > mFontFamiliesToLoad;
     uint32_t mStartIndex;
-    uint32_t mIncrement;
     uint32_t mNumFamilies;
 
     // xxx - info for diagnosing no default font aborts
     // see bugs 636957, 1070983, 1189129
     uint32_t mFontlistInitCount; // num times InitFontList called
 
     nsTHashtable<nsPtrHashKey<gfxUserFontSet> > mUserFontSetList;
 
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -526,21 +526,23 @@ private:
   DECL_GFX_PREF(Once, "image.cache.timeweight",                ImageCacheTimeWeight, int32_t, 500);
   DECL_GFX_PREF(Live, "image.decode-immediately.enabled",      ImageDecodeImmediatelyEnabled, bool, false);
   DECL_GFX_PREF(Live, "image.downscale-during-decode.enabled", ImageDownscaleDuringDecodeEnabled, bool, true);
   DECL_GFX_PREF(Live, "image.infer-src-animation.threshold-ms", ImageInferSrcAnimationThresholdMS, uint32_t, 2000);
   DECL_GFX_PREF(Live, "image.layout_network_priority",         ImageLayoutNetworkPriority, bool, true);
   DECL_GFX_PREF(Once, "image.mem.decode_bytes_at_a_time",      ImageMemDecodeBytesAtATime, uint32_t, 200000);
   DECL_GFX_PREF(Live, "image.mem.discardable",                 ImageMemDiscardable, bool, false);
   DECL_GFX_PREF(Once, "image.mem.animated.discardable",        ImageMemAnimatedDiscardable, bool, false);
+  DECL_GFX_PREF(Live, "image.mem.animated.use_heap",           ImageMemAnimatedUseHeap, bool, false);
   DECL_OVERRIDE_PREF(Live, "image.mem.shared",                 ImageMemShared, gfxPrefs::WebRenderAll());
   DECL_GFX_PREF(Once, "image.mem.surfacecache.discard_factor", ImageMemSurfaceCacheDiscardFactor, uint32_t, 1);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.max_size_kb",    ImageMemSurfaceCacheMaxSizeKB, uint32_t, 100 * 1024);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.min_expiration_ms", ImageMemSurfaceCacheMinExpirationMS, uint32_t, 60*1000);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.size_factor",    ImageMemSurfaceCacheSizeFactor, uint32_t, 64);
+  DECL_GFX_PREF(Live, "image.mem.volatile.min_threshold_kb",   ImageMemVolatileMinThresholdKB, int32_t, -1);
   DECL_GFX_PREF(Once, "image.multithreaded_decoding.limit",    ImageMTDecodingLimit, int32_t, -1);
   DECL_GFX_PREF(Once, "image.multithreaded_decoding.idle_timeout", ImageMTDecodingIdleTimeout, int32_t, -1);
 
   DECL_GFX_PREF(Once, "layers.acceleration.disabled",          LayersAccelerationDisabledDoNotUseDirectly, bool, false);
   DECL_GFX_PREF(Live, "layers.acceleration.draw-fps",          LayersDrawFPS, bool, false);
   DECL_GFX_PREF(Live, "layers.acceleration.draw-fps.print-histogram",  FPSPrintHistogram, bool, false);
   DECL_GFX_PREF(Live, "layers.acceleration.draw-fps.write-to-file", WriteFPSToFile, bool, false);
   DECL_GFX_PREF(Once, "layers.acceleration.force-enabled",     LayersAccelerationForceEnabledDoNotUseDirectly, bool, false);
--- a/gfx/webrender_bindings/RendererOGL.cpp
+++ b/gfx/webrender_bindings/RendererOGL.cpp
@@ -79,16 +79,22 @@ RendererOGL::GetExternalImageHandler()
     LockExternalImage,
     UnlockExternalImage,
   };
 }
 
 void
 RendererOGL::Update()
 {
+  uint32_t flags = gfx::gfxVars::WebRenderDebugFlags();
+  if (mDebugFlags.mBits != flags) {
+    mDebugFlags.mBits = flags;
+    wr_renderer_set_debug_flags(mRenderer, mDebugFlags);
+  }
+
   if (gl()->MakeCurrent()) {
     wr_renderer_update(mRenderer);
   }
 }
 
 bool
 RendererOGL::UpdateAndRender(bool aReadback)
 {
--- a/image/AnimationSurfaceProvider.cpp
+++ b/image/AnimationSurfaceProvider.cpp
@@ -109,26 +109,26 @@ AnimationSurfaceProvider::LogicalSizeInB
   IntSize size = GetSurfaceKey().Size();
   return 3 * size.width * size.height * sizeof(uint32_t);
 }
 
 void
 AnimationSurfaceProvider::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
                                                  size_t& aHeapSizeOut,
                                                  size_t& aNonHeapSizeOut,
-                                                 size_t& aSharedHandlesOut)
+                                                 size_t& aExtHandlesOut)
 {
   // Note that the surface cache lock is already held here, and then we acquire
   // mFramesMutex. For this method, this ordering is unavoidable, which means
   // that we must be careful to always use the same ordering elsewhere.
   MutexAutoLock lock(mFramesMutex);
 
   for (const RawAccessFrameRef& frame : mFrames) {
     frame->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut,
-                                  aNonHeapSizeOut, aSharedHandlesOut);
+                                  aNonHeapSizeOut, aExtHandlesOut);
   }
 }
 
 void
 AnimationSurfaceProvider::Run()
 {
   MutexAutoLock lock(mDecodingMutex);
 
--- a/image/AnimationSurfaceProvider.h
+++ b/image/AnimationSurfaceProvider.h
@@ -43,17 +43,17 @@ public:
   // our surfaces are computed lazily.
   DrawableSurface Surface() override { return DrawableSurface(WrapNotNull(this)); }
 
   bool IsFinished() const override;
   size_t LogicalSizeInBytes() const override;
   void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
                               size_t& aHeapSizeOut,
                               size_t& aNonHeapSizeOut,
-                              size_t& aSharedHandlesOut) override;
+                              size_t& aExtHandlesOut) override;
 
 protected:
   DrawableFrameRef DrawableRef(size_t aFrame) override;
 
   // Animation frames are always locked. This is because we only want to release
   // their memory atomically (due to the surface cache discarding them). If they
   // were unlocked, the OS could end up releasing the memory of random frames
   // from the middle of the animation, which is not worth the complexity of
--- a/image/FrameAnimator.cpp
+++ b/image/FrameAnimator.cpp
@@ -534,17 +534,17 @@ DoCollectSizeOfCompositingSurfaces(const
                                /* aCannotSubstitute */ false,
                                /* aIsFactor2 */ false, aType);
 
   // Extract the surface's memory usage information.
   size_t heap = 0, nonHeap = 0, handles = 0;
   aSurface->AddSizeOfExcludingThis(aMallocSizeOf, heap, nonHeap, handles);
   counter.Values().SetDecodedHeap(heap);
   counter.Values().SetDecodedNonHeap(nonHeap);
-  counter.Values().SetSharedHandles(handles);
+  counter.Values().SetExternalHandles(handles);
 
   // Record it.
   aCounters.AppendElement(counter);
 }
 
 void
 FrameAnimator::CollectSizeOfCompositingSurfaces(
     nsTArray<SurfaceMemoryCounter>& aCounters,
--- a/image/ISurfaceProvider.h
+++ b/image/ISurfaceProvider.h
@@ -60,25 +60,25 @@ public:
   virtual size_t LogicalSizeInBytes() const = 0;
 
   /// @return the actual number of bytes of memory this ISurfaceProvider is
   /// using. May vary over the lifetime of the ISurfaceProvider. The default
   /// implementation is appropriate for static ISurfaceProviders.
   virtual void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
                                       size_t& aHeapSizeOut,
                                       size_t& aNonHeapSizeOut,
-                                      size_t& aSharedHandlesOut)
+                                      size_t& aExtHandlesOut)
   {
     DrawableFrameRef ref = DrawableRef(/* aFrame = */ 0);
     if (!ref) {
       return;
     }
 
     ref->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut,
-                                aNonHeapSizeOut, aSharedHandlesOut);
+                                aNonHeapSizeOut, aExtHandlesOut);
   }
 
   /// @return the availability state of this ISurfaceProvider, which indicates
   /// whether DrawableRef() could successfully return a surface. Should only be
   /// called from SurfaceCache code as it relies on SurfaceCache for
   /// synchronization.
   AvailabilityState& Availability() { return mAvailability; }
   const AvailabilityState& Availability() const { return mAvailability; }
--- a/image/Image.h
+++ b/image/Image.h
@@ -31,42 +31,42 @@ class Image;
 ///////////////////////////////////////////////////////////////////////////////
 
 struct MemoryCounter
 {
   MemoryCounter()
     : mSource(0)
     , mDecodedHeap(0)
     , mDecodedNonHeap(0)
-    , mSharedHandles(0)
+    , mExternalHandles(0)
   { }
 
   void SetSource(size_t aCount) { mSource = aCount; }
   size_t Source() const { return mSource; }
   void SetDecodedHeap(size_t aCount) { mDecodedHeap = aCount; }
   size_t DecodedHeap() const { return mDecodedHeap; }
   void SetDecodedNonHeap(size_t aCount) { mDecodedNonHeap = aCount; }
   size_t DecodedNonHeap() const { return mDecodedNonHeap; }
-  void SetSharedHandles(size_t aCount) { mSharedHandles = aCount; }
-  size_t SharedHandles() const { return mSharedHandles; }
+  void SetExternalHandles(size_t aCount) { mExternalHandles = aCount; }
+  size_t ExternalHandles() const { return mExternalHandles; }
 
   MemoryCounter& operator+=(const MemoryCounter& aOther)
   {
     mSource += aOther.mSource;
     mDecodedHeap += aOther.mDecodedHeap;
     mDecodedNonHeap += aOther.mDecodedNonHeap;
-    mSharedHandles += aOther.mSharedHandles;
+    mExternalHandles += aOther.mExternalHandles;
     return *this;
   }
 
 private:
   size_t mSource;
   size_t mDecodedHeap;
   size_t mDecodedNonHeap;
-  size_t mSharedHandles;
+  size_t mExternalHandles;
 };
 
 enum class SurfaceMemoryCounterType
 {
   NORMAL,
   COMPOSITING,
   COMPOSITING_PREV
 };
--- a/image/SurfaceCache.cpp
+++ b/image/SurfaceCache.cpp
@@ -202,17 +202,17 @@ public:
       // for surfaces with PlaybackType::eAnimated.)
       size_t heap = 0;
       size_t nonHeap = 0;
       size_t handles = 0;
       aCachedSurface->mProvider
         ->AddSizeOfExcludingThis(mMallocSizeOf, heap, nonHeap, handles);
       counter.Values().SetDecodedHeap(heap);
       counter.Values().SetDecodedNonHeap(nonHeap);
-      counter.Values().SetSharedHandles(handles);
+      counter.Values().SetExternalHandles(handles);
 
       mCounters.AppendElement(counter);
     }
 
   private:
     nsTArray<SurfaceMemoryCounter>& mCounters;
     MallocSizeOf                    mMallocSizeOf;
   };
--- a/image/imgFrame.cpp
+++ b/image/imgFrame.cpp
@@ -14,27 +14,24 @@
 #include "gfxPlatform.h"
 #include "gfxPrefs.h"
 #include "gfxUtils.h"
 
 #include "GeckoProfiler.h"
 #include "MainThreadUtils.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/gfx/Tools.h"
+#include "mozilla/gfx/SourceSurfaceRawData.h"
 #include "mozilla/layers/SourceSurfaceSharedData.h"
 #include "mozilla/layers/SourceSurfaceVolatileData.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MemoryReporting.h"
 #include "nsMargin.h"
 #include "nsThreadUtils.h"
 
-#ifdef ANDROID
-#define ANIMATED_FRAMES_USE_HEAP
-#endif
-
 namespace mozilla {
 
 using namespace gfx;
 
 namespace image {
 
 static void
 ScopedMapRelease(void* aMap)
@@ -75,36 +72,57 @@ CreateLockedSurface(DataSourceSurface *a
       return surf.forget();
     }
   }
 
   delete smap;
   return nullptr;
 }
 
+static bool
+ShouldUseHeap(const IntSize& aSize,
+              int32_t aStride,
+              bool aIsAnimated)
+{
+  // On some platforms (i.e. Android), a volatile buffer actually keeps a file
+  // handle active. We would like to avoid too many since we could easily
+  // exhaust the pool. However, other platforms we do not have the file handle
+  // problem, and additionally we may avoid a superfluous memset since the
+  // volatile memory starts out as zero-filled. Hence the knobs below.
+
+  // For as long as an animated image is retained, its frames will never be
+  // released to let the OS purge volatile buffers.
+  if (aIsAnimated && gfxPrefs::ImageMemAnimatedUseHeap()) {
+    return true;
+  }
+
+  // Lets us avoid too many small images consuming all of the handles. The
+  // actual allocation checks for overflow.
+  int32_t bufferSize = (aStride * aSize.width) / 1024;
+  if (bufferSize < gfxPrefs::ImageMemVolatileMinThresholdKB()) {
+    return true;
+  }
+
+  return false;
+}
+
 static already_AddRefed<DataSourceSurface>
 AllocateBufferForImage(const IntSize& size,
                        SurfaceFormat format,
                        bool aIsAnimated = false)
 {
   int32_t stride = VolatileSurfaceStride(size, format);
 
-#ifdef ANIMATED_FRAMES_USE_HEAP
-  if (aIsAnimated) {
-    // For as long as an animated image is retained, its frames will never be
-    // released to let the OS purge volatile buffers. On Android, a volatile
-    // buffer actually keeps a file handle active, which we would like to avoid
-    // since many images and frames could easily exhaust the pool. As such, we
-    // use the heap. On the other platforms we do not have the file handle
-    // problem, and additionally we may avoid a superfluous memset since the
-    // volatile memory starts out as zero-filled.
-    return Factory::CreateDataSourceSurfaceWithStride(size, format,
-                                                      stride, false);
+  if (ShouldUseHeap(size, stride, aIsAnimated)) {
+    RefPtr<SourceSurfaceAlignedRawData> newSurf =
+      new SourceSurfaceAlignedRawData();
+    if (newSurf->Init(size, format, false, 0, stride)) {
+      return newSurf.forget();
+    }
   }
-#endif
 
   if (!aIsAnimated && gfxVars::GetUseWebRenderOrDefault()
                    && gfxPrefs::ImageMemShared()) {
     RefPtr<SourceSurfaceSharedData> newSurf = new SourceSurfaceSharedData();
     if (newSurf->Init(size, stride, format)) {
       return newSurf.forget();
     }
   } else {
@@ -927,38 +945,30 @@ imgFrame::SetCompositingFailed(bool val)
   MOZ_ASSERT(NS_IsMainThread());
   mCompositingFailed = val;
 }
 
 void
 imgFrame::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
                                  size_t& aHeapSizeOut,
                                  size_t& aNonHeapSizeOut,
-                                 size_t& aSharedHandlesOut) const
+                                 size_t& aExtHandlesOut) const
 {
   MonitorAutoLock lock(mMonitor);
 
   if (mPalettedImageData) {
     aHeapSizeOut += aMallocSizeOf(mPalettedImageData);
   }
   if (mLockedSurface) {
     aHeapSizeOut += aMallocSizeOf(mLockedSurface);
   }
   if (mOptSurface) {
     aHeapSizeOut += aMallocSizeOf(mOptSurface);
   }
   if (mRawSurface) {
     aHeapSizeOut += aMallocSizeOf(mRawSurface);
     mRawSurface->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut,
-                                        aNonHeapSizeOut);
-
-    if (mRawSurface->GetType() == SurfaceType::DATA_SHARED) {
-      auto sharedSurface =
-        static_cast<SourceSurfaceSharedData*>(mRawSurface.get());
-      if (sharedSurface->CanShare()) {
-        ++aSharedHandlesOut;
-      }
-    }
+                                        aNonHeapSizeOut, aExtHandlesOut);
   }
 }
 
 } // namespace image
 } // namespace mozilla
--- a/image/imgFrame.h
+++ b/image/imgFrame.h
@@ -245,17 +245,17 @@ public:
 
   void SetOptimizable();
 
   void FinalizeSurface();
   already_AddRefed<SourceSurface> GetSourceSurface();
 
   void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, size_t& aHeapSizeOut,
                               size_t& aNonHeapSizeOut,
-                              size_t& aSharedHandlesOut) const;
+                              size_t& aExtHandlesOut) const;
 
 private: // methods
 
   ~imgFrame();
 
   nsresult LockImageData();
   nsresult UnlockImageData();
   nsresult Optimize(gfx::DrawTarget* aTarget);
--- a/image/imgLoader.cpp
+++ b/image/imgLoader.cpp
@@ -292,19 +292,19 @@ private:
       if (counter.CannotSubstitute()) {
         surfacePathPrefix.AppendLiteral("cannot_substitute/");
       }
       surfacePathPrefix.AppendLiteral("surface(");
       surfacePathPrefix.AppendInt(counter.Key().Size().width);
       surfacePathPrefix.AppendLiteral("x");
       surfacePathPrefix.AppendInt(counter.Key().Size().height);
 
-      if (counter.Values().SharedHandles() > 0) {
-        surfacePathPrefix.AppendLiteral(", shared:");
-        surfacePathPrefix.AppendInt(uint32_t(counter.Values().SharedHandles()));
+      if (counter.Values().ExternalHandles() > 0) {
+        surfacePathPrefix.AppendLiteral(", external:");
+        surfacePathPrefix.AppendInt(uint32_t(counter.Values().ExternalHandles()));
       }
 
       if (counter.Type() == SurfaceMemoryCounterType::NORMAL) {
         PlaybackType playback = counter.Key().Playback();
         surfacePathPrefix.Append(playback == PlaybackType::eAnimated
                                  ? " (animation)"
                                  : "");
 
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -111,17 +111,18 @@ MSG_DEF(JSMSG_NOT_OBJORNULL,           1
 // JSON
 MSG_DEF(JSMSG_JSON_BAD_PARSE,          3, JSEXN_SYNTAXERR, "JSON.parse: {0} at line {1} column {2} of the JSON data")
 MSG_DEF(JSMSG_JSON_CYCLIC_VALUE,       0, JSEXN_TYPEERR, "cyclic object value")
 
 // Runtime errors
 MSG_DEF(JSMSG_BAD_INSTANCEOF_RHS,      1, JSEXN_TYPEERR, "invalid 'instanceof' operand {0}")
 MSG_DEF(JSMSG_BAD_LEFTSIDE_OF_ASS,     0, JSEXN_REFERENCEERR, "invalid assignment left-hand side")
 MSG_DEF(JSMSG_BAD_PROTOTYPE,           1, JSEXN_TYPEERR, "'prototype' property of {0} is not an object")
-MSG_DEF(JSMSG_IN_NOT_OBJECT,           2, JSEXN_TYPEERR, "cannot use 'in' operator to search for '{0}' in '{1}'")
+MSG_DEF(JSMSG_IN_NOT_OBJECT,           1, JSEXN_TYPEERR, "right-hand side of 'in' should be an object, got {0}")
+MSG_DEF(JSMSG_IN_STRING,               2, JSEXN_TYPEERR, "cannot use 'in' operator to search for '{0}' in '{1}'")
 MSG_DEF(JSMSG_TOO_MANY_CON_SPREADARGS, 0, JSEXN_RANGEERR, "too many constructor arguments")
 MSG_DEF(JSMSG_TOO_MANY_FUN_SPREADARGS, 0, JSEXN_RANGEERR, "too many function arguments")
 MSG_DEF(JSMSG_UNINITIALIZED_LEXICAL,   1, JSEXN_REFERENCEERR, "can't access lexical declaration `{0}' before initialization")
 MSG_DEF(JSMSG_BAD_CONST_ASSIGN,        1, JSEXN_TYPEERR, "invalid assignment to const `{0}'")
 MSG_DEF(JSMSG_CANT_DECLARE_GLOBAL_BINDING, 2, JSEXN_TYPEERR, "cannot declare global binding `{0}': {1}")
 
 // Date
 MSG_DEF(JSMSG_INVALID_DATE,            0, JSEXN_RANGEERR, "invalid date")
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1694,28 +1694,30 @@ js::ReportInNotObjectError(JSContext* cx
                 return nullptr;
             str = buf.finishString();
             if (!str)
                 return nullptr;
         }
         return UniqueChars(JS_EncodeString(cx, str));
     };
 
-    UniqueChars lbytes = lref.isString()
-                       ? uniqueCharsFromString(cx, lref)
-                       : DecompileValueGenerator(cx, lindex, lref, nullptr);
-    if (!lbytes)
+    if (lref.isString() && rref.isString()) {
+        UniqueChars lbytes = uniqueCharsFromString(cx, lref);
+        if (!lbytes)
+            return;
+        UniqueChars rbytes = uniqueCharsFromString(cx, rref);
+        if (!rbytes)
+            return;
+        JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_IN_STRING,
+                                   lbytes.get(), rbytes.get());
         return;
-    UniqueChars rbytes = rref.isString()
-                       ? uniqueCharsFromString(cx, rref)
-                       : DecompileValueGenerator(cx, rindex, rref, nullptr);
-    if (!rbytes)
-        return;
+    }
+
     JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_IN_NOT_OBJECT,
-                               lbytes.get(), rbytes.get());
+                               InformalValueTypeName(rref));
 }
 
 static MOZ_NEVER_INLINE bool
 Interpret(JSContext* cx, RunState& state)
 {
 /*
  * Define macros for an interpreter loop. Opcode dispatch may be either by a
  * switch statement or by indirect goto (aka a threaded interpreter), depending
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -1018,16 +1018,17 @@ PresShell::Init(nsIDocument* aDocument,
 #ifdef MOZ_XUL
       os->AddObserver(this, "chrome-flush-skin-caches", false);
 #endif
       os->AddObserver(this, "memory-pressure", false);
       os->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, false);
       if (XRE_IsParentProcess() && !sProcessInteractable) {
         os->AddObserver(this, "sessionstore-one-or-no-tab-restored", false);
       }
+      os->AddObserver(this, "font-info-updated", false);
     }
   }
 
 #ifdef MOZ_REFLOW_PERF
     if (mReflowCountMgr) {
       bool paintFrameCounts =
         Preferences::GetBool("layout.reflow.showframecounts");
 
@@ -1255,16 +1256,17 @@ PresShell::Destroy()
 #ifdef MOZ_XUL
       os->RemoveObserver(this, "chrome-flush-skin-caches");
 #endif
       os->RemoveObserver(this, "memory-pressure");
       os->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC);
       if (XRE_IsParentProcess()) {
         os->RemoveObserver(this, "sessionstore-one-or-no-tab-restored");
       }
+      os->RemoveObserver(this, "font-info-updated");
     }
   }
 
   // If our paint suppression timer is still active, kill it.
   if (mPaintSuppressionTimer) {
     mPaintSuppressionTimer->Cancel();
     mPaintSuppressionTimer = nullptr;
   }
@@ -5212,18 +5214,18 @@ PresShell::RenderSelection(nsISelection*
 }
 
 void
 PresShell::AddPrintPreviewBackgroundItem(nsDisplayListBuilder& aBuilder,
                                          nsDisplayList&        aList,
                                          nsIFrame*             aFrame,
                                          const nsRect&         aBounds)
 {
-  aList.AppendToBottom(new (&aBuilder)
-    nsDisplaySolidColor(&aBuilder, aFrame, aBounds, NS_RGB(115, 115, 115)));
+  aList.AppendToBottom(
+    MakeDisplayItem<nsDisplaySolidColor>(&aBuilder, aFrame, aBounds, NS_RGB(115, 115, 115)));
 }
 
 static bool
 AddCanvasBackgroundColor(const nsDisplayList& aList, nsIFrame* aCanvasFrame,
                          nscolor aColor, bool aCSSBackgroundColor)
 {
   for (nsDisplayItem* i = aList.GetBottom(); i; i = i->GetAbove()) {
     const DisplayItemType type = i->GetType();
@@ -5303,17 +5305,17 @@ PresShell::AddCanvasBackgroundColorItem(
     // If we're using ContainerLayers for a subdoc, then any items we add here will
     // still be scrolled (since we're inside the container at this point), so don't
     // bother and we will do it manually later.
     forceUnscrolledItem = false;
   }
 
   if (!addedScrollingBackgroundColor || forceUnscrolledItem) {
     aList.AppendToBottom(
-      new (&aBuilder) nsDisplaySolidColor(&aBuilder, aFrame, aBounds, bgcolor));
+      MakeDisplayItem<nsDisplaySolidColor>(&aBuilder, aFrame, aBounds, bgcolor));
   }
 }
 
 static bool IsTransparentContainerElement(nsPresContext* aPresContext)
 {
   nsCOMPtr<nsIDocShell> docShell = aPresContext->GetDocShell();
   if (!docShell) {
     return false;
@@ -9321,16 +9323,21 @@ PresShell::Observe(nsISupports* aSubject
 
     nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
     if (os) {
       os->RemoveObserver(this, "sessionstore-one-or-no-tab-restored");
     }
     return NS_OK;
   }
 
+  if (!nsCRT::strcmp(aTopic, "font-info-updated")) {
+    mPresContext->ForceReflowForFontInfoUpdate();
+    return NS_OK;
+  }
+
   NS_WARNING("unrecognized topic in PresShell::Observe");
   return NS_ERROR_FAILURE;
 }
 
 bool
 nsIPresShell::AddRefreshObserver(nsARefreshObserver* aObserver,
                                  FlushType aFlushType)
 {
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -3016,17 +3016,17 @@ nsLayoutUtils::GetLayerTransformForFrame
   }
 
   nsDisplayListBuilder builder(root,
                                nsDisplayListBuilderMode::TRANSFORM_COMPUTATION,
                                false/*don't build caret*/);
   builder.BeginFrame();
   nsDisplayList list;
   nsDisplayTransform* item =
-    new (&builder) nsDisplayTransform(&builder, aFrame, &list, nsRect());
+    MakeDisplayItem<nsDisplayTransform>(&builder, aFrame, &list, nsRect());
 
   *aTransform = item->GetTransform();
   item->Destroy(&builder);
 
   builder.EndFrame();
 
   return true;
 }
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -228,16 +228,25 @@ void
 nsPresContext::PrefChangedUpdateTimerCallback(nsITimer *aTimer, void *aClosure)
 {
   nsPresContext*  presContext = (nsPresContext*)aClosure;
   NS_ASSERTION(presContext != nullptr, "bad instance data");
   if (presContext)
     presContext->UpdateAfterPreferencesChanged();
 }
 
+void
+nsPresContext::ForceReflowForFontInfoUpdate()
+{
+  // 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;
 }
 
 nsPresContext::nsPresContext(nsIDocument* aDocument, nsPresContextType aType)
   : mType(aType),
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -1245,16 +1245,20 @@ protected:
     return StaticPresData::Get()->GetFontPrefsForLangHelper(lang, &mLangGroupFontPrefs, aNeedsToCache);
   }
 
   void UpdateCharSet(NotNull<const Encoding*> aCharSet);
 
   static bool NotifyDidPaintSubdocumentCallback(nsIDocument* aDocument, void* aData);
 
 public:
+  // Used by the PresShell to force a reflow when some aspect of font info
+  // has been updated, potentially affecting font selection and layout.
+  void ForceReflowForFontInfoUpdate();
+
   void DoChangeCharSet(NotNull<const Encoding*> aCharSet);
 
   /**
    * Checks for MozAfterPaint listeners on the document
    */
   bool MayHavePaintEventListener();
 
   /**
--- a/layout/forms/nsButtonFrameRenderer.cpp
+++ b/layout/forms/nsButtonFrameRenderer.cpp
@@ -472,33 +472,33 @@ nsDisplayButtonForeground::CreateWebRend
 }
 
 nsresult
 nsButtonFrameRenderer::DisplayButton(nsDisplayListBuilder* aBuilder,
                                      nsDisplayList* aBackground,
                                      nsDisplayList* aForeground)
 {
   if (mFrame->StyleEffects()->mBoxShadow) {
-    aBackground->AppendToTop(new (aBuilder)
-      nsDisplayButtonBoxShadowOuter(aBuilder, this));
+    aBackground->AppendToTop(
+      MakeDisplayItem<nsDisplayButtonBoxShadowOuter>(aBuilder, this));
   }
 
   nsRect buttonRect = mFrame->GetRectRelativeToSelf();
 
   nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
     aBuilder, mFrame, buttonRect, aBackground);
 
-  aBackground->AppendToTop(new (aBuilder)
-    nsDisplayButtonBorder(aBuilder, this));
+  aBackground->AppendToTop(
+    MakeDisplayItem<nsDisplayButtonBorder>(aBuilder, this));
 
   // Only display focus rings if we actually have them. Since at most one
   // button would normally display a focus ring, most buttons won't have them.
   if (mInnerFocusStyle && mInnerFocusStyle->StyleBorder()->HasBorder()) {
-    aForeground->AppendToTop(new (aBuilder)
-      nsDisplayButtonForeground(aBuilder, this));
+    aForeground->AppendToTop(
+      MakeDisplayItem<nsDisplayButtonForeground>(aBuilder, this));
   }
   return NS_OK;
 }
 
 void
 nsButtonFrameRenderer::GetButtonInnerFocusRect(const nsRect& aRect, nsRect& aResult)
 {
   aResult = aRect;
--- a/layout/forms/nsComboboxControlFrame.cpp
+++ b/layout/forms/nsComboboxControlFrame.cpp
@@ -1594,17 +1594,17 @@ nsComboboxControlFrame::BuildDisplayList
     nsPIDOMWindowOuter* window = doc->GetWindow();
     if (window && window->ShouldShowFocusRing()) {
       nsPresContext *presContext = PresContext();
       const nsStyleDisplay *disp = StyleDisplay();
       if ((!IsThemed(disp) ||
            !presContext->GetTheme()->ThemeDrawsFocusForWidget(disp->mAppearance)) &&
           mDisplayFrame && IsVisibleForPainting(aBuilder)) {
         aLists.Content()->AppendToTop(
-          new (aBuilder) nsDisplayComboboxFocus(aBuilder, this));
+          MakeDisplayItem<nsDisplayComboboxFocus>(aBuilder, this));
       }
     }
   }
 
   DisplaySelectionOverlay(aBuilder, aLists.Content());
 }
 
 void nsComboboxControlFrame::PaintFocus(DrawTarget& aDrawTarget, nsPoint aPt)
--- a/layout/forms/nsFieldSetFrame.cpp
+++ b/layout/forms/nsFieldSetFrame.cpp
@@ -191,27 +191,27 @@ nsFieldSetFrame::BuildDisplayList(nsDisp
                                   const nsDisplayListSet& aLists) {
   // Paint our background and border in a special way.
   // REVIEW: We don't really need to check frame emptiness here; if it's empty,
   // the background/border display item won't do anything, and if it isn't empty,
   // we need to paint the outline
   if (!(GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER) &&
       IsVisibleForPainting(aBuilder)) {
     if (StyleEffects()->mBoxShadow) {
-      aLists.BorderBackground()->AppendToTop(new (aBuilder)
-        nsDisplayBoxShadowOuter(aBuilder, this));
+      aLists.BorderBackground()->AppendToTop(
+        MakeDisplayItem<nsDisplayBoxShadowOuter>(aBuilder, this));
     }
 
     nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
       aBuilder, this, VisualBorderRectRelativeToSelf(),
       aLists.BorderBackground(),
       /* aAllowWillPaintBorderOptimization = */ false);
 
-    aLists.BorderBackground()->AppendToTop(new (aBuilder)
-      nsDisplayFieldSetBorder(aBuilder, this));
+    aLists.BorderBackground()->AppendToTop(
+      MakeDisplayItem<nsDisplayFieldSetBorder>(aBuilder, this));
 
     DisplayOutlineUnconditional(aBuilder, aLists);
 
     DO_GLOBAL_REFLOW_COUNT_DSP("nsFieldSetFrame");
   }
 
   if (GetPrevInFlow()) {
     DisplayOverflowContainers(aBuilder, aLists);
--- a/layout/forms/nsListControlFrame.cpp
+++ b/layout/forms/nsListControlFrame.cpp
@@ -180,17 +180,17 @@ nsListControlFrame::BuildDisplayList(nsD
 
   if (IsInDropDownMode()) {
     NS_ASSERTION(NS_GET_A(mLastDropdownBackstopColor) == 255,
                  "need an opaque backstop color");
     // XXX Because we have an opaque widget and we get called to paint with
     // this frame as the root of a stacking context we need make sure to draw
     // some opaque color over the whole widget. (Bug 511323)
     aLists.BorderBackground()->AppendToBottom(
-      new (aBuilder) nsDisplaySolidColor(aBuilder,
+      MakeDisplayItem<nsDisplaySolidColor>(aBuilder,
         this, nsRect(aBuilder->ToReferenceFrame(this), GetSize()),
         mLastDropdownBackstopColor));
   }
 
   nsHTMLScrollFrame::BuildDisplayList(aBuilder, aLists);
 }
 
 /**
--- a/layout/forms/nsRangeFrame.cpp
+++ b/layout/forms/nsRangeFrame.cpp
@@ -300,17 +300,17 @@ nsRangeFrame::BuildDisplayList(nsDisplay
   }
 
   if (IsThemed(disp) &&
       PresContext()->GetTheme()->ThemeDrawsFocusForWidget(disp->mAppearance)) {
     return; // the native theme displays its own visual indication of focus
   }
 
   aLists.Content()->AppendToTop(
-    new (aBuilder) nsDisplayRangeFocusRing(aBuilder, this));
+    MakeDisplayItem<nsDisplayRangeFocusRing>(aBuilder, this));
 }
 
 void
 nsRangeFrame::Reflow(nsPresContext*           aPresContext,
                      ReflowOutput&     aDesiredSize,
                      const ReflowInput& aReflowInput,
                      nsReflowStatus&          aStatus)
 {
--- a/layout/forms/nsSelectsAreaFrame.cpp
+++ b/layout/forms/nsSelectsAreaFrame.cpp
@@ -70,21 +70,21 @@ void nsDisplayOptionEventGrabber::HitTes
 }
 
 class nsOptionEventGrabberWrapper : public nsDisplayWrapper
 {
 public:
   nsOptionEventGrabberWrapper() {}
   virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder,
                                   nsIFrame* aFrame, nsDisplayList* aList) override {
-    return new (aBuilder) nsDisplayOptionEventGrabber(aBuilder, aFrame, aList);
+    return MakeDisplayItem<nsDisplayOptionEventGrabber>(aBuilder, aFrame, aList);
   }
   virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder,
                                   nsDisplayItem* aItem) override {
-    return new (aBuilder) nsDisplayOptionEventGrabber(aBuilder, aItem->Frame(), aItem);
+    return MakeDisplayItem<nsDisplayOptionEventGrabber>(aBuilder, aItem->Frame(), aItem);
   }
 };
 
 static nsListControlFrame* GetEnclosingListFrame(nsIFrame* aSelectsAreaFrame)
 {
   nsIFrame* frame = aSelectsAreaFrame->GetParent();
   while (frame) {
     if (frame->IsListControlFrame())
@@ -149,18 +149,18 @@ nsSelectsAreaFrame::BuildDisplayListInte
 {
   nsBlockFrame::BuildDisplayList(aBuilder, aLists);
 
   nsListControlFrame* listFrame = GetEnclosingListFrame(this);
   if (listFrame && listFrame->IsFocused()) {
     // we can't just associate the display item with the list frame,
     // because then the list's scrollframe won't clip it (the scrollframe
     // only clips contained descendants).
-    aLists.Outlines()->AppendToTop(new (aBuilder)
-      nsDisplayListFocus(aBuilder, this));
+    aLists.Outlines()->AppendToTop(
+      MakeDisplayItem<nsDisplayListFocus>(aBuilder, this));
   }
 }
 
 void
 nsSelectsAreaFrame::Reflow(nsPresContext*           aPresContext,
                            ReflowOutput&     aDesiredSize,
                            const ReflowInput& aReflowInput,
                            nsReflowStatus&          aStatus)
--- a/layout/generic/TextOverflow.cpp
+++ b/layout/generic/TextOverflow.cpp
@@ -856,36 +856,38 @@ TextOverflow::CreateMarkers(const nsLine
     LogicalRect markerLogicalRect(
       mBlockWM, aInsideMarkersArea.IStart(mBlockWM) - mIStart.mIntrinsicISize,
       aLine->BStart(), mIStart.mIntrinsicISize, aLine->BSize());
     nsPoint offset = mBuilder->ToReferenceFrame(mBlock);
     nsRect markerRect =
       markerLogicalRect.GetPhysicalRect(mBlockWM, mBlockSize) + offset;
     ClipMarker(aContentArea.GetPhysicalRect(mBlockWM, mBlockSize) + offset,
                markerRect, clipState);
-    nsDisplayItem* marker = new (mBuilder)
-      nsDisplayTextOverflowMarker(mBuilder, mBlock, markerRect,
-                                  aLine->GetLogicalAscent(), mIStart.mStyle, aLineNumber, 0);
+    nsDisplayItem* marker =
+      MakeDisplayItem<nsDisplayTextOverflowMarker>(mBuilder, mBlock, markerRect,
+                                                   aLine->GetLogicalAscent(), mIStart.mStyle,
+                                                   aLineNumber, 0);
     mMarkerList.AppendToTop(marker);
   }
 
   if (aCreateIEnd) {
     DisplayListClipState::AutoSaveRestore clipState(mBuilder);
 
     LogicalRect markerLogicalRect(
       mBlockWM, aInsideMarkersArea.IEnd(mBlockWM), aLine->BStart(),
       mIEnd.mIntrinsicISize, aLine->BSize());
     nsPoint offset = mBuilder->ToReferenceFrame(mBlock);
     nsRect markerRect =
       markerLogicalRect.GetPhysicalRect(mBlockWM, mBlockSize) + offset;
     ClipMarker(aContentArea.GetPhysicalRect(mBlockWM, mBlockSize) + offset,
                markerRect, clipState);
-    nsDisplayItem* marker = new (mBuilder)
-      nsDisplayTextOverflowMarker(mBuilder, mBlock, markerRect,
-                                  aLine->GetLogicalAscent(), mIEnd.mStyle, aLineNumber, 1);
+    nsDisplayItem* marker =
+      MakeDisplayItem<nsDisplayTextOverflowMarker>(mBuilder, mBlock, markerRect,
+                                                   aLine->GetLogicalAscent(), mIEnd.mStyle,
+                                                   aLineNumber, 1);
     mMarkerList.AppendToTop(marker);
   }
 }
 
 void
 TextOverflow::Marker::SetupString(nsIFrame* aFrame)
 {
   if (mInitialized) {
--- a/layout/generic/ViewportFrame.cpp
+++ b/layout/generic/ViewportFrame.cpp
@@ -67,17 +67,17 @@ ViewportFrame::BuildDisplayList(nsDispla
   }
 
   nsDisplayList topLayerList;
   BuildDisplayListForTopLayer(aBuilder, &topLayerList);
   if (!topLayerList.IsEmpty()) {
     // Wrap the whole top layer in a single item with maximum z-index,
     // and append it at the very end, so that it stays at the topmost.
     nsDisplayWrapList* wrapList =
-      new (aBuilder) nsDisplayWrapList(aBuilder, this, &topLayerList);
+      MakeDisplayItem<nsDisplayWrapList>(aBuilder, this, &topLayerList);
     wrapList->SetOverrideZIndex(
       std::numeric_limits<decltype(wrapList->ZIndex())>::max());
     aLists.PositionedDescendants()->AppendToTop(wrapList);
   }
 }
 
 #ifdef DEBUG
 /**
--- a/layout/generic/nsBulletFrame.cpp
+++ b/layout/generic/nsBulletFrame.cpp
@@ -784,17 +784,17 @@ nsBulletFrame::BuildDisplayList(nsDispla
                                 const nsDisplayListSet& aLists)
 {
   if (!IsVisibleForPainting(aBuilder))
     return;
 
   DO_GLOBAL_REFLOW_COUNT_DSP("nsBulletFrame");
 
   aLists.Content()->AppendToTop(
-    new (aBuilder) nsDisplayBullet(aBuilder, this));
+    MakeDisplayItem<nsDisplayBullet>(aBuilder, this));
 }
 
 Maybe<BulletRenderer>
 nsBulletFrame::CreateBulletRenderer(gfxContext& aRenderingContext, nsPoint aPt)
 {
   const nsStyleList* myList = StyleList();
   CounterStyle* listStyleType = myList->mCounterStyle;
   nsMargin padding = mPadding.GetPhysicalMargin(GetWritingMode());
--- a/layout/generic/nsCanvasFrame.cpp
+++ b/layout/generic/nsCanvasFrame.cpp
@@ -465,21 +465,21 @@ nsCanvasFrame::BuildDisplayList(nsDispla
     bool isThemed = IsThemed();
     if (!isThemed && nsCSSRendering::FindBackgroundFrame(this, &dependentFrame)) {
       bg = dependentFrame->StyleContext()->StyleBackground();
       if (dependentFrame == this) {
         dependentFrame = nullptr;
       }
     }
     aLists.BorderBackground()->AppendToTop(
-        new (aBuilder) nsDisplayCanvasBackgroundColor(aBuilder, this));
+        MakeDisplayItem<nsDisplayCanvasBackgroundColor>(aBuilder, this));
 
     if (isThemed) {
       aLists.BorderBackground()->AppendToTop(
-        new (aBuilder) nsDisplayCanvasThemedBackground(aBuilder, this));
+        MakeDisplayItem<nsDisplayCanvasThemedBackground>(aBuilder, this));
       return;
     }
 
     if (!bg) {
       return;
     }
 
     const ActiveScrolledRoot* asr =
@@ -523,34 +523,34 @@ nsCanvasFrame::BuildDisplayList(nsDispla
           asrSetter.SetCurrentActiveScrolledRoot(
             displayData->mContainingBlockActiveScrolledRoot);
           thisItemASR = displayData->mContainingBlockActiveScrolledRoot;
         }
         nsDisplayCanvasBackgroundImage* bgItem = nullptr;
         {
           DisplayListClipState::AutoSaveRestore bgImageClip(aBuilder);
           bgImageClip.Clear();
-          bgItem = new (aBuilder) nsDisplayCanvasBackgroundImage(bgData);
+          bgItem = MakeDisplayItem<nsDisplayCanvasBackgroundImage>(aBuilder, bgData);
           bgItem->SetDependentFrame(aBuilder, dependentFrame);
         }
         thisItemList.AppendToTop(
           nsDisplayFixedPosition::CreateForFixedBackground(aBuilder, this, bgItem, i));
 
       } else {
-        nsDisplayCanvasBackgroundImage* bgItem = new (aBuilder) nsDisplayCanvasBackgroundImage(bgData);
+        nsDisplayCanvasBackgroundImage* bgItem = MakeDisplayItem<nsDisplayCanvasBackgroundImage>(aBuilder, bgData);
         bgItem->SetDependentFrame(aBuilder, dependentFrame);
         thisItemList.AppendToTop(bgItem);
       }
 
       if (layers.mLayers[i].mBlendMode != NS_STYLE_BLEND_NORMAL) {
         DisplayListClipState::AutoSaveRestore blendClip(aBuilder);
         thisItemList.AppendToTop(
-          new (aBuilder) nsDisplayBlendMode(aBuilder, this, &thisItemList,
-                                            layers.mLayers[i].mBlendMode,
-                                            thisItemASR, i + 1));
+          MakeDisplayItem<nsDisplayBlendMode>(aBuilder, this, &thisItemList,
+                                              layers.mLayers[i].mBlendMode,
+                                              thisItemASR, i + 1));
       }
       aLists.BorderBackground()->AppendToTop(&thisItemList);
     }
 
     if (needBlendContainer) {
       const ActiveScrolledRoot* containerASR = contASRTracker.GetContainerASR();
       DisplayListClipState::AutoSaveRestore blendContainerClip(aBuilder);
       aLists.BorderBackground()->AppendToTop(
@@ -586,18 +586,18 @@ nsCanvasFrame::BuildDisplayList(nsDispla
 #endif
 
   if (!mDoPaintFocus)
     return;
   // Only paint the focus if we're visible
   if (!StyleVisibility()->IsVisible())
     return;
 
-  aLists.Outlines()->AppendToTop(new (aBuilder)
-    nsDisplayCanvasFocus(aBuilder, this));
+  aLists.Outlines()->AppendToTop(
+    MakeDisplayItem<nsDisplayCanvasFocus>(aBuilder, this));
 }
 
 void
 nsCanvasFrame::PaintFocus(DrawTarget* aDrawTarget, nsPoint aPt)
 {
   nsRect focusRect(aPt, GetSize());
 
   nsIScrollableFrame *scrollableFrame = do_QueryFrame(GetParent());
--- a/layout/generic/nsCanvasFrame.h
+++ b/layout/generic/nsCanvasFrame.h
@@ -185,18 +185,18 @@ public:
   NS_DISPLAY_DECL_NAME("CanvasBackgroundColor", TYPE_CANVAS_BACKGROUND_COLOR)
 #ifdef MOZ_DUMP_PAINTING
   virtual void WriteDebugInfo(std::stringstream& aStream) override;
 #endif
 };
 
 class nsDisplayCanvasBackgroundImage : public nsDisplayBackgroundImage {
 public:
-  explicit nsDisplayCanvasBackgroundImage(const InitData& aInitData)
-    : nsDisplayBackgroundImage(aInitData)
+  explicit nsDisplayCanvasBackgroundImage(nsDisplayListBuilder* aBuilder, const InitData& aInitData)
+    : nsDisplayBackgroundImage(aBuilder, aInitData)
   {
   }
 
   virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
 
   // We still need to paint a background color as well as an image for this item,
   // so we can't support this yet.
   virtual bool SupportsOptimizingToImage() const override { return false; }
--- a/layout/generic/nsColumnSetFrame.cpp
+++ b/layout/generic/nsColumnSetFrame.cpp
@@ -1275,17 +1275,17 @@ nsColumnSetFrame::Reflow(nsPresContext* 
 void
 nsColumnSetFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                    const nsDisplayListSet& aLists)
 {
   DisplayBorderBackgroundOutline(aBuilder, aLists);
 
   if (IsVisibleForPainting(aBuilder)) {
     aLists.BorderBackground()->
-      AppendToTop(new (aBuilder) nsDisplayColumnRule(aBuilder, this));
+      AppendToTop(MakeDisplayItem<nsDisplayColumnRule>(aBuilder, this));
   }
 
   // Our children won't have backgrounds so it doesn't matter where we put them.
   for (nsFrameList::Enumerator e(mFrames); !e.AtEnd(); e.Next()) {
     BuildDisplayListForChild(aBuilder, e.get(), aLists);
   }
 }
 
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -2313,30 +2313,30 @@ nsFrame::DisplaySelectionOverlay(nsDispl
     }
   }
 
   if (!normal && aContentType == nsISelectionDisplay::DISPLAY_IMAGES) {
     // Don't overlay an image if it's not in the primary selection.
     return;
   }
 
-  aList->AppendToTop(new (aBuilder)
-    nsDisplaySelectionOverlay(aBuilder, this, selectionValue));
+  aList->AppendToTop(
+    MakeDisplayItem<nsDisplaySelectionOverlay>(aBuilder, this, selectionValue));
 }
 
 void
 nsFrame::DisplayOutlineUnconditional(nsDisplayListBuilder*   aBuilder,
                                      const nsDisplayListSet& aLists)
 {
   if (!StyleOutline()->ShouldPaintOutline()) {
     return;
   }
 
   aLists.Outlines()->AppendToTop(
-    new (aBuilder) nsDisplayOutline(aBuilder, this));
+    MakeDisplayItem<nsDisplayOutline>(aBuilder, this));
 }
 
 void
 nsFrame::DisplayOutline(nsDisplayListBuilder*   aBuilder,
                         const nsDisplayListSet& aLists)
 {
   if (!IsVisibleForPainting(aBuilder))
     return;
@@ -2346,17 +2346,17 @@ nsFrame::DisplayOutline(nsDisplayListBui
 
 void
 nsIFrame::DisplayCaret(nsDisplayListBuilder* aBuilder,
                        nsDisplayList* aList)
 {
   if (!IsVisibleForPainting(aBuilder))
     return;
 
-  aList->AppendToTop(new (aBuilder) nsDisplayCaret(aBuilder, this));
+  aList->AppendToTop(MakeDisplayItem<nsDisplayCaret>(aBuilder, this));
 }
 
 nscolor
 nsIFrame::GetCaretColorAt(int32_t aOffset)
 {
   return nsLayoutUtils::GetColor(this, &nsStyleUserInterface::mCaretColor);
 }
 
@@ -2385,33 +2385,33 @@ nsFrame::DisplayBorderBackgroundOutline(
   // opportunity to override the visibility property and display even if
   // their parent is hidden.
   if (!IsVisibleForPainting(aBuilder)) {
     return;
   }
 
   nsCSSShadowArray* shadows = StyleEffects()->mBoxShadow;
   if (shadows && shadows->HasShadowWithInset(false)) {
-    aLists.BorderBackground()->AppendToTop(new (aBuilder)
-      nsDisplayBoxShadowOuter(aBuilder, this));
+    aLists.BorderBackground()->AppendToTop(
+      MakeDisplayItem<nsDisplayBoxShadowOuter>(aBuilder, this));
   }
 
   bool bgIsThemed = DisplayBackgroundUnconditional(aBuilder, aLists,
                                                    aForceBackground);
 
   if (shadows && shadows->HasShadowWithInset(true)) {
-    aLists.BorderBackground()->AppendToTop(new (aBuilder)
-      nsDisplayBoxShadowInner(aBuilder, this));
+    aLists.BorderBackground()->AppendToTop(
+      MakeDisplayItem<nsDisplayBoxShadowInner>(aBuilder, this));
   }
 
   // If there's a themed background, we should not create a border item.
   // It won't be rendered.
   if (!bgIsThemed && StyleBorder()->HasBorder()) {
-    aLists.BorderBackground()->AppendToTop(new (aBuilder)
-      nsDisplayBorder(aBuilder, this));
+    aLists.BorderBackground()->AppendToTop(
+      MakeDisplayItem<nsDisplayBorder>(aBuilder, this));
   }
 
   DisplayOutlineUnconditional(aBuilder, aLists);
 }
 
 inline static bool IsSVGContentWithCSSClip(const nsIFrame *aFrame)
 {
   // The CSS spec says that the 'clip' property only applies to absolutely
@@ -2536,26 +2536,26 @@ static void PaintEventTargetBorder(nsIFr
 }
 
 static void
 DisplayDebugBorders(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                     const nsDisplayListSet& aLists) {
   // Draw a border around the child
   // REVIEW: From nsContainerFrame::PaintChild
   if (nsFrame::GetShowFrameBorders() && !aFrame->GetRect().IsEmpty()) {
-    aLists.Outlines()->AppendToTop(new (aBuilder)
-        nsDisplayGeneric(aBuilder, aFrame, PaintDebugBorder, "DebugBorder",
-                         DisplayItemType::TYPE_DEBUG_BORDER));
+    aLists.Outlines()->AppendToTop(
+        MakeDisplayItem<nsDisplayGeneric>(aBuilder, aFrame, PaintDebugBorder, "DebugBorder",
+                                          DisplayItemType::TYPE_DEBUG_BORDER));
   }
   // Draw a border around the current event target
   if (nsFrame::GetShowEventTargetFrameBorder() &&
       aFrame->PresShell()->GetDrawEventTargetFrame() == aFrame) {
-    aLists.Outlines()->AppendToTop(new (aBuilder)
-        nsDisplayGeneric(aBuilder, aFrame, PaintEventTargetBorder, "EventTargetBorder",
-                         DisplayItemType::TYPE_EVENT_TARGET_BORDER));
+    aLists.Outlines()->AppendToTop(
+        MakeDisplayItem<nsDisplayGeneric>(aBuilder, aFrame, PaintEventTargetBorder, "EventTargetBorder",
+                                          DisplayItemType::TYPE_EVENT_TARGET_BORDER));
   }
 }
 #endif
 
 static bool
 IsScrollFrameActive(nsDisplayListBuilder* aBuilder, nsIScrollableFrame* aScrollableFrame)
 {
   return aScrollableFrame && aScrollableFrame->IsScrollingActive(aBuilder);
@@ -2649,17 +2649,17 @@ ItemParticipatesIn3DContext(nsIFrame* aA
 }
 
 static void
 WrapSeparatorTransform(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                        nsDisplayList* aSource, nsDisplayList* aTarget,
                        int aIndex) {
   if (!aSource->IsEmpty()) {
     nsDisplayTransform *sepIdItem =
-      new (aBuilder) nsDisplayTransform(aBuilder, aFrame, aSource,
+      MakeDisplayItem<nsDisplayTransform>(aBuilder, aFrame, aSource,
                                         aBuilder->GetVisibleRect(), Matrix4x4(), aIndex);
     sepIdItem->SetNoExtendContext();
     aTarget->AppendToTop(sepIdItem);
   }
 }
 
 // Try to compute a clip rect to bound the contents of the mask item
 // that will be built for |aMaskedFrame|. If we're not able to compute
@@ -3026,17 +3026,17 @@ nsIFrame::BuildDisplayListForStackingCon
       // going to be forced to descend into frames.
       aBuilder->MarkPreserve3DFramesForDisplayList(this);
     }
 
     aBuilder->AdjustWindowDraggingRegion(this);
 
     nsDisplayLayerEventRegions* eventRegions = nullptr;
     if (aBuilder->IsBuildingLayerEventRegions()) {
-      eventRegions = new (aBuilder) nsDisplayLayerEventRegions(aBuilder, this);
+      eventRegions = MakeDisplayItem<nsDisplayLayerEventRegions>(aBuilder, this);
       eventRegions->AddFrame(aBuilder, this);
       aBuilder->SetLayerEventRegions(eventRegions);
     }
 
     aBuilder->BuildCompositorHitTestInfoIfNeeded(this, set.BorderBackground(),
                                                  true);
 
     MarkAbsoluteFramesForDisplayList(aBuilder);
@@ -3067,17 +3067,17 @@ nsIFrame::BuildDisplayListForStackingCon
       // If we did a partial build then delete all the items we just built
       // and repeat building with the full area.
       if (!aBuilder->GetDirtyRect().Contains(aBuilder->GetVisibleRect())) {
         aBuilder->SetDirtyRect(aBuilder->GetVisibleRect());
         set.DeleteAll(aBuilder);
 
         if (eventRegions) {
           eventRegions->Destroy(aBuilder);
-          eventRegions = new (aBuilder) nsDisplayLayerEventRegions(aBuilder, this);
+          eventRegions = MakeDisplayItem<nsDisplayLayerEventRegions>(aBuilder, this);
           eventRegions->AddFrame(aBuilder, this);
           aBuilder->SetLayerEventRegions(eventRegions);
         }
 
         aBuilder->BuildCompositorHitTestInfoIfNeeded(this,
                                                      set.BorderBackground(),
                                                      true);
 
@@ -3111,17 +3111,17 @@ nsIFrame::BuildDisplayListForStackingCon
     set.Floats()->DeleteAll(aBuilder);
     set.Content()->DeleteAll(aBuilder);
     set.PositionedDescendants()->DeleteAll(aBuilder);
     set.Outlines()->DeleteAll(aBuilder);
   }
 
   if (hasOverrideDirtyRect && gfxPrefs::LayoutDisplayListShowArea()) {
     nsDisplaySolidColor* color =
-     new (aBuilder) nsDisplaySolidColor(aBuilder, this,
+     MakeDisplayItem<nsDisplaySolidColor>(aBuilder, this,
                                         dirtyRect + aBuilder->GetCurrentFrameOffsetToReferenceFrame(),
                                         NS_RGBA(255, 0, 0, 64), false);
     color->SetOverrideZIndex(INT32_MAX);
     set.PositionedDescendants()->AppendToTop(color);
   }
 
   // Sort PositionedDescendants() in CSS 'z-order' order.  The list is already
   // in content document order and SortByZOrder is a stable sort which
@@ -3214,17 +3214,17 @@ nsIFrame::BuildDisplayListForStackingCon
     if (usingFilter && !aBuilder->IsForGenerateGlyphMask()) {
       // If we are going to create a mask display item, handle opacity effect
       // in that mask display item; Otherwise, take care of opacity in this
       // filter display item.
       bool handleOpacity = !usingMask && !useOpacity;
 
       /* List now emptied, so add the new list to the top. */
       resultList.AppendToTop(
-        new (aBuilder) nsDisplayFilter(aBuilder, this, &resultList,
+        MakeDisplayItem<nsDisplayFilter>(aBuilder, this, &resultList,
                                        handleOpacity));
     }
 
     if (usingMask) {
       DisplayListClipState::AutoSaveRestore maskClipState(aBuilder);
       // The mask should move with aBuilder->CurrentActiveScrolledRoot(), so
       // that's the ASR we prefer to use for the mask item. However, we can
       // only do this if the mask if clipped with respect to that ASR, because
@@ -3234,17 +3234,17 @@ nsIFrame::BuildDisplayListForStackingCon
       // the mask's contents. That's not entirely crrect, but it satisfies
       // the base requirement of the ASR system (that items have finite bounds
       // wrt. their ASR).
       const ActiveScrolledRoot* maskASR = clipForMask.isSome()
                                         ? aBuilder->CurrentActiveScrolledRoot()
                                         : containerItemASR;
       /* List now emptied, so add the new list to the top. */
       resultList.AppendToTop(
-          new (aBuilder) nsDisplayMask(aBuilder, this, &resultList, !useOpacity,
+          MakeDisplayItem<nsDisplayMask>(aBuilder, this, &resultList, !useOpacity,
                                        maskASR));
     }
 
     // Also add the hoisted scroll info items. We need those for APZ scrolling
     // because nsDisplayMask items can't build active layers.
     aBuilder->ExitSVGEffectsContents();
     resultList.AppendToTop(&hoistedScrollInfoItemsStorage);
     if (aCreatedContainerItem) {
@@ -3256,17 +3256,17 @@ nsIFrame::BuildDisplayListForStackingCon
    * effects, wrap it up in an opacity item.
    */
   if (useOpacity) {
     // Don't clip nsDisplayOpacity items. We clip their descendants instead.
     // The clip we would set on an element with opacity would clip
     // all descendant content, but some should not be clipped.
     DisplayListClipState::AutoSaveRestore opacityClipState(aBuilder);
     resultList.AppendToTop(
-        new (aBuilder) nsDisplayOpacity(aBuilder, this, &resultList,
+        MakeDisplayItem<nsDisplayOpacity>(aBuilder, this, &resultList,
                                         containerItemASR,
                                         opacityItemForEventsAndPluginsOnly));
     if (aCreatedContainerItem) {
       *aCreatedContainerItem = true;
     }
   }
 
   /* If we're going to apply a transformation and don't have preserve-3d set, wrap
@@ -3322,41 +3322,41 @@ nsIFrame::BuildDisplayListForStackingCon
     if (this != aBuilder->RootReferenceFrame()) {
       outerReferenceFrame =
         aBuilder->FindReferenceFrameFor(GetParent(), &toOuterReferenceFrame);
     }
     buildingDisplayList.SetReferenceFrameAndCurrentOffset(outerReferenceFrame,
       GetOffsetToCrossDoc(outerReferenceFrame));
 
     nsDisplayTransform *transformItem =
-      new (aBuilder) nsDisplayTransform(aBuilder, this,
+      MakeDisplayItem<nsDisplayTransform>(aBuilder, this,
                                         &resultList, visibleRect, 0,
                                         allowAsyncAnimation);
     resultList.AppendToTop(transformItem);
 
     if (hasPerspective) {
       if (clipCapturedBy == ContainerItemType::ePerspective) {
         clipState.Restore();
       }
       resultList.AppendToTop(
-        new (aBuilder) nsDisplayPerspective(
+        MakeDisplayItem<nsDisplayPerspective>(
           aBuilder, this,
           GetContainingBlock(0, disp)->GetContent()->GetPrimaryFrame(),
           &resultList));
     }
 
     if (aCreatedContainerItem) {
       *aCreatedContainerItem = true;
     }
   }
 
   if (clipCapturedBy == ContainerItemType::eOwnLayerForTransformWithRoundedClip) {
     clipState.Restore();
     resultList.AppendToTop(
-      new (aBuilder) nsDisplayOwnLayer(aBuilder, this, &resultList,
+      MakeDisplayItem<nsDisplayOwnLayer>(aBuilder, this, &resultList,
                                        aBuilder->CurrentActiveScrolledRoot(),
                                        nsDisplayOwnLayerFlags::eNone,
                                        mozilla::layers::FrameMetrics::NULL_SCROLL_ID,
                                        ScrollThumbData{}, /* aForceActive = */ false));
     if (aCreatedContainerItem) {
       *aCreatedContainerItem = true;
     }
   }
@@ -3369,47 +3369,47 @@ nsIFrame::BuildDisplayListForStackingCon
     }
     // The ASR for the fixed item should be the ASR of our containing block,
     // which has been set as the builder's current ASR, unless this frame is
     // invisible and we hadn't saved display item data for it. In that case,
     // we need to take the containerItemASR since we might have fixed children.
     const ActiveScrolledRoot* fixedASR =
       ActiveScrolledRoot::PickAncestor(containerItemASR, aBuilder->CurrentActiveScrolledRoot());
     resultList.AppendToTop(
-        new (aBuilder) nsDisplayFixedPosition(aBuilder, this, &resultList, fixedASR));
+        MakeDisplayItem<nsDisplayFixedPosition>(aBuilder, this, &resultList, fixedASR));
     if (aCreatedContainerItem) {
       *aCreatedContainerItem = true;
     }
   } else if (useStickyPosition) {
     // For position:sticky, the clip needs to be applied both to the sticky
     // container item and to the contents. The container item needs the clip
     // because a scrolled clip needs to move independently from the sticky
     // contents, and the contents need the clip so that they have finite
     // clipped bounds with respect to the container item's ASR. The latter is
     // a little tricky in the case where the sticky item has both fixed and
     // non-fixed descendants, because that means that the sticky container
     // item's ASR is the ASR of the fixed descendant.
     const ActiveScrolledRoot* stickyASR =
       ActiveScrolledRoot::PickAncestor(containerItemASR, aBuilder->CurrentActiveScrolledRoot());
     resultList.AppendToTop(
-        new (aBuilder) nsDisplayStickyPosition(aBuilder, this, &resultList, stickyASR));
+        MakeDisplayItem<nsDisplayStickyPosition>(aBuilder, this, &resultList, stickyASR));
     if (aCreatedContainerItem) {
       *aCreatedContainerItem = true;
     }
   }
 
   /* If there's blending, wrap up the list in a blend-mode item. Note
    * that opacity can be applied before blending as the blend color is
    * not affected by foreground opacity (only background alpha).
    */
 
   if (useBlendMode) {
     DisplayListClipState::AutoSaveRestore blendModeClipState(aBuilder);
     resultList.AppendToTop(
-        new (aBuilder) nsDisplayBlendMode(aBuilder, this, &resultList,
+        MakeDisplayItem<nsDisplayBlendMode>(aBuilder, this, &resultList,
                                           effects->mMixBlendMode,
                                           containerItemASR));
     if (aCreatedContainerItem) {
       *aCreatedContainerItem = true;
     }
   }
 
   CreateOwnLayerIfNeeded(aBuilder, &resultList, aCreatedContainerItem);
@@ -3431,17 +3431,17 @@ WrapInWrapList(nsDisplayListBuilder* aBu
   if (aCanSkipWrapList) {
     MOZ_ASSERT(!item->GetAbove());
     aList->RemoveBottom();
     return item;
   }
 
   // Clear clip rect for the construction of the items below. Since we're
   // clipping all their contents, they themselves don't need to be clipped.
-  return new (aBuilder) nsDisplayWrapList(aBuilder, aFrame, aList, aContainerASR, true);
+  return MakeDisplayItem<nsDisplayWrapList>(aBuilder, aFrame, aList, aContainerASR, true);
 }
 
 /**
  * Check if a frame should be visited for building display list.
  */
 static bool
 DescendIntoChild(nsDisplayListBuilder* aBuilder, nsIFrame *aChild,
                  const nsRect& aVisible, const nsRect& aDirty)
@@ -3772,17 +3772,17 @@ nsIFrame::BuildDisplayListForChild(nsDis
 
     aBuilder->BuildCompositorHitTestInfoIfNeeded(child, toList, differentAGR);
 
     if (aBuilder->IsBuildingLayerEventRegions()) {
       // If this frame has a different animated geometry root than its parent,
       // make sure we accumulate event regions for its layer.
       if (buildingForChild.IsAnimatedGeometryRoot() || isPositioned) {
         nsDisplayLayerEventRegions* eventRegions =
-          new (aBuilder) nsDisplayLayerEventRegions(aBuilder, child);
+          MakeDisplayItem<nsDisplayLayerEventRegions>(aBuilder, child);
         eventRegions->AddFrame(aBuilder, child);
         aBuilder->SetLayerEventRegions(eventRegions);
 
         if (isPositioned) {
           // We need this nsDisplayLayerEventRegions to be sorted with the positioned
           // elements as positioned elements will be sorted on top of normal elements
           list.AppendToTop(eventRegions);
         } else {
@@ -10961,18 +10961,18 @@ nsIFrame::SetParent(nsContainerFrame* aP
 void
 nsIFrame::CreateOwnLayerIfNeeded(nsDisplayListBuilder* aBuilder,
                                  nsDisplayList* aList,
                                  bool* aCreatedContainerItem)
 {
   if (GetContent() &&
       GetContent()->IsXULElement() &&
       GetContent()->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::layer)) {
-    aList->AppendToTop(new (aBuilder)
-        nsDisplayOwnLayer(aBuilder, this, aList, aBuilder->CurrentActiveScrolledRoot()));
+    aList->AppendToTop(
+        MakeDisplayItem<nsDisplayOwnLayer>(aBuilder, this, aList, aBuilder->CurrentActiveScrolledRoot()));
     if (aCreatedContainerItem) {
       *aCreatedContainerItem = true;
     }
   }
 }
 
 bool
 nsIFrame::IsSelected() const
--- a/layout/generic/nsFrameSetFrame.cpp
+++ b/layout/generic/nsFrameSetFrame.cpp
@@ -675,17 +675,17 @@ nsHTMLFramesetFrame::GetCursor(const nsP
 void
 nsHTMLFramesetFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                       const nsDisplayListSet& aLists)
 {
   BuildDisplayListForInline(aBuilder, aLists);
 
   if (mDragger && aBuilder->IsForEventDelivery()) {
     aLists.Content()->AppendToTop(
-      new (aBuilder) nsDisplayEventReceiver(aBuilder, this));
+      MakeDisplayItem<nsDisplayEventReceiver>(aBuilder, this));
   }
 }
 
 void
 nsHTMLFramesetFrame::ReflowPlaceChild(nsIFrame*                aChild,
                                       nsPresContext*           aPresContext,
                                       const ReflowInput& aReflowInput,
                                       nsPoint&                 aOffset,
@@ -1420,17 +1420,17 @@ void nsDisplayFramesetBorder::Paint(nsDi
     PaintBorder(aCtx->GetDrawTarget(), ToReferenceFrame());
 }
 
 void
 nsHTMLFramesetBorderFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                             const nsDisplayListSet& aLists)
 {
   aLists.Content()->AppendToTop(
-    new (aBuilder) nsDisplayFramesetBorder(aBuilder, this));
+    MakeDisplayItem<nsDisplayFramesetBorder>(aBuilder, this));
 }
 
 void nsHTMLFramesetBorderFrame::PaintBorder(DrawTarget* aDrawTarget,
                                             nsPoint aPt)
 {
   nscoord widthInPixels = nsPresContext::AppUnitsToIntCSSPixels(mWidth);
   nscoord pixelWidth    = nsPresContext::CSSPixelsToAppUnits(1);
 
@@ -1631,10 +1631,10 @@ void nsDisplayFramesetBlank::Paint(nsDis
   drawTarget->FillRect(rect, white);
 }
 
 void
 nsHTMLFramesetBlankFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                            const nsDisplayListSet& aLists)
 {
   aLists.Content()->AppendToTop(
-    new (aBuilder) nsDisplayFramesetBlank(aBuilder, this));
+    MakeDisplayItem<nsDisplayFramesetBlank>(aBuilder, this));
 }
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -3067,19 +3067,19 @@ AppendToTop(nsDisplayListBuilder* aBuild
 
     if (aFlags & APPEND_SCROLLBAR_CONTAINER) {
       scrollTarget = aBuilder->GetCurrentScrollbarTarget();
       // The flags here should be exactly one scrollbar direction
       MOZ_ASSERT(flags != nsDisplayOwnLayerFlags::eNone);
       flags |= nsDisplayOwnLayerFlags::eScrollbarContainer;
     }
 
-    newItem = new (aBuilder) nsDisplayOwnLayer(aBuilder, aSourceFrame, aSource, asr, flags, scrollTarget);
+    newItem = MakeDisplayItem<nsDisplayOwnLayer>(aBuilder, aSourceFrame, aSource, asr, flags, scrollTarget);
   } else {
-    newItem = new (aBuilder) nsDisplayWrapList(aBuilder, aSourceFrame, aSource, asr);
+    newItem = MakeDisplayItem<nsDisplayWrapList>(aBuilder, aSourceFrame, aSource, asr);
   }
 
   if (aFlags & APPEND_POSITIONED) {
     // We want overlay scrollbars to always be on top of the scrolled content,
     // but we don't want them to unnecessarily cover overlapping elements from
     // outside our scroll frame.
     int32_t zIndex = MaxZIndexInList(aLists.PositionedDescendants(), aBuilder);
     AppendInternalItemToTop(aLists, newItem, zIndex);
@@ -3613,19 +3613,19 @@ ScrollFrameHelper::BuildDisplayList(nsDi
 
       nsDisplayListBuilder::AutoBuildingDisplayList
         building(aBuilder, mOuter, visibleRect, dirtyRect, aBuilder->IsAtRootOfPseudoStackingContext());
 
       mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, scrolledContent);
 
       if (dirtyRectHasBeenOverriden && gfxPrefs::LayoutDisplayListShowArea()) {
         nsDisplaySolidColor* color =
-          new (aBuilder) nsDisplaySolidColor(aBuilder, mOuter,
-                                             dirtyRect + aBuilder->GetCurrentFrameOffsetToReferenceFrame(),
-                                             NS_RGBA(0, 0, 255, 64), false);
+          MakeDisplayItem<nsDisplaySolidColor>(aBuilder, mOuter,
+                                               dirtyRect + aBuilder->GetCurrentFrameOffsetToReferenceFrame(),
+                                               NS_RGBA(0, 0, 255, 64), false);
         color->SetOverrideZIndex(INT32_MAX);
         scrolledContent.PositionedDescendants()->AppendToTop(color);
       }
     }
 
     if (extraContentBoxClipForNonCaretContent) {
       // The items were built while the inflated content box clip was in
       // effect, so that the caret wasn't clipped unnecessarily. We apply
@@ -3690,31 +3690,31 @@ ScrollFrameHelper::BuildDisplayList(nsDi
         // fallback behaviour of scrolling the enclosing scroll frame would
         // violate the specified overscroll-behavior.
         ScrollbarStyles scrollbarStyles = GetScrollbarStylesFromFrame();
         if (scrollbarStyles.mOverscrollBehaviorX != StyleOverscrollBehavior::Auto ||
             scrollbarStyles.mOverscrollBehaviorY != StyleOverscrollBehavior::Auto) {
           info |= CompositorHitTestInfo::eRequiresTargetConfirmation;
         }
         nsDisplayCompositorHitTestInfo* hitInfo =
-            new (aBuilder) nsDisplayCompositorHitTestInfo(aBuilder, mScrolledFrame, info, 1,
+            MakeDisplayItem<nsDisplayCompositorHitTestInfo>(aBuilder, mScrolledFrame, info, 1,
                 Some(mScrollPort + aBuilder->ToReferenceFrame(mOuter)));
         AppendInternalItemToTop(scrolledContent, hitInfo, zIndex);
       }
       if (aBuilder->IsBuildingLayerEventRegions()) {
         nsDisplayLayerEventRegions* inactiveRegionItem =
-            new (aBuilder) nsDisplayLayerEventRegions(aBuilder, mScrolledFrame, 1);
+            MakeDisplayItem<nsDisplayLayerEventRegions>(aBuilder, mScrolledFrame, 1);
         inactiveRegionItem->AddInactiveScrollPort(mScrolledFrame, mScrollPort + aBuilder->ToReferenceFrame(mOuter));
         AppendInternalItemToTop(scrolledContent, inactiveRegionItem, zIndex);
       }
     }
 
     if (aBuilder->ShouldBuildScrollInfoItemsForHoisting()) {
       aBuilder->AppendNewScrollInfoItemForHoisting(
-        new (aBuilder) nsDisplayScrollInfoLayer(aBuilder, mScrolledFrame,
+        MakeDisplayItem<nsDisplayScrollInfoLayer>(aBuilder, mScrolledFrame,
                                                 mOuter));
     }
   }
   // Now display overlay scrollbars and the resizer, if we have one.
   AppendScrollPartsTo(aBuilder, scrolledContent, createLayersForScrollbars, true);
 
   scrolledContent.MoveTo(aLists);
 }
--- a/layout/generic/nsHTMLCanvasFrame.cpp
+++ b/layout/generic/nsHTMLCanvasFrame.cpp
@@ -477,17 +477,17 @@ nsHTMLCanvasFrame::BuildDisplayList(nsDi
   uint32_t clipFlags =
     nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition()) ?
     0 : DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT;
 
   DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox
     clip(aBuilder, this, clipFlags);
 
   aLists.Content()->AppendToTop(
-    new (aBuilder) nsDisplayCanvas(aBuilder, this));
+    MakeDisplayItem<nsDisplayCanvas>(aBuilder, this));
 
   DisplaySelectionOverlay(aBuilder, aLists.Content(),
                           nsISelectionDisplay::DISPLAY_IMAGES);
 }
 
 // get the offset into the content area of the image where aImg starts if it is a continuation.
 // from nsImageFrame
 nscoord
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -1844,48 +1844,48 @@ nsImageFrame::BuildDisplayList(nsDisplay
     bool imageOK = IMAGE_OK(contentState, true);
 
     // XXX(seth): The SizeIsAvailable check here should not be necessary - the
     // intention is that a non-null mImage means we have a size, but there is
     // currently some code that violates this invariant.
     if (!imageOK || !mImage || !SizeIsAvailable(currentRequest)) {
       // No image yet, or image load failed. Draw the alt-text and an icon
       // indicating the status
-      aLists.Content()->AppendToTop(new (aBuilder)
-        nsDisplayAltFeedback(aBuilder, this));
+      aLists.Content()->AppendToTop(
+        MakeDisplayItem<nsDisplayAltFeedback>(aBuilder, this));
 
       // This image is visible (we are being asked to paint it) but it's not
       // decoded yet. And we are not going to ask the image to draw, so this
       // may be the only chance to tell it that it should decode.
       if (currentRequest) {
         uint32_t status = 0;
         currentRequest->GetImageStatus(&status);
         if (!(status & imgIRequest::STATUS_DECODE_COMPLETE)) {
           MaybeDecodeForPredictedSize();
         }
         // Increase loading priority if the image is ready to be displayed.
         if (!(status & imgIRequest::STATUS_LOAD_COMPLETE)){
           currentRequest->BoostPriority(imgIRequest::CATEGORY_DISPLAY);
         }
       }
     } else {
-      aLists.Content()->AppendToTop(new (aBuilder)
-        nsDisplayImage(aBuilder, this, mImage, mPrevImage));
+      aLists.Content()->AppendToTop(
+        MakeDisplayItem<nsDisplayImage>(aBuilder, this, mImage, mPrevImage));
 
       // If we were previously displaying an icon, we're not anymore
       if (mDisplayingIcon) {
         gIconLoad->RemoveIconObserver(this);
         mDisplayingIcon = false;
       }
 
 #ifdef DEBUG
       if (GetShowFrameBorders() && GetImageMap()) {
-        aLists.Outlines()->AppendToTop(new (aBuilder)
-          nsDisplayGeneric(aBuilder, this, PaintDebugImageMap, "DebugImageMap",
-                           DisplayItemType::TYPE_DEBUG_IMAGE_MAP));
+        aLists.Outlines()->AppendToTop(
+          MakeDisplayItem<nsDisplayGeneric>(aBuilder, this, PaintDebugImageMap, "DebugImageMap",
+                                            DisplayItemType::TYPE_DEBUG_IMAGE_MAP));
       }
 #endif
     }
   }
 
   if (ShouldDisplaySelection()) {
     DisplaySelectionOverlay(aBuilder, aLists.Content(),
                             nsISelectionDisplay::DISPLAY_IMAGES);
--- a/layout/generic/nsPageFrame.cpp
+++ b/layout/generic/nsPageFrame.cpp
@@ -581,24 +581,24 @@ nsPageFrame::BuildDisplayList(nsDisplayL
     // can monkey with the contents if necessary.
     nsRect backgroundRect =
       nsRect(aBuilder->ToReferenceFrame(child), child->GetSize());
 
     PresContext()->GetPresShell()->AddCanvasBackgroundColorItem(
       *aBuilder, content, child, backgroundRect, NS_RGBA(0,0,0,0));
   }
 
-  content.AppendToTop(new (aBuilder) nsDisplayTransform(aBuilder, child,
+  content.AppendToTop(MakeDisplayItem<nsDisplayTransform>(aBuilder, child,
       &content, content.GetVisibleRect(), ::ComputePageTransform));
 
   set.Content()->AppendToTop(&content);
 
   if (PresContext()->IsRootPaginatedDocument()) {
-    set.Content()->AppendToTop(new (aBuilder)
-        nsDisplayHeaderFooter(aBuilder, this));
+    set.Content()->AppendToTop(
+        MakeDisplayItem<nsDisplayHeaderFooter>(aBuilder, this));
   }
 
   set.MoveTo(aLists);
 }
 
 //------------------------------------------------------------------------------
 void
 nsPageFrame::SetPageNumInfo(int32_t aPageNumber, int32_t aTotalPages)
--- a/layout/generic/nsPlaceholderFrame.cpp
+++ b/layout/generic/nsPlaceholderFrame.cpp
@@ -268,19 +268,19 @@ void
 nsPlaceholderFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                      const nsDisplayListSet& aLists)
 {
   DO_GLOBAL_REFLOW_COUNT_DSP("nsPlaceholderFrame");
 
 #ifdef DEBUG
   if (GetShowFrameBorders()) {
     aLists.Outlines()->AppendToTop(
-      new (aBuilder) nsDisplayGeneric(aBuilder, this, PaintDebugPlaceholder,
-                                      "DebugPlaceholder",
-                                      DisplayItemType::TYPE_DEBUG_PLACEHOLDER));
+      MakeDisplayItem<nsDisplayGeneric>(aBuilder, this, PaintDebugPlaceholder,
+                                        "DebugPlaceholder",
+                                        DisplayItemType::TYPE_DEBUG_PLACEHOLDER));
   }
 #endif
 }
 #endif // DEBUG || (MOZ_REFLOW_PERF_DSP && MOZ_REFLOW_PERF)
 
 #ifdef DEBUG_FRAME_DUMP
 nsresult
 nsPlaceholderFrame::GetFrameName(nsAString& aResult) const
--- a/layout/generic/nsPluginFrame.cpp
+++ b/layout/generic/nsPluginFrame.cpp
@@ -1182,34 +1182,34 @@ nsPluginFrame::BuildDisplayList(nsDispla
     }
   }
 
   DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox
     clip(aBuilder, this);
 
   // determine if we are printing
   if (type == nsPresContext::eContext_Print) {
-    aLists.Content()->AppendToTop(new (aBuilder)
-      nsDisplayGeneric(aBuilder, this, PaintPrintPlugin, "PrintPlugin",
-                       DisplayItemType::TYPE_PRINT_PLUGIN));
+    aLists.Content()->AppendToTop(
+      MakeDisplayItem<nsDisplayGeneric>(aBuilder, this, PaintPrintPlugin, "PrintPlugin",
+                                        DisplayItemType::TYPE_PRINT_PLUGIN));
   } else {
     LayerState state = GetLayerState(aBuilder, nullptr);
     if (state == LAYER_INACTIVE &&
         nsDisplayItem::ForceActiveLayers()) {
       state = LAYER_ACTIVE;
     }
     if (aBuilder->IsPaintingToWindow() &&
         state == LAYER_ACTIVE &&
         IsTransparentMode()) {
-      aLists.Content()->AppendToTop(new (aBuilder)
-        nsDisplayPluginReadback(aBuilder, this));
+      aLists.Content()->AppendToTop(
+        MakeDisplayItem<nsDisplayPluginReadback>(aBuilder, this));
     }
 
-    aLists.Content()->AppendToTop(new (aBuilder)
-      nsDisplayPlugin(aBuilder, this));
+    aLists.Content()->AppendToTop(
+      MakeDisplayItem<nsDisplayPlugin>(aBuilder, this));
   }
 }
 
 void
 nsPluginFrame::PrintPlugin(gfxContext& aRenderingContext,
                            const nsRect& aDirtyRect)
 {
   nsCOMPtr<nsIObjectLoadingContent> obj(do_QueryInterface(mContent));
--- a/layout/generic/nsSimplePageSequenceFrame.cpp
+++ b/layout/generic/nsSimplePageSequenceFrame.cpp
@@ -742,19 +742,19 @@ nsSimplePageSequenceFrame::BuildDisplayL
                            aBuilder->IsAtRootOfPseudoStackingContext());
         child->BuildDisplayListForStackingContext(aBuilder, &content);
         aBuilder->ResetMarkedFramesForDisplayList(this);
       }
       child = child->GetNextSibling();
     }
   }
 
-  content.AppendToTop(new (aBuilder)
-      nsDisplayTransform(aBuilder, this, &content, content.GetVisibleRect(),
-                         ::ComputePageSequenceTransform));
+  content.AppendToTop(
+      MakeDisplayItem<nsDisplayTransform>(aBuilder, this, &content, content.GetVisibleRect(),
+                                          ::ComputePageSequenceTransform));
 
   aLists.Content()->AppendToTop(&content);
 }
 
 //------------------------------------------------------------------------------
 void
 nsSimplePageSequenceFrame::SetPageNumberFormat(const nsAString& aFormatStr, bool aForPageNumOnly)
 {
--- a/layout/generic/nsSubDocumentFrame.cpp
+++ b/layout/generic/nsSubDocumentFrame.cpp
@@ -313,17 +313,17 @@ WrapBackgroundColorInOwnLayer(nsDisplayL
                               nsDisplayList* aList)
 {
   nsDisplayList tempItems;
   nsDisplayItem* item;
   while ((item = aList->RemoveBottom()) != nullptr) {
     if (item->GetType() == DisplayItemType::TYPE_BACKGROUND_COLOR) {
       nsDisplayList tmpList;
       tmpList.AppendToTop(item);
-      item = new (aBuilder) nsDisplayOwnLayer(aBuilder, aFrame, &tmpList, aBuilder->CurrentActiveScrolledRoot());
+      item = MakeDisplayItem<nsDisplayOwnLayer>(aBuilder, aFrame, &tmpList, aBuilder->CurrentActiveScrolledRoot());
     }
     tempItems.AppendToTop(item);
   }
   aList->AppendToTop(&tempItems);
 }
 
 void
 nsSubDocumentFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
@@ -553,36 +553,36 @@ nsSubDocumentFrame::BuildDisplayList(nsD
   // want to add nsDisplayOwnLayer::GENERATE_SCROLLABLE_LAYER to whatever layer
   // becomes the topmost. We do this below.
   if (constructZoomItem) {
     nsDisplayOwnLayerFlags zoomFlags = flags;
     if (ignoreViewportScrolling && !constructResolutionItem) {
       zoomFlags |= nsDisplayOwnLayerFlags::eGenerateScrollableLayer;
     }
     nsDisplayZoom* zoomItem =
-      new (aBuilder) nsDisplayZoom(aBuilder, subdocRootFrame, &childItems,
+      MakeDisplayItem<nsDisplayZoom>(aBuilder, subdocRootFrame, &childItems,
                                    subdocAPD, parentAPD, zoomFlags);
     childItems.AppendToTop(zoomItem);
     needsOwnLayer = false;
   }
   // Wrap the zoom item in the resolution item if we have both because we want the
   // resolution scale applied on top of the app units per dev pixel conversion.
   if (ignoreViewportScrolling) {
     flags |= nsDisplayOwnLayerFlags::eGenerateScrollableLayer;
   }
   if (constructResolutionItem) {
     nsDisplayResolution* resolutionItem =
-      new (aBuilder) nsDisplayResolution(aBuilder, subdocRootFrame, &childItems,
-                                         flags);
+      MakeDisplayItem<nsDisplayResolution>(aBuilder, subdocRootFrame, &childItems,
+                                           flags);
     childItems.AppendToTop(resolutionItem);
     needsOwnLayer = false;
   }
 
   // We always want top level content documents to be in their own layer.
-  nsDisplaySubDocument* layerItem = new (aBuilder) nsDisplaySubDocument(
+  nsDisplaySubDocument* layerItem = MakeDisplayItem<nsDisplaySubDocument>(
     aBuilder, subdocRootFrame ? subdocRootFrame : this, this,
     &childItems, flags);
   childItems.AppendToTop(layerItem);
   layerItem->SetShouldFlattenAway(!needsOwnLayer);
 
   // If we're using containers for root frames, then the earlier call
   // to AddCanvasBackgroundColorItem won't have been able to add an
   // unscrolled color item for overscroll. Try again now that we're
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -5232,17 +5232,17 @@ nsTextFrame::BuildDisplayList(nsDisplayL
       GetTextDecorations(PresContext(), eResolvedColors, textDecs);
       if (!textDecs.HasDecorationLines()) {
         return;
       }
     }
   }
 
   aLists.Content()->AppendToTop(
-    new (aBuilder) nsDisplayText(aBuilder, this, isSelected));
+    MakeDisplayItem<nsDisplayText>(aBuilder, this, isSelected));
 }
 
 static nsIFrame*
 GetGeneratedContentOwner(nsIFrame* aFrame, bool* aIsBefore)
 {
   *aIsBefore = false;
   while (aFrame && (aFrame->GetStateBits() & NS_FRAME_GENERATED_CONTENT)) {
     if (aFrame->StyleContext()->GetPseudo() == nsCSSPseudoElements::before) {
--- a/layout/generic/nsVideoFrame.cpp
+++ b/layout/generic/nsVideoFrame.cpp
@@ -565,17 +565,17 @@ nsVideoFrame::BuildDisplayList(nsDisplay
     clipFlags = 0;
   }
 
   DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox
     clip(aBuilder, this, clipFlags);
 
   if (HasVideoElement() && !shouldDisplayPoster) {
     aLists.Content()->AppendToTop(
-      new (aBuilder) nsDisplayVideo(aBuilder, this));
+      MakeDisplayItem<nsDisplayVideo>(aBuilder, this));
   }
 
   // Add child frames to display list. We expect various children,
   // but only want to draw mPosterImage conditionally. Others we
   // always add to the display list.
   for (nsIFrame* child : mFrames) {
     if (child->GetContent() != mPosterImage || shouldDisplayPoster ||
         child->IsBoxFrame()) {
--- a/layout/ipc/RenderFrameParent.cpp
+++ b/layout/ipc/RenderFrameParent.cpp
@@ -289,17 +289,17 @@ RenderFrameParent::BuildDisplayList(nsDi
   // painted content.  Display its shadow layer tree.
   DisplayListClipState::AutoSaveRestore clipState(aBuilder);
 
   nsPoint offset = aBuilder->ToReferenceFrame(aFrame);
   nsRect bounds = aFrame->EnsureInnerView()->GetBounds() + offset;
   clipState.ClipContentDescendants(bounds);
 
   aLists.Content()->AppendToTop(
-    new (aBuilder) nsDisplayRemote(aBuilder, aFrame, this));
+    MakeDisplayItem<nsDisplayRemote>(aBuilder, aFrame, this));
 }
 
 void
 RenderFrameParent::GetTextureFactoryIdentifier(TextureFactoryIdentifier* aTextureFactoryIdentifier)
 {
   RefPtr<LayerManager> lm = mFrameLoader ? GetLayerManager(mFrameLoader) : nullptr;
   // Perhaps the document containing this frame currently has no presentation?
   if (lm) {
--- a/layout/mathml/nsMathMLChar.cpp
+++ b/layout/mathml/nsMathMLChar.cpp
@@ -1983,41 +1983,41 @@ nsMathMLChar::Display(nsDisplayListBuild
   if (!styleContext->StyleVisibility()->IsVisible())
     return;
 
   // if the leaf style context that we use for stretchy chars has a background
   // color we use it -- this feature is mostly used for testing and debugging
   // purposes. Normally, users will set the background on the container frame.
   // paint the selection background -- beware MathML frames overlap a lot
   if (aSelectedRect && !aSelectedRect->IsEmpty()) {
-    aLists.BorderBackground()->AppendToTop(new (aBuilder)
-      nsDisplayMathMLSelectionRect(aBuilder, aForFrame, *aSelectedRect));
+    aLists.BorderBackground()->AppendToTop(
+      MakeDisplayItem<nsDisplayMathMLSelectionRect>(aBuilder, aForFrame, *aSelectedRect));
   }
   else if (mRect.width && mRect.height) {
     if (styleContext != parentContext &&
         NS_GET_A(styleContext->StyleBackground()->
                  BackgroundColor(styleContext)) > 0) {
       nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
         aBuilder, aForFrame, mRect, aLists.BorderBackground(),
         /* aAllowWillPaintBorderOptimization */ true, styleContext);
     }
     //else
     //  our container frame will take care of painting its background
 
 #if defined(DEBUG) && defined(SHOW_BOUNDING_BOX)
     // for visual debug
-    aLists.BorderBackground()->AppendToTop(new (aBuilder)
-      nsDisplayMathMLCharDebug(aBuilder, aForFrame, mRect));
+    aLists.BorderBackground()->AppendToTop(
+      MakeDisplayItem<nsDisplayMathMLCharDebug>(aBuilder, aForFrame, mRect));
 #endif
   }
-  aLists.Content()->AppendToTop(new (aBuilder)
-    nsDisplayMathMLCharForeground(aBuilder, aForFrame, this,
-                                  aIndex,
-                                  aSelectedRect &&
-                                  !aSelectedRect->IsEmpty()));
+  aLists.Content()->AppendToTop(
+    MakeDisplayItem<nsDisplayMathMLCharForeground>(aBuilder, aForFrame, this,
+                                                   aIndex,
+                                                   aSelectedRect &&
+                                                   !aSelectedRect->IsEmpty()));
 }
 
 void
 nsMathMLChar::ApplyTransforms(gfxContext* aThebesContext,
                               int32_t aAppUnitsPerGfxUnit,
                               nsRect &r)
 {
   // apply the transforms
--- a/layout/mathml/nsMathMLContainerFrame.cpp
+++ b/layout/mathml/nsMathMLContainerFrame.cpp
@@ -620,17 +620,17 @@ nsMathMLContainerFrame::BuildDisplayList
                                          const nsDisplayListSet& aLists)
 {
   // report an error if something wrong was found in this frame
   if (NS_MATHML_HAS_ERROR(mPresentationData.flags)) {
     if (!IsVisibleForPainting(aBuilder))
       return;
 
     aLists.Content()->AppendToTop(
-      new (aBuilder) nsDisplayMathMLError(aBuilder, this));
+      MakeDisplayItem<nsDisplayMathMLError>(aBuilder, this));
     return;
   }
 
   DisplayBorderBackgroundOutline(aBuilder, aLists);
 
   BuildDisplayListForNonBlockChildren(aBuilder, aLists, DISPLAY_CHILD_INLINE);
 
 #if defined(DEBUG) && defined(SHOW_BOUNDING_BOX)
--- a/layout/mathml/nsMathMLFrame.cpp
+++ b/layout/mathml/nsMathMLFrame.cpp
@@ -313,18 +313,18 @@ nsMathMLFrame::DisplayBoundingMetrics(ns
   if (!NS_MATHML_PAINT_BOUNDING_METRICS(mPresentationData.flags))
     return;
 
   nscoord x = aPt.x + aMetrics.leftBearing;
   nscoord y = aPt.y - aMetrics.ascent;
   nscoord w = aMetrics.rightBearing - aMetrics.leftBearing;
   nscoord h = aMetrics.ascent + aMetrics.descent;
 
-  aLists.Content()->AppendToTop(new (aBuilder)
-      nsDisplayMathMLBoundingMetrics(aBuilder, aFrame, nsRect(x,y,w,h)));
+  aLists.Content()->AppendToTop(
+      MakeDisplayItem<nsDisplayMathMLBoundingMetrics>(aBuilder, aFrame, nsRect(x,y,w,h)));
 }
 #endif
 
 class nsDisplayMathMLBar : public nsDisplayItem {
 public:
   nsDisplayMathMLBar(nsDisplayListBuilder* aBuilder,
                      nsIFrame* aFrame, const nsRect& aRect, uint32_t aIndex)
     : nsDisplayItem(aBuilder, aFrame), mRect(aRect), mIndex(aIndex) {
@@ -365,18 +365,18 @@ void nsDisplayMathMLBar::Paint(nsDisplay
 void
 nsMathMLFrame::DisplayBar(nsDisplayListBuilder* aBuilder,
                           nsIFrame* aFrame, const nsRect& aRect,
                           const nsDisplayListSet& aLists,
                           uint32_t aIndex) {
   if (!aFrame->StyleVisibility()->IsVisible() || aRect.IsEmpty())
     return;
 
-  aLists.Content()->AppendToTop(new (aBuilder)
-    nsDisplayMathMLBar(aBuilder, aFrame, aRect, aIndex));
+  aLists.Content()->AppendToTop(
+    MakeDisplayItem<nsDisplayMathMLBar>(aBuilder, aFrame, aRect, aIndex));
 }
 
 void
 nsMathMLFrame::GetRadicalParameters(nsFontMetrics* aFontMetrics,
                                     bool aDisplayStyle,
                                     nscoord& aRadicalRuleThickness,
                                     nscoord& aRadicalExtraAscender,
                                     nscoord& aRadicalVerticalGap)
--- a/layout/mathml/nsMathMLmencloseFrame.cpp
+++ b/layout/mathml/nsMathMLmencloseFrame.cpp
@@ -868,11 +868,11 @@ nsMathMLmencloseFrame::DisplayNotation(n
                                        const nsDisplayListSet& aLists,
                                        nscoord aThickness,
                                        nsMencloseNotation aType)
 {
   if (!aFrame->StyleVisibility()->IsVisible() || aRect.IsEmpty() ||
       aThickness <= 0)
     return;
 
-  aLists.Content()->AppendToTop(new (aBuilder)
-    nsDisplayNotation(aBuilder, aFrame, aRect, aThickness, aType));
+  aLists.Content()->AppendToTop(
+    MakeDisplayItem<nsDisplayNotation>(aBuilder, aFrame, aRect, aThickness, aType));
 }
--- a/layout/mathml/nsMathMLmfracFrame.cpp
+++ b/layout/mathml/nsMathMLmfracFrame.cpp
@@ -651,12 +651,12 @@ void nsDisplayMathMLSlash::Paint(nsDispl
 void
 nsMathMLmfracFrame::DisplaySlash(nsDisplayListBuilder* aBuilder,
                                  nsIFrame* aFrame, const nsRect& aRect,
                                  nscoord aThickness,
                                  const nsDisplayListSet& aLists) {
   if (!aFrame->StyleVisibility()->IsVisible() || aRect.IsEmpty())
     return;
 
-  aLists.Content()->AppendToTop(new (aBuilder)
-    nsDisplayMathMLSlash(aBuilder, aFrame, aRect, aThickness,
-                         StyleVisibility()->mDirection));
+  aLists.Content()->AppendToTop(
+    MakeDisplayItem<nsDisplayMathMLSlash>(aBuilder, aFrame, aRect, aThickness,
+                                          StyleVisibility()->mDirection));
 }
--- a/layout/mathml/nsMathMLmtableFrame.cpp
+++ b/layout/mathml/nsMathMLmtableFrame.cpp
@@ -1227,18 +1227,17 @@ nsMathMLmtdFrame::GetVerticalAlign() con
   return alignment;
 }
 
 nsresult
 nsMathMLmtdFrame::ProcessBorders(nsTableFrame* aFrame,
                                  nsDisplayListBuilder* aBuilder,
                                  const nsDisplayListSet& aLists)
 {
-  aLists.BorderBackground()->AppendToTop(new (aBuilder)
-                                            nsDisplaymtdBorder(aBuilder, this));
+  aLists.BorderBackground()->AppendToTop(MakeDisplayItem<nsDisplaymtdBorder>(aBuilder, this));
   return NS_OK;
 }
 
 LogicalMargin
 nsMathMLmtdFrame::GetBorderWidth(WritingMode aWM) const
 {
   nsStyleBorder styleBorder = *StyleBorder();
   ApplyBorderToStyle(this, styleBorder);
--- a/layout/painting/FrameLayerBuilder.cpp
+++ b/layout/painting/FrameLayerBuilder.cpp
@@ -140,16 +140,17 @@ DisplayItemData::DisplayItemData(LayerMa
   : mRefCnt(0)
   , mParent(aParent)
   , mLayer(aLayer)
   , mDisplayItemKey(aKey)
   , mItem(nullptr)
   , mUsed(true)
   , mIsInvalid(false)
   , mReusedItem(false)
+  , mDisconnected(false)
 {
   MOZ_COUNT_CTOR(DisplayItemData);
 
   if (!sAliveDisplayItemDatas) {
     sAliveDisplayItemDatas = new nsTHashtable<nsPtrHashKey<DisplayItemData>>();
   }
   MOZ_RELEASE_ASSERT(!sAliveDisplayItemDatas->Contains(this));
   sAliveDisplayItemDatas->PutEntry(this);
@@ -255,40 +256,53 @@ DisplayItemData::BeginUpdate(Layer* aLay
                                   copy[i]->GetVisualOverflowRect());
   }
 }
 
 static const nsIFrame* sDestroyedFrame = nullptr;
 DisplayItemData::~DisplayItemData()
 {
   MOZ_COUNT_DTOR(DisplayItemData);
-  MOZ_RELEASE_ASSERT(mLayer);
-  for (uint32_t i = 0; i < mFrameList.Length(); i++) {
-    nsIFrame* frame = mFrameList[i];
-    if (frame == sDestroyedFrame) {
-      continue;
-    }
-    SmallPointerArray<DisplayItemData>& array = frame->DisplayItemData();
-    array.RemoveElement(this);
-  }
+  Disconnect();
 
   MOZ_RELEASE_ASSERT(sAliveDisplayItemDatas);
   nsPtrHashKey<mozilla::DisplayItemData>* entry
     = sAliveDisplayItemDatas->GetEntry(this);
   MOZ_RELEASE_ASSERT(entry);
 
   sAliveDisplayItemDatas->RemoveEntry(entry);
 
   if (sAliveDisplayItemDatas->Count() == 0) {
     delete sAliveDisplayItemDatas;
     sAliveDisplayItemDatas = nullptr;
   }
 }
 
 void
+DisplayItemData::Disconnect()
+{
+  if (mDisconnected) {
+    return;
+  }
+  mDisconnected = true;
+
+  for (uint32_t i = 0; i < mFrameList.Length(); i++) {
+    nsIFrame* frame = mFrameList[i];
+    if (frame == sDestroyedFrame) {
+      continue;
+    }
+    SmallPointerArray<DisplayItemData>& array = frame->DisplayItemData();
+    array.RemoveElement(this);
+  }
+
+  mLayer = nullptr;
+  mOptLayer = nullptr;
+}
+
+void
 DisplayItemData::ClearAnimationCompositorState()
 {
   if (mDisplayItemKey != static_cast<uint32_t>(DisplayItemType::TYPE_TRANSFORM) &&
       mDisplayItemKey != static_cast<uint32_t>(DisplayItemType::TYPE_OPACITY)) {
     return;
   }
 
   for (nsIFrame* frame : mFrameList) {
@@ -324,16 +338,20 @@ public:
     , mParent(nullptr)
 #endif
     , mInvalidateAllLayers(false)
   {
     MOZ_COUNT_CTOR(LayerManagerData);
   }
   ~LayerManagerData() {
     MOZ_COUNT_DTOR(LayerManagerData);
+
+    for (auto iter = mDisplayItems.Iter(); !iter.Done(); iter.Next()) {
+      iter.Get()->GetKey()->Disconnect();
+    }
   }
 
 #ifdef DEBUG_DISPLAY_ITEM_DATA
   void Dump(const char *aPrefix = "") {
     printf_stderr("%sLayerManagerData %p\n", aPrefix, this);
 
     for (auto iter = mDisplayItems.Iter(); !iter.Done(); iter.Next()) {
       FrameLayerBuilder::DisplayItemData* data = iter.Get()->GetKey();
@@ -1926,29 +1944,30 @@ FrameLayerBuilder::RemoveFrameFromLayerM
       rootData = rootData->mParent;
     }
     printf_stderr("Removing frame %p - dumping display data\n", aFrame);
     rootData->Dump();
   }
 #endif
 
   for (DisplayItemData* data : aArray) {
-    PaintedLayer* t = data->mLayer->AsPaintedLayer();
+    PaintedLayer* t = data->mLayer ? data->mLayer->AsPaintedLayer() : nullptr;
     if (t) {
       PaintedDisplayItemLayerUserData* paintedData =
           static_cast<PaintedDisplayItemLayerUserData*>(t->GetUserData(&gPaintedDisplayItemLayerUserData));
       if (paintedData && data->mGeometry) {
         nsRegion old = data->mGeometry->ComputeInvalidationRegion();
         nsIntRegion rgn = old.ScaleToOutsidePixels(paintedData->mXScale, paintedData->mYScale, paintedData->mAppUnitsPerDevPixel);
         rgn.MoveBy(-GetTranslationForPaintedLayer(t));
         paintedData->mRegionToInvalidate.Or(paintedData->mRegionToInvalidate, rgn);
         paintedData->mRegionToInvalidate.SimplifyOutward(8);
       }
     }
 
+    data->Disconnect();
     data->mParent->mDisplayItems.RemoveEntry(data);
   }
 
   arrayCopy.Clear();
   sDestroyedFrame = nullptr;
 }
 
 void
@@ -2010,16 +2029,17 @@ FrameLayerBuilder::WillEndTransaction()
 #endif
         InvalidatePostTransformRegion(t,
                                       data->mGeometry->ComputeInvalidationRegion(),
                                       data->mClip,
                                       GetLastPaintOffset(t));
       }
 
       data->ClearAnimationCompositorState();
+      data->Disconnect();
       iter.Remove();
     } else {
       ComputeGeometryChangeForItem(data);
     }
   }
 
   data->mInvalidateAllLayers = false;
 }
@@ -2056,24 +2076,28 @@ FrameLayerBuilder::HasRetainedDataFor(ns
     if (data) {
       return true;
     }
   }
   return false;
 }
 
 DisplayItemData*
-FrameLayerBuilder::GetOldLayerForFrame(nsIFrame* aFrame, uint32_t aDisplayItemKey)
+FrameLayerBuilder::GetOldLayerForFrame(nsIFrame* aFrame, uint32_t aDisplayItemKey, DisplayItemData* aOldData /* = nullptr */)
 {
   // If we need to build a new layer tree, then just refuse to recycle
   // anything.
   if (!mRetainingManager || mInvalidateAllLayers)
     return nullptr;
 
-  DisplayItemData *data = GetDisplayItemData(aFrame, aDisplayItemKey);
+  DisplayItemData* data = aOldData;
+  if (!data || data->Disconnected() || data->mLayer->Manager() != mRetainingManager) {
+    data = GetDisplayItemData(aFrame, aDisplayItemKey);
+  }
+  MOZ_ASSERT(data == GetDisplayItemData(aFrame, aDisplayItemKey));
 
   if (data && data->mLayer->Manager() == mRetainingManager) {
     return data;
   }
   return nullptr;
 }
 
 Layer*
@@ -3131,17 +3155,19 @@ void ContainerState::FinishPaintedLayerD
     mNewChildLayers[data->mNewChildLayersIndex].mLayer = paintedLayer.forget();
   }
 
   for (auto& item : data->mAssignedDisplayItems) {
     MOZ_ASSERT(item.mItem->GetType() != DisplayItemType::TYPE_LAYER_EVENT_REGIONS);
     MOZ_ASSERT(item.mItem->GetType() != DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO);
 
     DisplayItemData* oldData =
-      mLayerBuilder->GetOldLayerForFrame(item.mItem->Frame(), item.mItem->GetPerFrameKey());
+      mLayerBuilder->GetOldLayerForFrame(item.mItem->Frame(),
+                                         item.mItem->GetPerFrameKey(),
+                                         item.mItem->HasMergedFrames() ? nullptr : item.mItem->GetDisplayItemData());
     InvalidateForLayerChange(item.mItem, data->mLayer, oldData);
     mLayerBuilder->AddPaintedDisplayItem(data, item.mItem, item.mClip,
                                          *this, item.mLayerState,
                                          data->mAnimatedGeometryRootOffset,
                                          oldData, item);
   }
 
   PaintedDisplayItemLayerUserData* userData = GetPaintedDisplayItemLayerUserData(data->mLayer);
@@ -4877,16 +4903,20 @@ FrameLayerBuilder::StoreDataForFrame(nsD
   }
 
   LayerManagerData* lmd = static_cast<LayerManagerData*>
     (mRetainingManager->GetUserData(&gLayerManagerUserData));
 
   RefPtr<DisplayItemData> data =
     new (aItem->Frame()->PresContext()) DisplayItemData(lmd, aItem->GetPerFrameKey(), aLayer);
 
+  if (!data->HasMergedFrames()) {
+    aItem->SetDisplayItemData(data);
+  }
+
   data->BeginUpdate(aLayer, aState, aItem);
 
   lmd->mDisplayItems.PutEntry(data);
   return data;
 }
 
 void
 FrameLayerBuilder::StoreDataForFrame(nsIFrame* aFrame,
--- a/layout/painting/FrameLayerBuilder.h
+++ b/layout/painting/FrameLayerBuilder.h
@@ -66,16 +66,18 @@ public:
   friend class ContainerState;
 
   uint32_t GetDisplayItemKey() { return mDisplayItemKey; }
   layers::Layer* GetLayer() const { return mLayer; }
   nsDisplayItemGeometry* GetGeometry() const { return mGeometry.get(); }
   void Invalidate() { mIsInvalid = true; }
   void ClearAnimationCompositorState();
 
+  bool HasMergedFrames() const { return mFrameList.Length() > 1; }
+
   static DisplayItemData* AssertDisplayItemData(DisplayItemData* aData);
 
   void* operator new(size_t sz, nsPresContext* aPresContext)
   {
     // Check the recycle list first.
     return aPresContext->PresShell()->
       AllocateByObjectID(eArenaObjectID_DisplayItemData, sz);
   }
@@ -99,16 +101,20 @@ public:
     NS_LOG_RELEASE(this, mRefCnt, "nsStyleContext");
     if (mRefCnt == 0) {
       Destroy();
       return 0;
     }
     return mRefCnt;
   }
 
+  void Disconnect();
+
+  bool Disconnected() { return mDisconnected; }
+
 private:
   DisplayItemData(LayerManagerData* aParent,
                   uint32_t aKey,
                   layers::Layer* aLayer,
                   nsIFrame* aFrame = nullptr);
 
   /**
     * Removes any references to this object from frames
@@ -181,16 +187,17 @@ private:
 
   /**
     * Used to track if data currently stored in mFramesWithLayers (from an existing
     * paint) has been updated in the current paint.
     */
   bool            mUsed;
   bool            mIsInvalid;
   bool            mReusedItem;
+  bool            mDisconnected;
 };
 
 class RefCountedRegion {
 private:
   ~RefCountedRegion() {}
 public:
   NS_INLINE_DECL_REFCOUNTING(RefCountedRegion)
 
@@ -617,17 +624,17 @@ public:
 
   /**
    * Given a frame and a display item key that uniquely identifies a
    * display item for the frame, find the layer that was last used to
    * render that display item. Returns null if there is no such layer.
    * This could be a dedicated layer for the display item, or a PaintedLayer
    * that renders many display items.
    */
-  DisplayItemData* GetOldLayerForFrame(nsIFrame* aFrame, uint32_t aDisplayItemKey);
+  DisplayItemData* GetOldLayerForFrame(nsIFrame* aFrame, uint32_t aDisplayItemKey, DisplayItemData* aOldData = nullptr);
 
 protected:
 
   friend class LayerManagerData;
 
   /**
    * Stores DisplayItemData associated with aFrame, stores the data in
    * mNewDisplayItemData.
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -2252,17 +2252,17 @@ nsDisplayListBuilder::BuildCompositorHit
   CompositorHitTestInfo info = aFrame->GetCompositorHitTestInfo(this);
   if (!ShouldBuildCompositorHitTestInfo(aFrame, info, aBuildNew)) {
     // Either the parent hit test info can be reused, or this frame has no hit
     // test flags set.
     return;
   }
 
   nsDisplayCompositorHitTestInfo* item =
-    new (this) nsDisplayCompositorHitTestInfo(this, aFrame, info);
+    MakeDisplayItem<nsDisplayCompositorHitTestInfo>(this, aFrame, info);
 
   SetCompositorHitTestInfo(item);
   aList->AppendToTop(item);
 }
 
 bool
 nsDisplayListBuilder::ShouldBuildCompositorHitTestInfo(const nsIFrame* aFrame,
                                                        const CompositorHitTestInfo& aInfo,
@@ -3526,19 +3526,20 @@ nsDisplayBackgroundImage::GetInitData(ns
   }
   return InitData{
     aBuilder, aFrame, aBackgroundStyle, image, aBackgroundRect,
     state.mFillArea, state.mDestArea, aLayer, isRasterImage,
     shouldFixToViewport
   };
 }
 
-nsDisplayBackgroundImage::nsDisplayBackgroundImage(const InitData& aInitData,
+nsDisplayBackgroundImage::nsDisplayBackgroundImage(nsDisplayListBuilder* aBuilder,
+                                                   const InitData& aInitData,
                                                    nsIFrame* aFrameForBounds)
-  : nsDisplayImageContainer(aInitData.builder, aInitData.frame)
+  : nsDisplayImageContainer(aBuilder, aInitData.frame)
   , mBackgroundStyle(aInitData.backgroundStyle)
   , mImage(aInitData.image)
   , mDependentFrame(nullptr)
   , mBackgroundRect(aInitData.backgroundRect)
   , mFillRect(aInitData.fillArea)
   , mDestRect(aInitData.destArea)
   , mLayer(aInitData.layer)
   , mIsRasterImage(aInitData.isRasterImage)
@@ -3638,17 +3639,17 @@ SpecialCutoutRegionCase(nsDisplayListBui
   if (NS_GET_A(aColor) == 0) {
     return true;
   }
 
   nsRegion region;
   region.Sub(aBackgroundRect, *static_cast<nsRegion*>(cutoutRegion));
   region.MoveBy(aBuilder->ToReferenceFrame(aFrame));
   aList->AppendToTop(
-    new (aBuilder) nsDisplaySolidColorRegion(aBuilder, aFrame, region, aColor));
+    MakeDisplayItem<nsDisplaySolidColorRegion>(aBuilder, aFrame, region, aColor));
 
   return true;
 }
 
 
 /*static*/ bool
 nsDisplayBackgroundImage::AppendBackgroundItemsToTop(nsDisplayListBuilder* aBuilder,
                                                      nsIFrame* aFrame,
@@ -3743,46 +3744,46 @@ nsDisplayBackgroundImage::AppendBackgrou
       if (clip.mHasRoundedCorners) {
         clipState.emplace(aBuilder);
         clipState->ClipContentDescendants(clip.mBGClipArea, clip.mRadii);
       }
     }
     nsDisplayBackgroundColor *bgItem;
     if (aSecondaryReferenceFrame) {
       bgItem =
-          new (aBuilder) nsDisplayTableBackgroundColor(aBuilder, aSecondaryReferenceFrame, bgColorRect, bg,
+          MakeDisplayItem<nsDisplayTableBackgroundColor>(aBuilder, aSecondaryReferenceFrame, bgColorRect, bg,
                                                        drawBackgroundColor ? color : NS_RGBA(0, 0, 0, 0),
                                                        aFrame);
     } else {
       bgItem =
-          new (aBuilder) nsDisplayBackgroundColor(aBuilder, aFrame, bgColorRect, bg,
+          MakeDisplayItem<nsDisplayBackgroundColor>(aBuilder, aFrame, bgColorRect, bg,
                                                   drawBackgroundColor ? color : NS_RGBA(0, 0, 0, 0));
     }
     bgItem->SetDependentFrame(aBuilder, dependentFrame);
     bgItemList.AppendToTop(bgItem);
   }
 
   if (isThemed) {
     nsITheme* theme = presContext->GetTheme();
     if (theme->NeedToClearBackgroundBehindWidget(aFrame, aFrame->StyleDisplay()->mAppearance) &&
         aBuilder->IsInChromeDocumentOrPopup() && !aBuilder->IsInTransform()) {
       bgItemList.AppendToTop(
-        new (aBuilder) nsDisplayClearBackground(aBuilder, aFrame));
+        MakeDisplayItem<nsDisplayClearBackground>(aBuilder, aFrame));
     }
     if (aSecondaryReferenceFrame) {
       nsDisplayTableThemedBackground* bgItem =
-        new (aBuilder) nsDisplayTableThemedBackground(aBuilder,
+        MakeDisplayItem<nsDisplayTableThemedBackground>(aBuilder,
                                                       aSecondaryReferenceFrame,
                                                       bgRect,
                                                       aFrame);
       bgItem->Init(aBuilder);
       bgItemList.AppendToTop(bgItem);
     } else {
       nsDisplayThemedBackground* bgItem =
-        new (aBuilder) nsDisplayThemedBackground(aBuilder, aFrame, bgRect);
+        MakeDisplayItem<nsDisplayThemedBackground>(aBuilder, aFrame, bgRect);
       bgItem->Init(aBuilder);
       bgItemList.AppendToTop(bgItem);
     }
     aList->AppendToTop(&bgItemList);
     return true;
   }
 
   if (!bg) {
@@ -3846,19 +3847,19 @@ nsDisplayBackgroundImage::AppendBackgrou
         // The clip is captured by the nsDisplayFixedPosition, so clear the
         // clip for the nsDisplayBackgroundImage inside.
         DisplayListClipState::AutoSaveRestore bgImageClip(aBuilder);
         bgImageClip.Clear();
         if (aSecondaryReferenceFrame) {
           nsDisplayBackgroundImage::InitData tableData = bgData;
           nsIFrame* styleFrame = tableData.frame;
           tableData.frame = aSecondaryReferenceFrame;
-          bgItem = new (aBuilder) nsDisplayTableBackgroundImage(tableData, styleFrame);
+          bgItem = MakeDisplayItem<nsDisplayTableBackgroundImage>(aBuilder, tableData, styleFrame);
         } else {
-          bgItem = new (aBuilder) nsDisplayBackgroundImage(bgData);
+          bgItem = MakeDisplayItem<nsDisplayBackgroundImage>(aBuilder, bgData);
         }
       }
       bgItem->SetDependentFrame(aBuilder, dependentFrame);
       if (aSecondaryReferenceFrame) {
         thisItemList.AppendToTop(
           nsDisplayTableFixedPosition::CreateForFixedBackground(aBuilder,
                                                                 aSecondaryReferenceFrame,
                                                                 bgItem,
@@ -3871,37 +3872,37 @@ nsDisplayBackgroundImage::AppendBackgrou
 
     } else {
       nsDisplayBackgroundImage* bgItem;
       if (aSecondaryReferenceFrame) {
         nsDisplayBackgroundImage::InitData tableData = bgData;
         nsIFrame* styleFrame = tableData.frame;
         tableData.frame = aSecondaryReferenceFrame;
 
-        bgItem = new (aBuilder) nsDisplayTableBackgroundImage(tableData, styleFrame);
+        bgItem = MakeDisplayItem<nsDisplayTableBackgroundImage>(aBuilder, tableData, styleFrame);
       } else {
-        bgItem = new (aBuilder) nsDisplayBackgroundImage(bgData);
+        bgItem = MakeDisplayItem<nsDisplayBackgroundImage>(aBuilder, bgData);
       }
       bgItem->SetDependentFrame(aBuilder, dependentFrame);
       thisItemList.AppendToTop(bgItem);
     }
 
     if (bg->mImage.mLayers[i].mBlendMode != NS_STYLE_BLEND_NORMAL) {
       DisplayListClipState::AutoSaveRestore blendClip(aBuilder);
       // asr is scrolled. Even if we wrap a fixed background layer, that's
       // fine, because the item will have a scrolled clip that limits the
       // item with respect to asr.
       if (aSecondaryReferenceFrame) {
         thisItemList.AppendToTop(
-          new (aBuilder) nsDisplayTableBlendMode(aBuilder, aSecondaryReferenceFrame, &thisItemList,
+          MakeDisplayItem<nsDisplayTableBlendMode>(aBuilder, aSecondaryReferenceFrame, &thisItemList,
                                                  bg->mImage.mLayers[i].mBlendMode,
                                                  asr, i + 1, aFrame));
       } else {
         thisItemList.AppendToTop(
-          new (aBuilder) nsDisplayBlendMode(aBuilder, aFrame, &thisItemList,
+          MakeDisplayItem<nsDisplayBlendMode>(aBuilder, aFrame, &thisItemList,
                                             bg->mImage.mLayers[i].mBlendMode,
                                             asr, i + 1));
       }
     }
     bgItemList.AppendToTop(&thisItemList);
   }
 
   if (needBlendContainer) {
@@ -4394,19 +4395,20 @@ nsDisplayBackgroundImage::GetBoundsInter
     clipRect = canvasFrame->CanvasArea() + ToReferenceFrame();
   }
   const nsStyleImageLayers::Layer& layer = mBackgroundStyle->mImage.mLayers[mLayer];
   return nsCSSRendering::GetBackgroundLayerRect(presContext, frame,
                                                 mBackgroundRect, clipRect, layer,
                                                 aBuilder->GetBackgroundPaintFlags());
 }
 
-nsDisplayTableBackgroundImage::nsDisplayTableBackgroundImage(const InitData& aData,
+nsDisplayTableBackgroundImage::nsDisplayTableBackgroundImage(nsDisplayListBuilder* aBuilder,
+                                                             const InitData& aData,
                                                              nsIFrame* aCellFrame)
-  : nsDisplayBackgroundImage(aData, aCellFrame)
+  : nsDisplayBackgroundImage(aBuilder, aData, aCellFrame)
   , mStyleFrame(aCellFrame)
   , mTableType(GetTableTypeFromFrame(mStyleFrame))
 {
 }
 
 bool
 nsDisplayTableBackgroundImage::IsInvalid(nsRect& aRect) const
 {
@@ -6263,17 +6265,17 @@ void
 nsDisplayWrapList::MergeDisplayListFromItem(nsDisplayListBuilder* aBuilder,
                                             const nsDisplayItem* aItem)
 {
   const nsDisplayWrapList* wrappedItem = aItem->AsDisplayWrapList();
   MOZ_ASSERT(wrappedItem);
 
   // Create a new nsDisplayWrapList using a copy-constructor. This is done
   // to preserve the information about bounds.
-  nsDisplayWrapList* wrapper = new (aBuilder) nsDisplayWrapList(aBuilder, *wrappedItem);
+  nsDisplayWrapList* wrapper = MakeDisplayItem<nsDisplayWrapList>(aBuilder, *wrappedItem);
 
   // Set the display list pointer of the new wrapper item to the display list
   // of the wrapped item.
   wrapper->mListPtr = wrappedItem->mListPtr;
 
   mListPtr->AppendToBottom(wrapper);
 }
 
@@ -6915,25 +6917,25 @@ nsDisplayBlendMode::CanMerge(const nsDis
   return true;
 }
 
 /* static */ nsDisplayBlendContainer*
 nsDisplayBlendContainer::CreateForMixBlendMode(nsDisplayListBuilder* aBuilder,
                                                nsIFrame* aFrame, nsDisplayList* aList,
                                                const ActiveScrolledRoot* aActiveScrolledRoot)
 {
-  return new (aBuilder) nsDisplayBlendContainer(aBuilder, aFrame, aList, aActiveScrolledRoot, false);
+  return MakeDisplayItem<nsDisplayBlendContainer>(aBuilder, aFrame, aList, aActiveScrolledRoot, false);
 }
 
 /* static */ nsDisplayBlendContainer*
 nsDisplayBlendContainer::CreateForBackgroundBlendMode(nsDisplayListBuilder* aBuilder,
                                                       nsIFrame* aFrame, nsDisplayList* aList,
                                                       const ActiveScrolledRoot* aActiveScrolledRoot)
 {
-  return new (aBuilder) nsDisplayBlendContainer(aBuilder, aFrame, aList, aActiveScrolledRoot, true);
+  return MakeDisplayItem<nsDisplayBlendContainer>(aBuilder, aFrame, aList, aActiveScrolledRoot, true);
 }
 
 nsDisplayBlendContainer::nsDisplayBlendContainer(nsDisplayListBuilder* aBuilder,
                                                  nsIFrame* aFrame, nsDisplayList* aList,
                                                  const ActiveScrolledRoot* aActiveScrolledRoot,
                                                  bool aIsForBackground)
     : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, true)
     , mIsForBackground(aIsForBackground)
@@ -6990,17 +6992,17 @@ nsDisplayBlendContainer::CreateWebRender
 }
 
 /* static */ nsDisplayTableBlendContainer*
 nsDisplayTableBlendContainer::CreateForBackgroundBlendMode(nsDisplayListBuilder* aBuilder,
                                                            nsIFrame* aFrame, nsDisplayList* aList,
                                                            const ActiveScrolledRoot* aActiveScrolledRoot,
                                                            nsIFrame* aAncestorFrame)
 {
-  return new (aBuilder) nsDisplayTableBlendContainer(aBuilder, aFrame, aList, aActiveScrolledRoot, true, aAncestorFrame);
+  return MakeDisplayItem<nsDisplayTableBlendContainer>(aBuilder, aFrame, aList, aActiveScrolledRoot, true, aAncestorFrame);
 }
 
 nsDisplayOwnLayer::nsDisplayOwnLayer(nsDisplayListBuilder* aBuilder,
                                      nsIFrame* aFrame, nsDisplayList* aList,
                                      const ActiveScrolledRoot* aActiveScrolledRoot,
                                      nsDisplayOwnLayerFlags aFlags, ViewID aScrollTarget,
                                      const ScrollThumbData& aThumbData,
                                      bool aForceActive,
@@ -7369,17 +7371,17 @@ nsDisplayFixedPosition::Init(nsDisplayLi
 nsDisplayFixedPosition::CreateForFixedBackground(nsDisplayListBuilder* aBuilder,
                                                  nsIFrame* aFrame,
                                                  nsDisplayBackgroundImage* aImage,
                                                  uint32_t aIndex)
 {
   nsDisplayList temp;
   temp.AppendToTop(aImage);
 
-  return new (aBuilder) nsDisplayFixedPosition(aBuilder, aFrame, &temp, aIndex + 1);
+  return MakeDisplayItem<nsDisplayFixedPosition>(aBuilder, aFrame, &temp, aIndex + 1);
 }
 
 
 #ifdef NS_BUILD_REFCNT_LOGGING
 nsDisplayFixedPosition::~nsDisplayFixedPosition() {
   MOZ_COUNT_DTOR(nsDisplayFixedPosition);
 }
 #endif
@@ -7482,17 +7484,17 @@ nsDisplayTableFixedPosition::CreateForFi
                                                       nsIFrame* aFrame,
                                                       nsDisplayBackgroundImage* aImage,
                                                       uint32_t aIndex,
                                                       nsIFrame* aAncestorFrame)
 {
   nsDisplayList temp;
   temp.AppendToTop(aImage);
 
-  return new (aBuilder) nsDisplayTableFixedPosition(aBuilder, aFrame, &temp, aIndex + 1, aAncestorFrame);
+  return MakeDisplayItem<nsDisplayTableFixedPosition>(aBuilder, aFrame, &temp, aIndex + 1, aAncestorFrame);
 }
 
 nsDisplayStickyPosition::nsDisplayStickyPosition(nsDisplayListBuilder* aBuilder,
                                                  nsIFrame* aFrame,
                                                  nsDisplayList* aList,
                                                  const ActiveScrolledRoot* aActiveScrolledRoot)
   : nsDisplayOwnLayer(aBuilder, aFrame, aList, aActiveScrolledRoot)
 {
--- a/layout/painting/nsDisplayList.h
+++ b/layout/painting/nsDisplayList.h
@@ -125,20 +125,24 @@ typedef mozilla::EnumSet<mozilla::gfx::C
  * Both the display root and any frame with a transform act as a reference frame
  * for their frame subtrees.
  */
 
 // All types are defined in nsDisplayItemTypes.h
 #define NS_DISPLAY_DECL_NAME(n, e) \
   virtual const char* Name() const override { return n; } \
   virtual DisplayItemType GetType() const override { return DisplayItemType::e; } \
+private: \
   void* operator new(size_t aSize, \
                      nsDisplayListBuilder* aBuilder) { \
     return aBuilder->Allocate(aSize, DisplayItemType::e); \
-  }
+  } \
+  template<typename T, typename... Args> \
+  friend T* ::MakeDisplayItem(nsDisplayListBuilder* aBuilder, Args&&... aArgs); \
+public:
 
 
 /**
  * Represents a frame that is considered to have (or will have) "animated geometry"
  * for itself and descendant frames.
  *
  * For example the scrolled frames of scrollframes which are actively being scrolled
  * fall into this category. Frames with certain CSS properties that are being animated
@@ -2020,16 +2024,37 @@ protected:
   nsDisplayItemLink(const nsDisplayItemLink&) : mAbove(nullptr) {}
   nsDisplayItem* mAbove;
 
   friend class nsDisplayList;
 };
 
 class nsDisplayWrapList;
 
+template<typename T, typename... Args>
+MOZ_ALWAYS_INLINE T*
+MakeDisplayItem(nsDisplayListBuilder* aBuilder, Args&&... aArgs)
+{
+  T* item = new (aBuilder) T(aBuilder, mozilla::Forward<Args>(aArgs)...);
+
+  const mozilla::SmallPointerArray<mozilla::DisplayItemData>& array =
+    item->Frame()->DisplayItemData();
+  for (uint32_t i = 0; i < array.Length(); i++) {
+    mozilla::DisplayItemData* did = array.ElementAt(i);
+    if (did->GetDisplayItemKey() == item->GetPerFrameKey()) {
+      if (!did->HasMergedFrames()) {
+        item->SetDisplayItemData(did);
+      }
+      break;
+    }
+  }
+
+  return item;
+}
+
 /**
  * This is the unit of rendering and event testing. Each instance of this
  * class represents an entity that can be drawn on the screen, e.g., a
  * frame's CSS background, or a frame's text string.
  *
  * nsDisplayItems can be containers --- i.e., they can perform hit testing
  * and painting by recursively traversing a list of child items.
  *
@@ -2111,16 +2136,17 @@ public:
     mDisableSubpixelAA = false;
   }
 
   virtual void RemoveFrame(nsIFrame* aFrame)
   {
     if (mFrame && aFrame == mFrame) {
       MOZ_ASSERT(!mFrame->HasDisplayItem(this));
       mFrame = nullptr;
+      mDisplayItemData = nullptr;
     }
   }
 
   /**
    * Downcasts this item to nsDisplayWrapList, if possible.
    */
   virtual const nsDisplayWrapList* AsDisplayWrapList() const { return nullptr; }
   virtual nsDisplayWrapList* AsDisplayWrapList() { return nullptr; }
@@ -2577,16 +2603,18 @@ public:
 
   /**
    * Appends the underlying frames of all display items that have been
    * merged into this one (excluding  this item's own underlying frame)
    * to aFrames.
    */
   virtual void GetMergedFrames(nsTArray<nsIFrame*>* aFrames) const {}
 
+  virtual bool HasMergedFrames() const { return false; }
+
   /**
    * During the visibility computation and after TryMerge, display lists may
    * return true here to flatten themselves away, removing them. This
    * flattening is distinctly different from FlattenTo, which occurs before
    * items are merged together.
    */
   virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) {
     return false;
@@ -2802,32 +2830,39 @@ public:
   {
     return nullptr;
   }
 
   virtual mozilla::Maybe<nsRect> GetClipWithRespectToASR(
       nsDisplayListBuilder* aBuilder,
       const ActiveScrolledRoot* aASR) const;
 
+  void SetDisplayItemData(mozilla::DisplayItemData* aDID) {
+    mDisplayItemData = aDID;
+  }
+
+  mozilla::DisplayItemData* GetDisplayItemData() { return mDisplayItemData; }
+
 protected:
   nsDisplayItem() = delete;
 
   typedef bool (*PrefFunc)(void);
   bool ShouldUseAdvancedLayer(LayerManager* aManager, PrefFunc aFunc) const;
   bool CanUseAdvancedLayer(LayerManager* aManager) const;
 
   nsIFrame* mFrame;
   RefPtr<const DisplayItemClipChain> mClipChain;
   const DisplayItemClip* mClip;
   RefPtr<const ActiveScrolledRoot> mActiveScrolledRoot;
   // Result of FindReferenceFrameFor(mFrame), if mFrame is non-null
   const nsIFrame* mReferenceFrame;
   RefPtr<struct AnimatedGeometryRoot> mAnimatedGeometryRoot;
   // Result of ToReferenceFrame(mFrame), if mFrame is non-null
   nsPoint   mToReferenceFrame;
+  RefPtr<mozilla::DisplayItemData> mDisplayItemData;
   // This is the rectangle that needs to be painted.
   // Display item construction sets this to the dirty rect.
   // nsDisplayList::ComputeVisibility sets this to the visible region
   // of the item by intersecting the current visible region with the bounds
   // of the item. Paint implementations can use this to limit their drawing.
   // Guaranteed to be contained in GetBounds().
   nsRect    mVisibleRect;
   bool      mForceNotVisible;
@@ -3381,30 +3416,33 @@ public:
     if (mPaint) {
       mPaint(mFrame, aCtx->GetDrawTarget(), mVisibleRect, ToReferenceFrame());
     } else {
       mOldPaint(mFrame, aCtx, mVisibleRect, ToReferenceFrame());
     }
   }
   virtual const char* Name() const override { return mName; }
   virtual DisplayItemType GetType() const override { return mType; }
-  void* operator new(size_t aSize,
-                     nsDisplayListBuilder* aBuilder) {
-    return aBuilder->Allocate(aSize, DisplayItemType::TYPE_GENERIC);
-  }
 
   // This override is needed because GetType() for nsDisplayGeneric subclasses
   // does not match TYPE_GENERIC that was used to allocate the object.
   virtual void Destroy(nsDisplayListBuilder* aBuilder) override
   {
     this->~nsDisplayGeneric();
     aBuilder->Destroy(DisplayItemType::TYPE_GENERIC, this);
   }
 
 protected:
+  void* operator new(size_t aSize,
+                     nsDisplayListBuilder* aBuilder) {
+    return aBuilder->Allocate(aSize, DisplayItemType::TYPE_GENERIC);
+  }
+  template<typename T, typename... Args>
+  friend T* MakeDisplayItem(nsDisplayListBuilder* aBuilder, Args&&... aArgs);
+
   PaintCallback mPaint;
   OldPaintCallback mOldPaint;   // XXX: should be removed eventually
   const char* mName;
   DisplayItemType mType;
 };
 
 #if defined(MOZ_REFLOW_PERF_DSP) && defined(MOZ_REFLOW_PERF)
 /**
@@ -3448,26 +3486,26 @@ protected:
   nscolor mColor;
 };
 
 #define DO_GLOBAL_REFLOW_COUNT_DSP(_name)                                     \
   PR_BEGIN_MACRO                                                              \
     if (!aBuilder->IsBackgroundOnly() && !aBuilder->IsForEventDelivery() &&   \
         PresShell()->IsPaintingFrameCounts()) {                               \
         aLists.Outlines()->AppendToTop(                                    \
-            new (aBuilder) nsDisplayReflowCount(aBuilder, this, _name));      \
+            MakeDisplayItem<nsDisplayReflowCount>(aBuilder, this, _name));      \
     }                                                                         \
   PR_END_MACRO
 
 #define DO_GLOBAL_REFLOW_COUNT_DSP_COLOR(_name, _color)                       \
   PR_BEGIN_MACRO                                                              \
     if (!aBuilder->IsBackgroundOnly() && !aBuilder->IsForEventDelivery() &&   \
         PresShell()->IsPaintingFrameCounts()) {                               \
         aLists.Outlines()->AppendToTop(                                    \
-             new (aBuilder) nsDisplayReflowCount(aBuilder, this, _name, _color)); \
+             MakeDisplayItem<nsDisplayReflowCount>(aBuilder, this, _name, _color)); \
     }                                                                         \
   PR_END_MACRO
 
 /*
   Macro to be used for classes that don't actually implement BuildDisplayList
  */
 #define DECL_DO_GLOBAL_REFLOW_COUNT_DSP(_class, _super)                   \
   void BuildDisplayList(nsDisplayListBuilder*   aBuilder,                 \
@@ -3829,17 +3867,18 @@ public:
    * aBackgroundRect is relative to aFrame.
    */
   static InitData GetInitData(nsDisplayListBuilder* aBuilder,
                               nsIFrame* aFrame,
                               uint32_t aLayer,
                               const nsRect& aBackgroundRect,
                               const nsStyleBackground* aBackgroundStyle);
 
-  explicit nsDisplayBackgroundImage(const InitData& aInitData,
+  explicit nsDisplayBackgroundImage(nsDisplayListBuilder* aBuilder,
+                                    const InitData& aInitData,
                                     nsIFrame* aFrameForBounds = nullptr);
   virtual ~nsDisplayBackgroundImage();
 
   // This will create and append new items for all the layers of the
   // background. Returns whether we appended a themed background.
   // aAllowWillPaintBorderOptimization should usually be left at true, unless
   // aFrame has special border drawing that causes opaque borders to not
   // actually be opaque.
@@ -4023,17 +4062,17 @@ TableType GetTableTypeFromFrame(nsIFrame
  * we let mFrame point to cell frame and store the table type of the ancestor
  * frame. And use mFrame and table type as key to generate DisplayItemData to
  * avoid sharing DisplayItemData.
  *
  * Also store ancestor frame as mStyleFrame for all rendering informations.
  */
 class nsDisplayTableBackgroundImage : public nsDisplayBackgroundImage {
 public:
-  nsDisplayTableBackgroundImage(const InitData& aInitData, nsIFrame* aCellFrame);
+  nsDisplayTableBackgroundImage(nsDisplayListBuilder* aBuilder, const InitData& aInitData, nsIFrame* aCellFrame);
 
   virtual uint32_t GetPerFrameKey() const override {
     return (mLayer << (TYPE_BITS + static_cast<uint8_t>(TableTypeBits::COUNT))) |
            (static_cast<uint8_t>(mTableType) << TYPE_BITS) |
            nsDisplayItem::GetPerFrameKey();
   }
 
   virtual bool IsInvalid(nsRect& aRect) const override;
@@ -4947,16 +4986,21 @@ public:
     MergeFromTrackingMergedFrames(static_cast<const nsDisplayWrapList*>(aItem));
   }
 
   virtual void GetMergedFrames(nsTArray<nsIFrame*>* aFrames) const override
   {
     aFrames->AppendElements(mMergedFrames);
   }
 
+  virtual bool HasMergedFrames() const override
+  {
+    return !mMergedFrames.IsEmpty();
+  }
+
   virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override
   {
     return true;
   }
 
   virtual bool IsInvalid(nsRect& aRect) const override
   {
     if (mFrame->IsInvalid(aRect) && aRect.IsEmpty()) {
@@ -5099,17 +5143,17 @@ public:
   {
     nsDisplayItem::RestoreState();
     mOpacity = mState.mOpacity;
   }
 
   virtual nsDisplayWrapList* Clone(nsDisplayListBuilder* aBuilder) const override
   {
     MOZ_COUNT_CTOR(nsDisplayOpacity);
-    return new (aBuilder) nsDisplayOpacity(aBuilder, *this);
+    return MakeDisplayItem<nsDisplayOpacity>(aBuilder, *this);
   }
 
   virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
                                    bool* aSnap) const override;
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager,
                                              const ContainerLayerParameters& aContainerParameters) override;
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
@@ -5179,17 +5223,17 @@ public:
   {}
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayBlendMode();
 #endif
 
   virtual nsDisplayWrapList* Clone(nsDisplayListBuilder* aBuilder) const override
   {
     MOZ_COUNT_CTOR(nsDisplayBlendMode);
-    return new (aBuilder) nsDisplayBlendMode(aBuilder, *this);
+    return MakeDisplayItem<nsDisplayBlendMode>(aBuilder, *this);
   }
 
   nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
                            bool* aSnap) const override;
 
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager,
                                              const ContainerLayerParameters& aContainerParameters) override;
@@ -5243,17 +5287,17 @@ public:
                           const nsDisplayTableBlendMode& aOther)
     : nsDisplayBlendMode(aBuilder, aOther)
     , mAncestorFrame(aOther.mAncestorFrame)
     , mTableType(aOther.mTableType)
   {}
 
   virtual nsDisplayWrapList* Clone(nsDisplayListBuilder* aBuilder) const override
   {
-    return new (aBuilder) nsDisplayTableBlendMode(aBuilder, *this);
+    return MakeDisplayItem<nsDisplayTableBlendMode>(aBuilder, *this);
   }
 
   virtual nsIFrame* FrameForInvalidation() const override { return mAncestorFrame; }
 
   virtual uint32_t GetPerFrameKey() const override {
     return (mIndex << (TYPE_BITS + static_cast<uint8_t>(TableTypeBits::COUNT))) |
            (static_cast<uint8_t>(mTableType) << TYPE_BITS) |
            nsDisplayItem::GetPerFrameKey();
@@ -5280,17 +5324,17 @@ public:
 
 #ifdef NS_BUILD_REFCNT_LOGGING
     virtual ~nsDisplayBlendContainer();
 #endif
 
     virtual nsDisplayWrapList* Clone(nsDisplayListBuilder* aBuilder) const override
     {
       MOZ_COUNT_CTOR(nsDisplayBlendContainer);
-      return new (aBuilder) nsDisplayBlendContainer(aBuilder, *this);
+      return MakeDisplayItem<nsDisplayBlendContainer>(aBuilder, *this);
     }
 
     virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                                LayerManager* aManager,
                                                const ContainerLayerParameters& aContainerParameters) override;
     virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                      LayerManager* aManager,
                                      const ContainerLayerParameters& aParameters) override;
@@ -5339,17 +5383,17 @@ public:
   static nsDisplayTableBlendContainer*
   CreateForBackgroundBlendMode(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                                nsDisplayList* aList,
                                const ActiveScrolledRoot* aActiveScrolledRoot,
                                nsIFrame* aAncestorFrame);
 
   virtual nsDisplayWrapList* Clone(nsDisplayListBuilder* aBuilder) const override
   {
-    return new (aBuilder) nsDisplayTableBlendContainer(aBuilder, *this);
+    return MakeDisplayItem<nsDisplayTableBlendContainer>(aBuilder, *this);
   }
 
   virtual nsIFrame* FrameForInvalidation() const override { return mAncestorFrame; }
 
   virtual uint32_t GetPerFrameKey() const override {
     return (static_cast<uint8_t>(mTableType) << TYPE_BITS) |
            nsDisplayItem::GetPerFrameKey();
   }
@@ -5577,17 +5621,17 @@ public:
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayStickyPosition();
 #endif
 
   void SetClipChain(const DisplayItemClipChain* aClipChain, bool aStore) override;
   virtual nsDisplayWrapList* Clone(nsDisplayListBuilder* aBuilder) const override
   {
     MOZ_COUNT_CTOR(nsDisplayStickyPosition);
-    return new (aBuilder) nsDisplayStickyPosition(aBuilder, *this);
+    return MakeDisplayItem<nsDisplayStickyPosition>(aBuilder, *this);
   }
 
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager,
                                              const ContainerLayerParameters& aContainerParameters) override;
   NS_DISPLAY_DECL_NAME("StickyPosition", TYPE_STICKY_POSITION)
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
@@ -5631,17 +5675,17 @@ public:
 
 
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayFixedPosition();
 #endif
 
   virtual nsDisplayWrapList* Clone(nsDisplayListBuilder* aBuilder) const override
   {
-    return new (aBuilder) nsDisplayFixedPosition(aBuilder, *this);
+    return MakeDisplayItem<nsDisplayFixedPosition>(aBuilder, *this);
   }
 
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager,
                                              const ContainerLayerParameters& aContainerParameters) override;
   NS_DISPLAY_DECL_NAME("FixedPosition", TYPE_FIXED_POSITION)
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
@@ -5689,17 +5733,17 @@ public:
   static nsDisplayTableFixedPosition* CreateForFixedBackground(nsDisplayListBuilder* aBuilder,
                                                                nsIFrame* aFrame,
                                                                nsDisplayBackgroundImage* aImage,
                                                                uint32_t aIndex,
                                                                nsIFrame* aAncestorFrame);
 
   virtual nsDisplayWrapList* Clone(nsDisplayListBuilder* aBuilder) const override
   {
-    return new (aBuilder) nsDisplayTableFixedPosition(aBuilder, *this);
+    return MakeDisplayItem<nsDisplayTableFixedPosition>(aBuilder, *this);
   }
 
   virtual nsIFrame* FrameForInvalidation() const override { return mAncestorFrame; }
 
   virtual uint32_t GetPerFrameKey() const override {
     return (mIndex << (TYPE_BITS + static_cast<uint8_t>(TableTypeBits::COUNT))) |
            (static_cast<uint8_t>(mTableType) << TYPE_BITS) |
            nsDisplayItem::GetPerFrameKey();
@@ -5713,17 +5757,16 @@ protected:
 
   nsDisplayTableFixedPosition(nsDisplayListBuilder* aBuilder,
                               const nsDisplayTableFixedPosition& aOther)
     : nsDisplayFixedPosition(aBuilder, aOther)
     , mAncestorFrame(aOther.mAncestorFrame)
     , mTableType(aOther.mTableType)
   {}
 
-
   nsIFrame* mAncestorFrame;
   TableType mTableType;
 };
 
 /**
  * This creates an empty scrollable layer. It has no child layers.
  * It is used to record the existence of a scrollable frame in the layer
  * tree.
@@ -5894,17 +5937,17 @@ public:
   {}
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayMask();
 #endif
 
   virtual nsDisplayWrapList* Clone(nsDisplayListBuilder* aBuilder) const override
   {
     MOZ_COUNT_CTOR(nsDisplayMask);
-    return new (aBuilder) nsDisplayMask(aBuilder, *this);
+    return MakeDisplayItem<nsDisplayMask>(aBuilder, *this);
   }
 
   NS_DISPLAY_DECL_NAME("Mask", TYPE_MASK)
 
   virtual bool CanMerge(const nsDisplayItem* aItem) const override;
 
   virtual void Merge(const nsDisplayItem* aItem) override
   {
@@ -5983,17 +6026,17 @@ public:
   {}
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayFilter();
 #endif
 
   virtual nsDisplayWrapList* Clone(nsDisplayListBuilder* aBuilder) const override
   {
     MOZ_COUNT_CTOR(nsDisplayFilter);
-    return new (aBuilder) nsDisplayFilter(aBuilder, *this);
+    return MakeDisplayItem<nsDisplayFilter>(aBuilder, *this);
   }
 
   NS_DISPLAY_DECL_NAME("Filter", TYPE_FILTER)
 
   virtual bool CanMerge(const nsDisplayItem* aItem) const override
   {
     // Items for the same content element should be merged into a single
     // compositing group.
--- a/layout/printing/nsPrintJob.cpp
+++ b/layout/printing/nsPrintJob.cpp
@@ -3365,25 +3365,25 @@ nsPrintJob::TurnScriptingOn(bool aDoTurn
     MOZ_ASSERT(po);
 
     nsIDocument* doc = po->mDocument;
     if (!doc) {
       continue;
     }
 
     if (nsCOMPtr<nsPIDOMWindowInner> window = doc->GetInnerWindow()) {
-      nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(window);
-      NS_WARNING_ASSERTION(go && go->GetGlobalJSObject(), "Can't get global");
+      nsCOMPtr<nsIGlobalObject> go = window->AsGlobal();
+      NS_WARNING_ASSERTION(go->GetGlobalJSObject(), "Can't get global");
       nsresult propThere = NS_PROPTABLE_PROP_NOT_THERE;
       doc->GetProperty(nsGkAtoms::scriptEnabledBeforePrintOrPreview,
                        &propThere);
       if (aDoTurnOn) {
         if (propThere != NS_PROPTABLE_PROP_NOT_THERE) {
           doc->DeleteProperty(nsGkAtoms::scriptEnabledBeforePrintOrPreview);
-          if (go && go->GetGlobalJSObject()) {
+          if (go->GetGlobalJSObject()) {
             xpc::Scriptability::Get(go->GetGlobalJSObject()).Unblock();
           }
           window->Resume();
         }
       } else {
         // Have to be careful, because people call us over and over again with
         // aDoTurnOn == false.  So don't set the property if it's already
         // set, since in that case we'd set it to the wrong value.
--- a/layout/style/FontFaceSet.cpp
+++ b/layout/style/FontFaceSet.cpp
@@ -117,22 +117,20 @@ FontFaceSet::FontFaceSet(nsPIDOMWindowIn
   , mHasLoadingFontFacesIsDirty(false)
   , mDelayedLoadCheck(false)
   , mBypassCache(false)
   , mPrivateBrowsing(false)
   , mHasStandardFontLoadPrincipalChanged(false)
 {
   MOZ_ASSERT(mDocument, "We should get a valid document from the caller!");
 
-  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aWindow);
-
   // If the pref is not set, don't create the Promise (which the page wouldn't
   // be able to get to anyway) as it causes the window.FontFaceSet constructor
   // to be created.
-  if (global && PrefEnabled()) {
+  if (aWindow && PrefEnabled()) {
     mResolveLazilyCreatedReadyPromise = true;
   }
 
   // Record the state of the "bypass cache" flags from the docshell now,
   // since we want to look at them from style worker threads, and we can
   // only get to the docshell through a weak pointer (which is only
   // possible on the main thread).
   //
--- a/layout/svg/SVGGeometryFrame.cpp
+++ b/layout/svg/SVGGeometryFrame.cpp
@@ -255,17 +255,17 @@ SVGGeometryFrame::BuildDisplayList(nsDis
                                    const nsDisplayListSet& aLists)
 {
   if (!static_cast<const nsSVGElement*>(GetContent())->HasValidDimensions() ||
       (!IsVisibleForPainting(aBuilder) && aBuilder->IsForPainting())) {
     return;
   }
   DisplayOutline(aBuilder, aLists);
   aLists.Content()->AppendToTop(
-    new (aBuilder) nsDisplaySVGGeometry(aBuilder, this));
+    MakeDisplayItem<nsDisplaySVGGeometry>(aBuilder, this));
 }
 
 //----------------------------------------------------------------------
 // nsSVGDisplayableFrame methods
 
 void
 SVGGeometryFrame::PaintSVG(gfxContext& aContext,
                            const gfxMatrix& aTransform,
--- a/layout/svg/SVGTextFrame.cpp
+++ b/layout/svg/SVGTextFrame.cpp
@@ -3199,17 +3199,17 @@ SVGTextFrame::BuildDisplayList(nsDisplay
     return;
   }
   if (!IsVisibleForPainting(aBuilder) &&
       aBuilder->IsForPainting()) {
     return;
   }
   DisplayOutline(aBuilder, aLists);
   aLists.Content()->AppendToTop(
-    new (aBuilder) nsDisplaySVGText(aBuilder, this));
+    MakeDisplayItem<nsDisplaySVGText>(aBuilder, this));
 }
 
 nsresult
 SVGTextFrame::AttributeChanged(int32_t aNameSpaceID,
                                nsAtom* aAttribute,
                                int32_t aModType)
 {
   if (aNameSpaceID != kNameSpaceID_None)
--- a/layout/svg/nsSVGOuterSVGFrame.cpp
+++ b/layout/svg/nsSVGOuterSVGFrame.cpp
@@ -781,20 +781,20 @@ nsSVGOuterSVGFrame::BuildDisplayList(nsD
   if ((aBuilder->IsForEventDelivery() &&
        NS_SVGDisplayListHitTestingEnabled()) ||
       (!aBuilder->IsForEventDelivery() &&
        NS_SVGDisplayListPaintingEnabled())) {
     nsDisplayList newList;
     nsDisplayListSet set(&newList, &newList, &newList,
                          &newList, &newList, &newList);
     BuildDisplayListForNonBlockChildren(aBuilder, set);
-    aLists.Content()->AppendToTop(new (aBuilder) nsDisplaySVGWrapper(aBuilder, this, &newList));
+    aLists.Content()->AppendToTop(MakeDisplayItem<nsDisplaySVGWrapper>(aBuilder, this, &newList));
   } else if (IsVisibleForPainting(aBuilder) || !aBuilder->IsForPainting()) {
     aLists.Content()->AppendToTop(
-      new (aBuilder) nsDisplayOuterSVG(aBuilder, this));
+      MakeDisplayItem<nsDisplayOuterSVG>(aBuilder, this));
   }
 }
 
 nsSplittableType
 nsSVGOuterSVGFrame::GetSplittableType() const
 {
   return NS_FRAME_NOT_SPLITTABLE;
 }
--- a/layout/tables/nsTableCellFrame.cpp
+++ b/layout/tables/nsTableCellFrame.cpp
@@ -386,18 +386,17 @@ nsTableCellFrame::ProcessBorders(nsTable
                                  const nsDisplayListSet& aLists)
 {
   const nsStyleBorder* borderStyle = StyleBorder();
   if (aFrame->IsBorderCollapse() || !borderStyle->HasBorder())
     return NS_OK;
 
   if (!GetContentEmpty() ||
       StyleTableBorder()->mEmptyCells == NS_STYLE_TABLE_EMPTY_CELLS_SHOW) {
-    aLists.BorderBackground()->AppendToTop(new (aBuilder)
-                                              nsDisplayBorder(aBuilder, this));
+    aLists.BorderBackground()->AppendToTop(MakeDisplayItem<nsDisplayBorder>(aBuilder, this));
   }
 
   return NS_OK;
 }
 
 class nsDisplayTableCellBackground : public nsDisplayTableItem {
 public:
   nsDisplayTableCellBackground(nsDisplayListBuilder* aBuilder,
@@ -491,42 +490,42 @@ nsTableCellFrame::BuildDisplayList(nsDis
                                    const nsDisplayListSet& aLists)
 {
   DO_GLOBAL_REFLOW_COUNT_DSP("nsTableCellFrame");
   if (ShouldPaintBordersAndBackgrounds()) {
     // display outset box-shadows if we need to.
     bool hasBoxShadow = !!StyleEffects()->mBoxShadow;
     if (hasBoxShadow) {
       aLists.BorderBackground()->AppendToTop(
-        new (aBuilder) nsDisplayBoxShadowOuter(aBuilder, this));
+        MakeDisplayItem<nsDisplayBoxShadowOuter>(aBuilder, this));
     }
 
     // display background if we need to.
     if (aBuilder->IsForEventDelivery() ||
         !StyleBackground()->IsTransparent(this) ||
         StyleDisplay()->mAppearance) {
       nsDisplayBackgroundImage::AppendBackgroundItemsToTop(aBuilder,
           this,
           GetRectRelativeToSelf(),
           aLists.BorderBackground());
     }
 
     // display inset box-shadows if we need to.
     if (hasBoxShadow) {
       aLists.BorderBackground()->AppendToTop(
-        new (aBuilder) nsDisplayBoxShadowInner(aBuilder, this));
+         MakeDisplayItem<nsDisplayBoxShadowInner>(aBuilder, this));
     }
 
     // display borders if we need to
     ProcessBorders(GetTableFrame(), aBuilder, aLists);
 
     // and display the selection border if we need to
     if (IsSelected()) {
-      aLists.BorderBackground()->AppendToTop(new (aBuilder)
-        nsDisplayTableCellSelection(aBuilder, this));
+      aLists.BorderBackground()->AppendToTop(
+        MakeDisplayItem<nsDisplayTableCellSelection>(aBuilder, this));
     }
   }
 
   // the 'empty-cells' property has no effect on 'outline'
   DisplayOutline(aBuilder, aLists);
 
   // Push a null 'current table item' so that descendant tables can't
   // accidentally mess with our table
--- a/layout/tables/nsTableFrame.cpp
+++ b/layout/tables/nsTableFrame.cpp
@@ -1528,17 +1528,17 @@ nsTableFrame::DisplayGenericTablePart(ns
   if (isVisible) {
     // XXXbz should box-shadow for rows/rowgroups/columns/colgroups get painted
     // just because we're visible?  Or should it depend on the cell visibility
     // when we're not the whole table?
 
     // Paint the outset box-shadows for the table frames
     if (aFrame->StyleEffects()->mBoxShadow) {
       aLists.BorderBackground()->AppendToTop(
-        new (aBuilder) nsDisplayBoxShadowOuter(aBuilder, aFrame));
+        MakeDisplayItem<nsDisplayBoxShadowOuter>(aBuilder, aFrame));
     }
   }
 
   // Background visibility for rows, rowgroups, columns, colgroups depends on
   // the visibility of the _cell_, not of the row/col(group).
   if (aFrame->IsTableRowGroupFrame()) {
     nsTableRowGroupFrame* rowGroup = static_cast<nsTableRowGroupFrame*>(aFrame);
     PaintRowGroupBackground(rowGroup, aFrame, aBuilder, aLists);
@@ -1596,38 +1596,38 @@ nsTableFrame::DisplayGenericTablePart(ns
   if (isVisible) {
     // XXXbz should box-shadow for rows/rowgroups/columns/colgroups get painted
     // just because we're visible?  Or should it depend on the cell visibility
     // when we're not the whole table?
 
     // Paint the inset box-shadows for the table frames
     if (aFrame->StyleEffects()->mBoxShadow) {
       aLists.BorderBackground()->AppendToTop(
-        new (aBuilder) nsDisplayBoxShadowInner(aBuilder, aFrame));
+        MakeDisplayItem<nsDisplayBoxShadowInner>(aBuilder, aFrame));
     }
   }
 
   aFrame->DisplayOutline(aBuilder, aLists);
 
   aTraversal(aBuilder, aFrame, aLists);
 
   if (isVisible) {
     if (isTable) {
       nsTableFrame* table = static_cast<nsTableFrame*>(aFrame);
       // In the collapsed border model, overlay all collapsed borders.
       if (table->IsBorderCollapse()) {
         if (table->HasBCBorders()) {
           aLists.BorderBackground()->AppendToTop(
-            new (aBuilder) nsDisplayTableBorderCollapse(aBuilder, table));
+            MakeDisplayItem<nsDisplayTableBorderCollapse>(aBuilder, table));
         }
       } else {
         const nsStyleBorder* borderStyle = aFrame->StyleBorder();
         if (borderStyle->HasBorder()) {
           aLists.BorderBackground()->AppendToTop(
-            new (aBuilder) nsDisplayBorder(aBuilder, table));
+            MakeDisplayItem<nsDisplayBorder>(aBuilder, table));
         }
       }
     }
   }
 }
 
 // table paint code is concerned primarily with borders and bg color
 // SEC: TODO: adjust the rect for captions
--- a/layout/xul/nsBoxFrame.cpp
+++ b/layout/xul/nsBoxFrame.cpp
@@ -1343,21 +1343,21 @@ nsBoxFrame::BuildDisplayList(nsDisplayLi
 
   nsDisplayListCollection tempLists(aBuilder);
   const nsDisplayListSet& destination = forceLayer ? tempLists : aLists;
 
   DisplayBorderBackgroundOutline(aBuilder, destination);
 
 #ifdef DEBUG_LAYOUT
   if (mState & NS_STATE_CURRENTLY_IN_DEBUG) {
-    destination.BorderBackground()->AppendToTop(new (aBuilder)
-      nsDisplayGeneric(aBuilder, this, PaintXULDebugBackground,
+    destination.BorderBackground()->AppendToTop(
+      MakeDisplayItem<nsDisplayGeneric>(aBuilder, this, PaintXULDebugBackground,
                        "XULDebugBackground"));
-    destination.Outlines()->AppendToTop(new (aBuilder)
-      nsDisplayXULDebug(aBuilder, this));
+    destination.Outlines()->AppendToTop(
+      MakeDisplayItem<nsDisplayXULDebug>(aBuilder, this));
   }
 #endif
 
   Maybe<nsDisplayListBuilder::AutoContainerASRTracker> contASRTracker;
   if (forceLayer) {
     contASRTracker.emplace(aBuilder);
   }
 
@@ -1379,21 +1379,21 @@ nsBoxFrame::BuildDisplayList(nsDisplayLi
     masterList.AppendToTop(tempLists.PositionedDescendants());
     masterList.AppendToTop(tempLists.Outlines());
 
     const ActiveScrolledRoot* ownLayerASR = contASRTracker->GetContainerASR();
 
     DisplayListClipState::AutoSaveRestore ownLayerClipState(aBuilder);
 
     // Wrap the list to make it its own layer
-    aLists.Content()->AppendToTop(new (aBuilder)
-      nsDisplayOwnLayer(aBuilder, this, &masterList, ownLayerASR,
-                        nsDisplayOwnLayerFlags::eNone,
-                        mozilla::layers::FrameMetrics::NULL_SCROLL_ID,
-                        mozilla::layers::ScrollThumbData{}, true, true));
+    aLists.Content()->AppendToTop(
+      MakeDisplayItem<nsDisplayOwnLayer>(aBuilder, this, &masterList, ownLayerASR,
+                                         nsDisplayOwnLayerFlags::eNone,
+                                         mozilla::layers::FrameMetrics::NULL_SCROLL_ID,
+                                         mozilla::layers::ScrollThumbData{}, true, true));
   }
 }
 
 void
 nsBoxFrame::BuildDisplayListForChildren(nsDisplayListBuilder*   aBuilder,
                                         const nsDisplayListSet& aLists)
 {
   nsIFrame* kid = mFrames.FirstChild();
@@ -2081,24 +2081,23 @@ void nsDisplayXULEventRedirector::HitTes
 class nsXULEventRedirectorWrapper final : public nsDisplayWrapper
 {
 public:
   explicit nsXULEventRedirectorWrapper(nsIFrame* aTargetFrame)
       : mTargetFrame(aTargetFrame) {}
   virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder,
                                   nsIFrame* aFrame,
                                   nsDisplayList* aList) override {
-    return new (aBuilder)
-        nsDisplayXULEventRedirector(aBuilder, aFrame, aList, mTargetFrame);
+    return MakeDisplayItem<nsDisplayXULEventRedirector>(aBuilder, aFrame, aList,
+                                                        mTargetFrame);
   }
   virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder,
                                   nsDisplayItem* aItem) override {
-    return new (aBuilder)
-        nsDisplayXULEventRedirector(aBuilder, aItem->Frame(), aItem,
-                                    mTargetFrame);
+    return MakeDisplayItem<nsDisplayXULEventRedirector>(aBuilder, aItem->Frame(), aItem,
+                                                        mTargetFrame);
   }
 private:
   nsIFrame* mTargetFrame;
 };
 
 void
 nsBoxFrame::WrapListsInRedirector(nsDisplayListBuilder*   aBuilder,
                                   const nsDisplayListSet& aIn,
--- a/layout/xul/nsGroupBoxFrame.cpp
+++ b/layout/xul/nsGroupBoxFrame.cpp
@@ -148,18 +148,18 @@ void
 nsGroupBoxFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                   const nsDisplayListSet& aLists)
 {
   // Paint our background and border
   if (IsVisibleForPainting(aBuilder)) {
     nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
       aBuilder, this, GetBackgroundRectRelativeToSelf(),
       aLists.BorderBackground());
-    aLists.BorderBackground()->AppendToTop(new (aBuilder)
-      nsDisplayXULGroupBorder(aBuilder, this));
+    aLists.BorderBackground()->AppendToTop(
+      MakeDisplayItem<nsDisplayXULGroupBorder>(aBuilder, this));
 
     DisplayOutline(aBuilder, aLists);
   }
 
   BuildDisplayListForChildren(aBuilder, aLists);
 }
 
 nsRect
--- a/layout/xul/nsImageBoxFrame.cpp
+++ b/layout/xul/nsImageBoxFrame.cpp
@@ -337,17 +337,17 @@ nsImageBoxFrame::BuildDisplayList(nsDisp
     nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition()) ?
     0 : DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT;
 
   DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox
     clip(aBuilder, this, clipFlags);
 
   nsDisplayList list;
   list.AppendToTop(
-    new (aBuilder) nsDisplayXULImage(aBuilder, this));
+    MakeDisplayItem<nsDisplayXULImage>(aBuilder, this));
 
   CreateOwnLayerIfNeeded(aBuilder, &list);
 
   aLists.Content()->AppendToTop(&list);
 }
 
 already_AddRefed<imgIContainer>
 nsImageBoxFrame::GetImageContainerForPainting(const nsPoint& aPt,
--- a/layout/xul/nsLeafBoxFrame.cpp
+++ b/layout/xul/nsLeafBoxFrame.cpp
@@ -107,18 +107,18 @@ nsLeafBoxFrame::BuildDisplayList(nsDispl
   // BlockBorderBackground() list. But I don't see any need to preserve
   // that anomalous behaviour. The important thing I'm preserving is that
   // leaf boxes continue to receive events in the foreground layer.
   DisplayBorderBackgroundOutline(aBuilder, aLists);
 
   if (!aBuilder->IsForEventDelivery() || !IsVisibleForPainting(aBuilder))
     return;
 
-  aLists.Content()->AppendToTop(new (aBuilder)
-    nsDisplayEventReceiver(aBuilder, this));
+  aLists.Content()->AppendToTop(
+    MakeDisplayItem<nsDisplayEventReceiver>(aBuilder, this));
 }
 
 /* virtual */ nscoord
 nsLeafBoxFrame::GetMinISize(gfxContext *aRenderingContext)
 {
   nscoord result;
   DISPLAY_MIN_WIDTH(this, result);
   nsBoxLayoutState state(PresContext(), aRenderingContext);
--- a/layout/xul/nsSliderFrame.cpp
+++ b/layout/xul/nsSliderFrame.cpp
@@ -321,18 +321,18 @@ nsSliderFrame::AttributeChanged(int32_t 
 
 void
 nsSliderFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                 const nsDisplayListSet& aLists)
 {
   if (aBuilder->IsForEventDelivery() && isDraggingThumb()) {
     // This is EVIL, we shouldn't be messing with event delivery just to get
     // thumb mouse drag events to arrive at the slider!
-    aLists.Outlines()->AppendToTop(new (aBuilder)
-      nsDisplayEventReceiver(aBuilder, this));
+    aLists.Outlines()->AppendToTop(
+      MakeDisplayItem<nsDisplayEventReceiver>(aBuilder, this));
     return;
   }
 
   nsBoxFrame::BuildDisplayList(aBuilder, aLists);
 }
 
 static bool
 UsesCustomScrollbarMediator(nsIFrame* scrollbarBox) {
@@ -453,26 +453,26 @@ nsSliderFrame::BuildDisplayListForChildr
       masterList.AppendToTop(tempLists.PositionedDescendants());
       masterList.AppendToTop(tempLists.Outlines());
 
       // Restore the saved clip so it applies to the thumb container layer.
       thumbContentsClipState.Restore();
 
       // Wrap the list to make it its own layer.
       const ActiveScrolledRoot* ownLayerASR = contASRTracker.GetContainerASR();
-      aLists.Content()->AppendToTop(new (aBuilder)
-        nsDisplayOwnLayer(aBuilder, this, &masterList, ownLayerASR,
-                          flags, scrollTargetId,
-                          ScrollThumbData{scrollDirection,
-                                          GetThumbRatio(),
-                                          thumbStart,
-                                          thumbLength,
-                                          isAsyncDraggable,
-                                          sliderTrackStart,
-                                          sliderTrackLength}));
+      aLists.Content()->AppendToTop(
+        MakeDisplayItem<nsDisplayOwnLayer>(aBuilder, this, &masterList, ownLayerASR,
+                                           flags, scrollTargetId,
+                                           ScrollThumbData{scrollDirection,
+                                                           GetThumbRatio(),
+                                                           thumbStart,
+                                                           thumbLength,
+                                                           isAsyncDraggable,
+                                                           sliderTrackStart,
+                                                           sliderTrackLength}));
 
       return;
     }
   }
 
   nsBoxFrame::BuildDisplayListForChildren(aBuilder, aLists);
 }
 
--- a/layout/xul/nsSplitterFrame.cpp
+++ b/layout/xul/nsSplitterFrame.cpp
@@ -379,18 +379,18 @@ nsSplitterFrame::BuildDisplayList(nsDisp
                                   const nsDisplayListSet& aLists)
 {
   nsBoxFrame::BuildDisplayList(aBuilder, aLists);
 
   // if the mouse is captured always return us as the frame.
   if (mInner->mDragging && aBuilder->IsForEventDelivery())
   {
     // XXX It's probably better not to check visibility here, right?
-    aLists.Outlines()->AppendToTop(new (aBuilder)
-      nsDisplayEventReceiver(aBuilder, this));
+    aLists.Outlines()->AppendToTop(
+      MakeDisplayItem<nsDisplayEventReceiver>(aBuilder, this));
     return;
   }
 }
 
 nsresult
 nsSplitterFrame::HandleEvent(nsPresContext* aPresContext,
                              WidgetGUIEvent* aEvent,
                              nsEventStatus* aEventStatus)
--- a/layout/xul/nsTextBoxFrame.cpp
+++ b/layout/xul/nsTextBoxFrame.cpp
@@ -368,18 +368,18 @@ void
 nsTextBoxFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                  const nsDisplayListSet& aLists)
 {
     if (!IsVisibleForPainting(aBuilder))
         return;
 
     nsLeafBoxFrame::BuildDisplayList(aBuilder, aLists);
 
-    aLists.Content()->AppendToTop(new (aBuilder)
-        nsDisplayXULTextBox(aBuilder, this));
+    aLists.Content()->AppendToTop(
+        MakeDisplayItem<nsDisplayXULTextBox>(aBuilder, this));
 }
 
 void
 nsTextBoxFrame::PaintTitle(gfxContext&          aRenderingContext,
                            const nsRect&        aDirtyRect,
                            nsPoint              aPt,
                            const nscolor*       aOverrideColor)
 {
--- a/layout/xul/tree/nsTreeBodyFrame.cpp
+++ b/layout/xul/tree/nsTreeBodyFrame.cpp
@@ -2840,17 +2840,17 @@ nsTreeBodyFrame::BuildDisplayList(nsDisp
   // Handles painting our background, border, and outline.
   nsLeafBoxFrame::BuildDisplayList(aBuilder, aLists);
 
   // Bail out now if there's no view or we can't run script because the
   // document is a zombie
   if (!mView || !GetContent ()->GetComposedDoc()->GetWindow())
     return;
 
-  nsDisplayItem* item = new (aBuilder) nsDisplayTreeBody(aBuilder, this);
+  nsDisplayItem* item = MakeDisplayItem<nsDisplayTreeBody>(aBuilder, this);
   aLists.Content()->AppendToTop(item);
 
 #ifdef XP_MACOSX
   nsIContent* baseElement = GetBaseElement();
   nsIFrame* treeFrame =
     baseElement ? baseElement->GetPrimaryFrame() : nullptr;
   nsCOMPtr<nsITreeSelection> selection;
   mView->GetSelection(getter_AddRefs(selection));
--- a/layout/xul/tree/nsTreeColFrame.cpp
+++ b/layout/xul/tree/nsTreeColFrame.cpp
@@ -121,18 +121,18 @@ nsTreeColFrame::BuildDisplayListForChild
     return;
   }
 
   nsDisplayListCollection set(aBuilder);
   nsBoxFrame::BuildDisplayListForChildren(aBuilder, set);
 
   WrapListsInRedirector(aBuilder, set, aLists);
 
-  aLists.Content()->AppendToTop(new (aBuilder)
-    nsDisplayXULTreeColSplitterTarget(aBuilder, this));
+  aLists.Content()->AppendToTop(
+    MakeDisplayItem<nsDisplayXULTreeColSplitterTarget>(aBuilder, this));
 }
 
 nsresult
 nsTreeColFrame::AttributeChanged(int32_t aNameSpaceID,
                                  nsAtom* aAttribute,
                                  int32_t aModType)
 {
   nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute,
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -848,17 +848,16 @@ pref("gfx.bundled_fonts.enabled", true);
 pref("gfx.bundled_fonts.force-enabled", false);
 #endif
 
 // Do we fire a notification about missing fonts, so the front-end can decide
 // whether to try and do something about it (e.g. download additional fonts)?
 pref("gfx.missing_fonts.notify", false);
 
 // prefs controlling the font (name/cmap) loader that runs shortly after startup
-pref("gfx.font_loader.families_per_slice", 3); // read in info 3 families at a time
 #ifdef XP_WIN
 pref("gfx.font_loader.delay", 120000);         // 2 minutes after startup
 pref("gfx.font_loader.interval", 1000);        // every 1 second until complete
 #else
 pref("gfx.font_loader.delay", 8000);           // 8 secs after startup
 pref("gfx.font_loader.interval", 50);          // run every 50 ms
 #endif
 
@@ -4729,16 +4728,24 @@ pref("image.layout_network_priority", tr
 // Discards inactive image frames and re-decodes them on demand from
 // compressed data.
 pref("image.mem.discardable", true);
 
 // Discards inactive image frames of _animated_ images and re-decodes them on
 // demand from compressed data. Has no effect if image.mem.discardable is false.
 pref("image.mem.animated.discardable", true);
 
+// Whether the heap should be used for frames from animated images. On Android,
+// volatile memory keeps file handles open for each buffer.
+#if defined(ANDROID)
+pref("image.mem.animated.use_heap", true);
+#else
+pref("image.mem.animated.use_heap", false);
+#endif
+
 // Decodes images into shared memory to allow direct use in separate
 // rendering processes.
 pref("image.mem.shared", 2);
 
 // Allows image locking of decoded image data in content processes.
 pref("image.mem.allow_locking_in_content_processes", true);
 
 // Chunk size for calls to the image decoders
@@ -4760,16 +4767,24 @@ pref("image.mem.surfacecache.size_factor
 // How much of the data in the surface cache is discarded when we get a memory
 // pressure notification, as a fraction. The discard factor is interpreted as a
 // reciprocal, so a discard factor of 1 means to discard everything in the
 // surface cache on memory pressure, a discard factor of 2 means to discard half
 // of the data, and so forth. The default should be a good balance for desktop
 // and laptop systems, where we never discard visible images.
 pref("image.mem.surfacecache.discard_factor", 1);
 
+// What is the minimum buffer size in KB before using volatile memory over the
+// heap. On Android, volatile memory keeps file handles open for each buffer.
+#if defined(ANDROID)
+pref("image.mem.volatile.min_threshold_kb", 100);
+#else
+pref("image.mem.volatile.min_threshold_kb", -1);
+#endif
+
 // How many threads we'll use for multithreaded decoding. If < 0, will be
 // automatically determined based on the system's number of cores.
 pref("image.multithreaded_decoding.limit", -1);
 
 // How long in ms before we should start shutting down idle decoder threads.
 pref("image.multithreaded_decoding.idle_timeout", 600000);
 
 // Limit for the canvas image cache. 0 means we don't limit the size of the
--- a/testing/mozharness/configs/partner_repacks/release_mozilla-esr52_desktop.py
+++ b/testing/mozharness/configs/partner_repacks/release_mozilla-esr52_desktop.py
@@ -1,6 +1,7 @@
 config = {
     "appName": "Firefox",
     "log_name": "partner_repack",
     "repack_manifests_url": "git@github.com:mozilla-partners/mozilla-sha1-manifest",
     "repo_file": "https://raw.githubusercontent.com/mozilla/git-repo/master/repo",
+    "repo_url": "git@github.com:mozilla/git-repo.git",
 }
--- a/testing/mozharness/configs/partner_repacks/release_mozilla-release_desktop.py
+++ b/testing/mozharness/configs/partner_repacks/release_mozilla-release_desktop.py
@@ -1,6 +1,7 @@
 config = {
     "appName": "Firefox",
     "log_name": "partner_repack",
     "repack_manifests_url": "git@github.com:mozilla-partners/repack-manifests.git",
     "repo_file": "https://raw.githubusercontent.com/mozilla/git-repo/master/repo",
+    "repo_url": "git@github.com:mozilla/git-repo.git",
 }
--- a/testing/mozharness/configs/partner_repacks/staging_release_mozilla-release_desktop.py
+++ b/testing/mozharness/configs/partner_repacks/staging_release_mozilla-release_desktop.py
@@ -1,6 +1,7 @@
 config = {
     "appName": "Firefox",
     "log_name": "partner_repack",
     "repack_manifests_url": "git@github.com:mozilla-partners/repack-manifests.git",
     "repo_file": "https://raw.githubusercontent.com/mozilla/git-repo/master/repo",
+    "repo_url": "git@github.com:mozilla/git-repo.git",
 }
--- a/testing/mozharness/scripts/desktop_partner_repacks.py
+++ b/testing/mozharness/scripts/desktop_partner_repacks.py
@@ -104,27 +104,29 @@ class DesktopPartnerRepacks(ReleaseMixin
             self.warning("Skipping buildbot properties overrides")
         else:
             if self.config.get('require_buildprops', False) is True:
                 if not self.buildbot_config:
                     self.fatal("Unable to load properties from file: %s" %
                                self.config.get('buildbot_json_path'))
             props = self.buildbot_config["properties"]
             for prop in ['version', 'build_number', 'revision', 'repo_file',
-                         'repack_manifests_url', 'partner']:
+                         'repo_url', 'repack_manifests_url', 'partner']:
                 if props.get(prop):
                     self.info("Overriding %s with %s" % (prop, props[prop]))
                     self.config[prop] = props.get(prop)
 
         if 'version' not in self.config:
             self.fatal("Version (-v) not supplied.")
         if 'build_number' not in self.config:
             self.fatal("Build number (-n) not supplied.")
         if 'repo_file' not in self.config:
             self.fatal("repo_file not supplied.")
+        if 'repo_url' not in self.config:
+            self.fatal("repo_url not supplied.")
         if 'repack_manifests_url' not in self.config:
             self.fatal("repack_manifests_url not supplied.")
 
     def query_abs_dirs(self):
         if self.abs_dirs:
             return self.abs_dirs
         abs_dirs = super(DesktopPartnerRepacks, self).query_abs_dirs()
         for directory in abs_dirs:
@@ -143,16 +145,17 @@ class DesktopPartnerRepacks(ReleaseMixin
     # Actions {{{
     def _repo_cleanup(self):
         self.rmtree(self.query_abs_dirs()['abs_repo_dir'])
         self.rmtree(self.query_abs_dirs()['abs_partners_dir'])
         self.rmtree(self.query_abs_dirs()['abs_scripts_dir'])
 
     def _repo_init(self, repo):
         status = self.run_command([repo, "init", "--no-repo-verify",
+                                   "--repo-url", self.config['repo_url'],
                                    "-u", self.config['repack_manifests_url']],
                                   cwd=self.query_abs_dirs()['abs_work_dir'])
         if status:
             return status
         return self.run_command([repo, "sync"],
                                 cwd=self.query_abs_dirs()['abs_work_dir'])
 
     def setup(self):
--- a/toolkit/components/extensions/ExtensionContent.jsm
+++ b/toolkit/components/extensions/ExtensionContent.jsm
@@ -19,36 +19,33 @@ XPCOMUtils.defineLazyModuleGetters(this,
   TelemetryStopwatch: "resource://gre/modules/TelemetryStopwatch.jsm",
   WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.jsm",
 });
 
 XPCOMUtils.defineLazyServiceGetter(this, "styleSheetService",
                                    "@mozilla.org/content/style-sheet-service;1",
                                    "nsIStyleSheetService");
 
-// xpcshell doesn't handle idle callbacks well.
-XPCOMUtils.defineLazyGetter(this, "idleTimeout",
-                            () => Services.appinfo.name === "XPCShell" ? 500 : undefined);
-
 const DocumentEncoder = Components.Constructor(
   "@mozilla.org/layout/documentEncoder;1?type=text/plain",
   "nsIDocumentEncoder", "init");
 
 const Timer = Components.Constructor("@mozilla.org/timer;1", "nsITimer", "initWithCallback");
 
 ChromeUtils.import("resource://gre/modules/ExtensionChild.jsm");
 ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
 ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm");
 
 const {
   DefaultMap,
   DefaultWeakMap,
   defineLazyGetter,
   getInnerWindowID,
   getWinUtils,
+  promiseDocumentIdle,
   promiseDocumentLoaded,
   promiseDocumentReady,
   runSafeSyncWithoutClone,
   stringToCryptoHash,
 } = ExtensionUtils;
 
 const {
   BaseContext,
@@ -367,23 +364,18 @@ class Script {
   }
 
   async injectInto(window) {
     let context = this.extension.getContext(window);
     try {
       if (this.runAt === "document_end") {
         await promiseDocumentReady(window.document);
       } else if (this.runAt === "document_idle") {
-        let readyThenIdle = promiseDocumentReady(window.document).then(() => {
-          return new Promise(resolve =>
-            window.requestIdleCallback(resolve, {timeout: idleTimeout}));
-        });
-
         await Promise.race([
-          readyThenIdle,
+          promiseDocumentIdle(window),
           promiseDocumentLoaded(window.document),
         ]);
       }
 
       return this.inject(context);
     } catch (e) {
       return Promise.reject(context.normalizeError(e));
     }
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -26,16 +26,20 @@ XPCOMUtils.defineLazyGetter(this, "conso
 
 XPCOMUtils.defineLazyGetter(this, "utf8Encoder", () => {
   return new TextEncoder("utf-8");
 });
 XPCOMUtils.defineLazyGetter(this, "utf8Decoder", () => {
   return new TextDecoder("utf-8");
 });
 
+// xpcshell doesn't handle idle callbacks well.
+XPCOMUtils.defineLazyGetter(this, "idleTimeout",
+                            () => Services.appinfo.name === "XPCShell" ? 500 : undefined);
+
 // It would be nicer to go through `Services.appinfo`, but some tests need to be
 // able to replace that field with a custom implementation before it is first
 // called.
 // eslint-disable-next-line mozilla/use-services
 const appinfo = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
 
 let nextId = 0;
 const uniqueProcessID = appinfo.uniqueProcessID;
@@ -303,16 +307,32 @@ function promiseDocumentReady(doc) {
         doc.removeEventListener("DOMContentLoaded", onReady, true);
         resolve(doc);
       }
     }, true);
   });
 }
 
 /**
+  * Returns a Promise which resolves when the given window's document's DOM has
+  * fully loaded, the <head> stylesheets have fully loaded, and we have hit an
+  * idle time.
+  *
+  * @param {Window} window The window whose document we will await
+                           the readiness of.
+  * @returns {Promise<IdleDeadline>}
+  */
+function promiseDocumentIdle(window) {
+  return window.document.documentReadyForIdle.then(() => {
+    return new Promise(resolve =>
+      window.requestIdleCallback(resolve, {timeout: idleTimeout}));
+  });
+}
+
+/**
  * Returns a Promise which resolves when the given document is fully
  * loaded.
  *
  * @param {Document} doc The document to await the load of.
  * @returns {Promise<Document>}
  */
 function promiseDocumentLoaded(doc) {
   if (doc.readyState == "complete") {
@@ -663,16 +683,17 @@ this.ExtensionUtils = {
   getConsole,
   getInnerWindowID,
   getMessageManager,
   getUniqueId,
   filterStack,
   getWinUtils,
   instanceOf,
   normalizeTime,
+  promiseDocumentIdle,
   promiseDocumentLoaded,
   promiseDocumentReady,
   promiseEvent,
   promiseObserved,
   runSafeSyncWithoutClone,
   stringToCryptoHash,
   withHandlingUserInput,
   DefaultMap,
--- a/toolkit/modules/RemoteFinder.jsm
+++ b/toolkit/modules/RemoteFinder.jsm
@@ -204,16 +204,20 @@ RemoteFinder.prototype = {
   }
 };
 
 function RemoteFinderListener(global) {
   let {Finder} = ChromeUtils.import("resource://gre/modules/Finder.jsm", {});
   this._finder = new Finder(global.docShell);
   this._finder.addResultListener(this);
   this._global = global;
+  this.KeyboardEvent =
+    global.docShell
+          .QueryInterface(Ci.nsIInterfaceRequestor)
+          .getInterface(Ci.nsIDOMWindow).KeyboardEvent;
 
   for (let msg of this.MESSAGES) {
     global.addMessageListener(msg, this);
   }
 }
 
 RemoteFinderListener.prototype = {
   MESSAGES: [
@@ -309,17 +313,17 @@ RemoteFinderListener.prototype = {
         this._finder.onFindbarClose();
         break;
 
       case "Finder:FindbarOpen":
         this._finder.onFindbarOpen();
         break;
 
       case "Finder:KeyPress":
-        this._finder.keyPress(data);
+        this._finder.keyPress(new this.KeyboardEvent("keypress", data));
         break;
 
       case "Finder:MatchesCount":
         this._finder.requestMatchesCount(data.searchString, data.linksOnly);
         break;
 
       case "Finder:ModalHighlightChange":
         this._finder.onModalHighlightChange(data.useModalHighlight);
--- a/xpcom/string/nsSubstring.cpp
+++ b/xpcom/string/nsSubstring.cpp
@@ -36,17 +36,17 @@
 #include <pthread.h>
 #include <unistd.h>
 #endif
 
 #ifdef STRING_BUFFER_CANARY
 #define CHECK_STRING_BUFFER_CANARY(c)                                     \
   do {                                                                    \
     if ((c) != CANARY_OK) {                                               \
-      MOZ_CRASH_UNSAFE_PRINTF("Bad canary value %d", c);                  \
+      MOZ_CRASH_UNSAFE_PRINTF("Bad canary value 0x%x", c);                  \
     }                                                                     \
   } while(0)
 #else
 #define CHECK_STRING_BUFFER_CANARY(c)                                     \
   do {                                                                    \
   } while(0)
 #endif
 
@@ -371,17 +371,17 @@ nsStringBuffer::SizeOfIncludingThisEvenI
 {
   return aMallocSizeOf(this);
 }
 
 #ifdef STRING_BUFFER_CANARY
 void
 nsStringBuffer::FromDataCanaryCheckFailed() const
 {
-  MOZ_CRASH_UNSAFE_PRINTF("Bad canary value %d in FromData", mCanary);
+  MOZ_CRASH_UNSAFE_PRINTF("Bad canary value 0x%x in FromData", mCanary);
 }
 #endif
 
 // ---------------------------------------------------------------------------
 
 // define nsAString
 #include "nsTSubstring.cpp"