Merge autoland to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Wed, 13 Sep 2017 23:17:32 -0700
changeset 430294 9517eea4a1a5955618fc79d039f9f0282b7185ca
parent 430214 cb717386aec8601a301babd4156aabc4cccff0b0 (current diff)
parent 430293 a4bfe93c85a455f518c5dca6df42c0763747a5ec (diff)
child 430332 4702042aa919d5ae49dfe3ac5ee86507839b3c83
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to central, a=merge MozReview-Commit-ID: BptG5xWc5Ax
dom/tests/browser/browser_test__content.js
mobile/android/app/src/main/res/drawable-ldltr/as_contextmenu_divider.xml
mobile/android/app/src/main/res/drawable/as_contextmenu_divider_helper.xml
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -578,17 +578,17 @@ pref("browser.gesture.swipe.up", "cmd_sc
 pref("browser.gesture.swipe.down", "cmd_scrollBottom");
 #ifdef XP_MACOSX
 pref("browser.gesture.pinch.latched", true);
 pref("browser.gesture.pinch.threshold", 150);
 #else
 pref("browser.gesture.pinch.latched", false);
 pref("browser.gesture.pinch.threshold", 25);
 #endif
-#ifdef XP_WIN
+#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
 // Enabled for touch input display zoom.
 pref("browser.gesture.pinch.out", "cmd_fullZoomEnlarge");
 pref("browser.gesture.pinch.in", "cmd_fullZoomReduce");
 pref("browser.gesture.pinch.out.shift", "cmd_fullZoomReset");
 pref("browser.gesture.pinch.in.shift", "cmd_fullZoomReset");
 #else
 // Disabled by default due to issues with track pad input.
 pref("browser.gesture.pinch.out", "");
@@ -1270,17 +1270,17 @@ pref("browser.newtabpage.rows", 3);
 pref("browser.newtabpage.columns", 5);
 
 // directory tiles download URL
 pref("browser.newtabpage.directory.source", "https://tiles.services.mozilla.com/v3/links/fetch/%LOCALE%/%CHANNEL%");
 
 // activates Activity Stream
 pref("browser.newtabpage.activity-stream.enabled", true);
 pref("browser.newtabpage.activity-stream.prerender", true);
-pref("browser.newtabpage.activity-stream.aboutHome.enabled", false);
+pref("browser.newtabpage.activity-stream.aboutHome.enabled", true);
 
 // Enable the DOM fullscreen API.
 pref("full-screen-api.enabled", true);
 
 // Startup Crash Tracking
 // number of startup crashes that can occur before starting into safe mode automatically
 // (this pref has no effect if more than 6 hours have passed since the last crash)
 pref("toolkit.startup.max_resumed_crashes", 3);
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -280,16 +280,19 @@
     <key id="key_fullScreen" key="&fullScreenCmd.macCommandKey;" command="View:FullScreen" modifiers="accel,control"/>
     <key id="key_fullScreen_old" key="&fullScreenCmd.macCommandKey;" command="View:FullScreen" modifiers="accel,shift"/>
     <key keycode="VK_F11" command="View:FullScreen"/>
 #endif
     <key id="key_toggleReaderMode" key="&toggleReaderMode.key;" command="View:ReaderView" modifiers="accel,alt" disabled="true"/>
     <key key="&reloadCmd.commandkey;" command="Browser:Reload" modifiers="accel" id="key_reload"/>
     <key key="&reloadCmd.commandkey;" command="Browser:ReloadSkipCache" modifiers="accel,shift"/>
     <key id="key_viewSource" key="&pageSourceCmd.commandkey;" command="View:PageSource" modifiers="accel"/>
+#ifdef XP_MACOSX
+    <key id="key_viewSourceSafari" key="&pageSourceCmd.SafariCommandKey;" command="View:PageSource" modifiers="accel,alt"/>
+#endif
 #ifndef XP_WIN
     <key id="key_viewInfo"   key="&pageInfoCmd.commandkey;"   command="View:PageInfo"   modifiers="accel"/>
 #endif
     <key id="key_find" key="&findOnCmd.commandkey;" command="cmd_find" modifiers="accel"/>
     <key id="key_findAgain" key="&findAgainCmd.commandkey;" command="cmd_findAgain" modifiers="accel"/>
     <key id="key_findPrevious" key="&findAgainCmd.commandkey;" command="cmd_findPrevious" modifiers="accel,shift"/>
 #ifdef XP_MACOSX
     <key id="key_findSelection" key="&findSelectionCmd.commandkey;" command="cmd_findSelection" modifiers="accel"/>
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -179,16 +179,17 @@ tabbrowser {
   pointer-events: none; /* avoid blocking dragover events on scroll buttons */
 }
 
 .tabbrowser-tab[tabdrop-samewindow],
 .tabbrowser-tabs[movingtab] > .tabbrowser-tab[fadein]:not([selected]) {
   transition: transform 200ms var(--animation-easing-function);
 }
 
+#TabsToolbar[movingtab] > .chromeclass-toolbar-additional,
 #TabsToolbar[movingtab] > .tabbrowser-tabs {
   padding-bottom: 15px;
 }
 
 #TabsToolbar[movingtab] + #nav-bar {
   margin-top: -15px;
   pointer-events: none;
 }
@@ -450,19 +451,22 @@ toolbarpaletteitem > #personal-bookmarks
 }
 
 toolbarpaletteitem[place="toolbar"] > #personal-bookmarks > #bookmarks-toolbar-placeholder,
 toolbarpaletteitem[place="palette"] > #personal-bookmarks > #bookmarks-toolbar-button,
 #personal-bookmarks:-moz-any([overflowedItem=true], [cui-areatype="menu-panel"]) > #bookmarks-toolbar-button {
   display: -moz-box;
 }
 
+#personal-bookmarks {
+  position: relative;
+}
+
 #PlacesToolbarDropIndicatorHolder {
   position: absolute;
-  top: 25%;
 }
 
 #nav-bar-customization-target > #personal-bookmarks,
 toolbar:not(#TabsToolbar) > #wrapper-personal-bookmarks,
 toolbar:not(#TabsToolbar) > #personal-bookmarks {
   -moz-box-flex: 1;
 }
 
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -296,16 +296,20 @@ var gInitialPages = [
   "about:blank",
   "about:newtab",
   "about:home",
   "about:privatebrowsing",
   "about:welcomeback",
   "about:sessionrestore"
 ];
 
+function isInitialPage(url) {
+  return gInitialPages.includes(url) || url == BROWSER_NEW_TAB_URL;
+}
+
 function* browserWindows() {
   let windows = Services.wm.getEnumerator("navigator:browser");
   while (windows.hasMoreElements())
     yield windows.getNext();
 }
 
 /**
 * We can avoid adding multiple load event listeners and save some time by adding
@@ -2746,29 +2750,33 @@ function URLBarSetURI(aURI) {
     let uri = aURI || gBrowser.currentURI;
     // Strip off "wyciwyg://" and passwords for the location bar
     try {
       uri = Services.uriFixup.createExposableURI(uri);
     } catch (e) {}
 
     // Replace initial page URIs with an empty string
     // only if there's no opener (bug 370555).
-    if (gInitialPages.includes(uri.spec) &&
+    if (isInitialPage(uri.spec) &&
         checkEmptyPageOrigin(gBrowser.selectedBrowser, uri)) {
       value = "";
     } else {
       // We should deal with losslessDecodeURI throwing for exotic URIs
       try {
         value = losslessDecodeURI(uri);
       } catch (ex) {
         value = "about:blank";
       }
     }
 
     valid = !isBlankPageURL(uri.spec) || uri.schemeIs("moz-extension");
+  } else if (isInitialPage(value) &&
+             checkEmptyPageOrigin(gBrowser.selectedBrowser)) {
+    value = "";
+    valid = true;
   }
 
   let isDifferentValidValue = valid && value != gURLBar.value;
   gURLBar.value = value;
   gURLBar.valueIsTyped = !valid;
   gURLBar.removeAttribute("usertyping");
   if (isDifferentValidValue) {
     gURLBar.selectionStart = gURLBar.selectionEnd = 0;
--- a/browser/base/content/contentSearchUI.js
+++ b/browser/base/content/contentSearchUI.js
@@ -241,17 +241,18 @@ ContentSearchUIController.prototype = {
   search(aEvent) {
     if (!this.defaultEngine) {
       return; // Not initialized yet.
     }
 
     let searchText = this.input;
     let searchTerms;
     if (this._table.hidden ||
-        aEvent.originalTarget.id == "contentSearchDefaultEngineHeader" ||
+        (aEvent.originalTarget &&
+          aEvent.originalTarget.id == "contentSearchDefaultEngineHeader") ||
         aEvent instanceof KeyboardEvent) {
       searchTerms = searchText.value;
     } else {
       searchTerms = this.suggestionAtIndex(this.selectedIndex) || searchText.value;
     }
     // Send an event that will perform a search and Firefox Health Report will
     // record that a search from the healthReportKey passed to the constructor.
     let eventData = {
--- a/browser/base/content/test/about/browser_aboutHome.js
+++ b/browser/base/content/test/about/browser_aboutHome.js
@@ -22,18 +22,19 @@ registerCleanupFunction(function() {
   Services.prefs.clearUserPref("browser.rights.override");
   Services.prefs.clearUserPref("browser.rights." + gRightsVersion + ".shown");
 });
 
 add_task(async function() {
   // The onboarding tour's notification would affect tests
   // and it isn't out test target so make sure disabling it
   // before any tests start.
-  await promiseDisableOnboardingTours();
+  await promiseDisableOnboardingToursAndActivityStream();
   is(false, Services.prefs.getBoolPref("browser.onboarding.enabled"), "Should disable the onboarding tours");
+  is(false, Services.prefs.getBoolPref("browser.newtabpage.activity-stream.aboutHome.enabled"), "Should disable activity stream on about:home");
 });
 
 add_task(async function() {
   info("Check that clearing cookies does not clear storage");
 
   await withSnippetsMap(
     () => {
       Cc["@mozilla.org/observer-service;1"]
--- a/browser/base/content/test/about/head.js
+++ b/browser/base/content/test/about/head.js
@@ -145,11 +145,14 @@ function waitForDocLoadAndStopIt(aExpect
 
     let mm = aBrowser.messageManager;
     mm.loadFrameScript("data:,(" + content_script.toString() + ")(" + aStopFromProgressListener + ");", true);
     mm.addMessageListener("Test:WaitForDocLoadAndStopIt", complete);
     info("waitForDocLoadAndStopIt: Waiting for URL: " + aExpectedURL);
   });
 }
 
-function promiseDisableOnboardingTours() {
-  return SpecialPowers.pushPrefEnv({set: [["browser.onboarding.enabled", false]]});
+function promiseDisableOnboardingToursAndActivityStream() {
+  return SpecialPowers.pushPrefEnv({set: [
+    ["browser.onboarding.enabled", false],
+    ["browser.newtabpage.activity-stream.aboutHome.enabled", false]
+  ]});
 }
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -4334,17 +4334,17 @@ OverflowableToolbar.prototype = {
       }, {once: true});
     });
   },
 
   _onClickChevron(aEvent) {
     if (this._chevron.open) {
       this._panel.hidePopup();
       this._chevron.open = false;
-    } else if (this._panel.state != "hiding") {
+    } else if (this._panel.state != "hiding" && !this._chevron.disabled) {
       this.show();
     }
   },
 
   _onPanelHiding(aEvent) {
     if (aEvent.target != this._panel) {
       // Ignore context menus, <select> popups, etc.
       return;
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -100,16 +100,17 @@ this.browserAction = class extends Exten
     }
 
     browserActionMap.set(extension, this);
 
     this.defaults.icon = await StartupCache.get(
       extension, ["browserAction", "default_icon"],
       () => IconDetails.normalize({
         path: options.default_icon,
+        iconType: "browserAction",
         themeIcons: options.theme_icons,
       }, extension));
 
     this.iconData.set(
       this.defaults.icon,
       await StartupCache.get(
         extension, ["browserAction", "default_icon_data"],
         () => this.getIconData(this.defaults.icon)));
@@ -617,16 +618,18 @@ this.browserAction = class extends Exten
 
           let title = browserAction.getProperty(tab, "title");
           return Promise.resolve(title);
         },
 
         setIcon: function(details) {
           let tab = getTab(details.tabId);
 
+          details.iconType = "browserAction";
+
           let icon = IconDetails.normalize(details, extension, context);
           browserAction.setProperty(tab, "icon", icon);
         },
 
         setBadgeText: function(details) {
           let tab = getTab(details.tabId);
 
           browserAction.setProperty(tab, "badgeText", details.text);
--- a/browser/components/extensions/ext-c-devtools-panels.js
+++ b/browser/components/extensions/ext-c-devtools-panels.js
@@ -55,30 +55,29 @@ class ChildDevToolsPanel extends Extensi
         return view;
       }
     }
 
     return null;
   }
 
   receiveMessage({name, data}) {
-    // Filter out any message received while the panel context do not yet
-    // exist.
-    if (!this.panelContext || !this.panelContext.contentWindow) {
-      return;
-    }
-
     // Filter out any message that is not related to the id of this
     // toolbox panel.
     if (!data || data.toolboxPanelId !== this.id) {
       return;
     }
 
     switch (name) {
       case "Extension:DevToolsPanelShown":
+        // Filter out *Shown message received while the panel context do not yet
+        // exist.
+        if (!this.panelContext || !this.panelContext.contentWindow) {
+          return;
+        }
         this.onParentPanelShown();
         break;
       case "Extension:DevToolsPanelHidden":
         this.onParentPanelHidden();
         break;
     }
   }
 
--- a/browser/components/extensions/ext-devtools-panels.js
+++ b/browser/components/extensions/ext-devtools-panels.js
@@ -56,23 +56,31 @@ class ParentDevToolsPanel {
 
     this.panelOptions = panelOptions;
 
     this.context.callOnClose(this);
 
     this.id = this.panelOptions.id;
 
     this.onToolboxPanelSelect = this.onToolboxPanelSelect.bind(this);
+    this.onToolboxHostWillChange = this.onToolboxHostWillChange.bind(this);
+    this.onToolboxHostChanged = this.onToolboxHostChanged.bind(this);
+
+    this.unwatchExtensionProxyContextLoad = null;
+    this.waitTopLevelContext = new Promise(resolve => {
+      this._resolveTopLevelContext = resolve;
+    });
+
+    // References to the panel browser XUL element and the toolbox window global which
+    // contains the devtools panel UI.
+    this.browser = null;
+    this.browserContainerWindow = null;
 
     this.panelAdded = false;
     this.addPanel();
-
-    this.waitTopLevelContext = new Promise(resolve => {
-      this._resolveTopLevelContext = resolve;
-    });
   }
 
   addPanel() {
     const {icon, title} = this.panelOptions;
     const extensionName = this.context.extension.name;
 
     this.toolbox.addAdditionalTool({
       id: this.id,
@@ -83,26 +91,133 @@ class ParentDevToolsPanel {
       invertIconForLightTheme: false,
       visibilityswitch:  `devtools.webext-${this.id}.enabled`,
       isTargetSupported: target => target.isLocalTab,
       build: (window, toolbox) => {
         if (toolbox !== this.toolbox) {
           throw new Error("Unexpected toolbox received on addAdditionalTool build property");
         }
 
-        const destroy = this.buildPanel(window, toolbox);
+        const destroy = this.buildPanel(window);
 
         return {toolbox, destroy};
       },
     });
 
     this.panelAdded = true;
   }
 
-  buildPanel(window, toolbox) {
+  buildPanel(window) {
+    const {toolbox} = this;
+
+    this.createBrowserElement(window);
+
+    // Store the last panel's container element (used to restore it when the toolbox
+    // host is switched between docked and undocked).
+    this.browserContainerWindow = window;
+
+    toolbox.on("select", this.onToolboxPanelSelect);
+    toolbox.on("host-will-change", this.onToolboxHostWillChange);
+    toolbox.on("host-changed", this.onToolboxHostChanged);
+
+    // Return a cleanup method that is when the panel is destroyed, e.g.
+    // - when addon devtool panel has been disabled by the user from the toolbox preferences,
+    //   its ParentDevToolsPanel instance is still valid, but the built devtools panel is removed from
+    //   the toolbox (and re-built again if the user re-enables it from the toolbox preferences panel)
+    // - when the creator context has been destroyed, the ParentDevToolsPanel close method is called,
+    //   it removes the tool definition from the toolbox, which will call this destroy method.
+    return () => {
+      this.destroyBrowserElement();
+      this.browserContainerWindow = null;
+      toolbox.off("select", this.onToolboxPanelSelect);
+      toolbox.off("host-will-change", this.onToolboxHostWillChange);
+      toolbox.off("host-changed", this.onToolboxHostChanged);
+    };
+  }
+
+  onToolboxHostWillChange() {
+    // NOTE: Using a content iframe here breaks the devtools panel
+    // switching between docked and undocked mode,
+    // because of a swapFrameLoader exception (see bug 1075490),
+    // destroy the browser and recreate it after the toolbox host has been
+    // switched is a reasonable workaround to fix the issue on release and beta
+    // Firefox versions (at least until the underlying bug can be fixed).
+    if (this.browser) {
+      // Fires a panel.onHidden event before destroying the browser element because
+      // the toolbox hosts is changing.
+      if (this.visible) {
+        this.context.parentMessageManager.sendAsyncMessage("Extension:DevToolsPanelHidden", {
+          toolboxPanelId: this.id,
+        });
+      }
+
+      this.destroyBrowserElement();
+    }
+  }
+
+  async onToolboxHostChanged() {
+    if (this.browserContainerWindow) {
+      this.createBrowserElement(this.browserContainerWindow);
+
+      // Fires a panel.onShown event once the browser element has been recreated
+      // after the toolbox hosts has been changed (needed to provide the new window
+      // object to the extension page that has created the devtools panel).
+      if (this.visible) {
+        await this.waitTopLevelContext;
+
+        this.context.parentMessageManager.sendAsyncMessage("Extension:DevToolsPanelShown", {
+          toolboxPanelId: this.id,
+        });
+      }
+    }
+  }
+
+  async onToolboxPanelSelect(what, id) {
+    if (!this.waitTopLevelContext || !this.panelAdded) {
+      return;
+    }
+
+    // Wait that the panel is fully loaded and emit show.
+    await this.waitTopLevelContext;
+
+    if (!this.visible && id === this.id) {
+      this.visible = true;
+    } else if (this.visible && id !== this.id) {
+      this.visible = false;
+    }
+
+    const extensionMessage = `Extension:DevToolsPanel${this.visible ? "Shown" : "Hidden"}`;
+    this.context.parentMessageManager.sendAsyncMessage(extensionMessage, {
+      toolboxPanelId: this.id,
+    });
+  }
+
+  close() {
+    const {toolbox} = this;
+
+    if (!toolbox) {
+      throw new Error("Unable to destroy a closed devtools panel");
+    }
+
+    // Explicitly remove the panel if it is registered and the toolbox is not
+    // closing itself.
+    if (this.panelAdded && toolbox.isToolRegistered(this.id)) {
+      toolbox.removeAdditionalTool(this.id);
+    }
+
+    this.waitTopLevelContext = null;
+    this._resolveTopLevelContext = null;
+    this.context = null;
+    this.toolbox = null;
+    this.browser = null;
+    this.browserContainerWindow = null;
+  }
+
+  createBrowserElement(window) {
+    const {toolbox} = this;
     const {url} = this.panelOptions;
     const {document} = window;
 
     const browser = document.createElementNS(XUL_NS, "browser");
     browser.setAttribute("type", "content");
     browser.setAttribute("disableglobalhistory", "true");
     browser.setAttribute("style", "width: 100%; height: 100%;");
     browser.setAttribute("transparent", "true");
@@ -114,28 +229,22 @@ class ParentDevToolsPanel {
 
     const {extension} = this.context;
 
     let awaitFrameLoader = Promise.resolve();
     if (extension.remote) {
       browser.setAttribute("remote", "true");
       browser.setAttribute("remoteType", E10SUtils.EXTENSION_REMOTE_TYPE);
       awaitFrameLoader = promiseEvent(browser, "XULFrameLoaderCreated");
-    } else if (!AppConstants.RELEASE_OR_BETA) {
-      // NOTE: Using a content iframe here breaks the devtools panel
-      // switching between docked and undocked mode,
-      // because of a swapFrameLoader exception (see bug 1075490).
-      browser.setAttribute("type", "chrome");
-      browser.setAttribute("forcemessagemanager", true);
     }
 
     let hasTopLevelContext = false;
 
     // Listening to new proxy contexts.
-    const unwatchExtensionProxyContextLoad = watchExtensionProxyContextLoad(this, context => {
+    this.unwatchExtensionProxyContextLoad = watchExtensionProxyContextLoad(this, context => {
       // Keep track of the toolbox and target associated to the context, which is
       // needed by the API methods implementation.
       context.devToolsToolbox = toolbox;
 
       if (!hasTopLevelContext) {
         hasTopLevelContext = true;
 
         // Resolve the promise when the root devtools_panel context has been created.
@@ -149,75 +258,36 @@ class ParentDevToolsPanel {
     extensions.emit("extension-browser-inserted", browser, {
       devtoolsToolboxInfo: {
         toolboxPanelId: this.id,
         inspectedWindowTabId: getTargetTabIdForToolbox(toolbox),
       },
     });
 
     browser.loadURI(url);
-
-    toolbox.on("select", this.onToolboxPanelSelect);
-
-    // Return a cleanup method that is when the panel is destroyed, e.g.
-    // - when addon devtool panel has been disabled by the user from the toolbox preferences,
-    //   its ParentDevToolsPanel instance is still valid, but the built devtools panel is removed from
-    //   the toolbox (and re-built again if the user re-enable it from the toolbox preferences panel)
-    // - when the creator context has been destroyed, the ParentDevToolsPanel close method is called,
-    //   it remove the tool definition from the toolbox, which will call this destroy method.
-    return () => {
-      unwatchExtensionProxyContextLoad();
-      browser.remove();
-      toolbox.off("select", this.onToolboxPanelSelect);
-
-      // If the panel has been disabled from the toolbox preferences,
-      // we need to re-initialize the waitTopLevelContext Promise.
-      this.waitTopLevelContext = new Promise(resolve => {
-        this._resolveTopLevelContext = resolve;
-      });
-    };
   }
 
-  onToolboxPanelSelect(what, id) {
-    if (!this.waitTopLevelContext || !this.panelAdded) {
-      return;
-    }
-    if (!this.visible && id === this.id) {
-      // Wait that the panel is fully loaded and emit show.
-      this.waitTopLevelContext.then(() => {
-        this.visible = true;
-        this.context.parentMessageManager.sendAsyncMessage("Extension:DevToolsPanelShown", {
-          toolboxPanelId: this.id,
-        });
-      });
-    } else if (this.visible && id !== this.id) {
-      this.visible = false;
-      this.context.parentMessageManager.sendAsyncMessage("Extension:DevToolsPanelHidden", {
-        toolboxPanelId: this.id,
-      });
-    }
-  }
-
-  close() {
-    const {toolbox} = this;
-
-    if (!toolbox) {
-      throw new Error("Unable to destroy a closed devtools panel");
+  destroyBrowserElement() {
+    const {browser, unwatchExtensionProxyContextLoad} = this;
+    if (unwatchExtensionProxyContextLoad) {
+      this.unwatchExtensionProxyContextLoad = null;
+      unwatchExtensionProxyContextLoad();
     }
 
-    // Explicitly remove the panel if it is registered and the toolbox is not
-    // closing itself.
-    if (this.panelAdded && toolbox.isToolRegistered(this.id) && !toolbox._destroyer) {
-      toolbox.removeAdditionalTool(this.id);
+    if (browser) {
+      browser.remove();
+      this.browser = null;
     }
 
-    this.context = null;
-    this.toolbox = null;
-    this.waitTopLevelContext = null;
-    this._resolveTopLevelContext = null;
+    // If the panel has been removed or disabled (e.g. from the toolbox preferences
+    // or during the toolbox switching between docked and undocked),
+    // we need to re-initialize the waitTopLevelContext Promise.
+    this.waitTopLevelContext = new Promise(resolve => {
+      this._resolveTopLevelContext = resolve;
+    });
   }
 }
 
 class DevToolsSelectionObserver extends EventEmitter {
   constructor(context) {
     if (!context.devToolsToolbox) {
       // This should never happen when this constructor is called with a valid
       // devtools extension context.
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -143,22 +143,21 @@ this.pageAction = class extends Extensio
         button.setAttribute("style", style);
       }
 
       button.hidden = !tabData.show;
     });
   }
 
   getIconData(icons) {
-    // These URLs should already be properly escaped, but make doubly sure CSS
-    // string escape characters are escaped here, since they could lead to a
-    // sandbox break.
-    let escape = str => str.replace(/[\\\s"]/g, encodeURIComponent);
-
-    let getIcon = size => escape(IconDetails.getPreferredIcon(icons, this.extension, size).icon);
+    let getIcon = size => {
+      let {icon} = IconDetails.getPreferredIcon(icons, this.extension, size);
+      // TODO: implement theme based icon for pageAction (Bug 1398156)
+      return IconDetails.escapeUrl(icon);
+    };
 
     let style = `
       --webextension-urlbar-image: url("${getIcon(16)}");
       --webextension-urlbar-image-2x: url("${getIcon(32)}");
     `;
 
     return {style};
   }
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js
@@ -292,8 +292,80 @@ add_task(async function testDetailsObjec
 
     if (!test.menuResolutions) {
       continue;
     }
   }
 
   await extension.unload();
 });
+
+// NOTE: The current goal of this test is ensuring that Bug 1397196 has been fixed,
+// and so this test extension manifest has a browser action which specify
+// a theme based icon and a pageAction, both the pageAction and the browserAction
+// have a common default_icon.
+//
+// Once Bug 1398156 will be fixed, this test should be converted into testing that
+// the browserAction and pageAction themed icons (as well as any other cached icon,
+// e.g. the sidebar and devtools panel icons) can be specified in the same extension
+// and do not conflict with each other.
+//
+// This test currently fails without the related fix, but only if the browserAction
+// API has been already loaded before the pageAction, otherwise the icons will be cached
+// in the opposite order and the test is not able to reproduce the issue anymore.
+add_task(async function testPageActionIconLoadingOnBrowserActionThemedIcon() {
+  async function background() {
+    const tabs = await browser.tabs.query({active: true});
+    await browser.pageAction.show(tabs[0].id);
+
+    browser.test.sendMessage("ready");
+  }
+
+  const extension = ExtensionTestUtils.loadExtension({
+    background,
+    manifest: {
+      "name": "Foo Extension",
+
+      "browser_action": {
+        "default_icon": "common_cached_icon.png",
+        "default_popup": "default_popup.html",
+        "default_title": "BrowserAction title",
+        "theme_icons": [
+          {
+            "dark": "1.png",
+            "light": "2.png",
+            "size": 16
+          }
+        ],
+      },
+
+      "page_action": {
+        "default_icon": "common_cached_icon.png",
+        "default_popup": "default_popup.html",
+        "default_title": "PageAction title",
+      },
+
+      "permissions": ["tabs"],
+    },
+
+    "files": {
+      "common_cached_icon.png": imageBuffer,
+      "1.png": imageBuffer,
+      "2.png": imageBuffer,
+      "default_popup.html": "<!DOCTYPE html><html><body>popup</body></html>",
+    },
+  });
+
+  await extension.startup();
+
+  await extension.awaitMessage("ready");
+
+  await promiseAnimationFrame();
+
+  let pageActionId = `${makeWidgetId(extension.id)}-page-action`;
+  let pageActionImage = document.getElementById(pageActionId);
+
+  const iconURL = new URL(getListStyleImage(pageActionImage));
+
+  is(iconURL.pathname, "/common_cached_icon.png", "Got the expected pageAction icon url");
+
+  await extension.unload();
+});
--- a/browser/components/extensions/test/browser/browser_ext_devtools_panel.js
+++ b/browser/components/extensions/test/browser/browser_ext_devtools_panel.js
@@ -304,8 +304,132 @@ add_task(async function test_devtools_pa
 
   await gDevTools.closeToolbox(target);
   await target.destroy();
 
   await extension.unload();
 
   await BrowserTestUtils.removeTab(tab);
 });
+
+add_task(async function test_devtools_page_panels_switch_toolbox_host() {
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
+
+  function devtools_panel() {
+    const hasDevToolsAPINamespace = "devtools" in browser;
+
+    browser.test.sendMessage("devtools_panel_loaded", {
+      hasDevToolsAPINamespace,
+      panelLoadedURL: window.location.href,
+    });
+  }
+
+  async function devtools_page() {
+    const panel = await browser.devtools.panels.create(
+      "Test Panel", "fake-icon.png", "devtools_panel.html"
+    );
+
+    panel.onShown.addListener(panelWindow => {
+      browser.test.sendMessage("devtools_panel_shown", panelWindow.location.href);
+    });
+
+    panel.onHidden.addListener(() => {
+      browser.test.sendMessage("devtools_panel_hidden");
+    });
+
+    browser.test.sendMessage("devtools_panel_created");
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      devtools_page: "devtools_page.html",
+    },
+    files: {
+      "devtools_page.html": `<!DOCTYPE html>
+      <html>
+       <head>
+         <meta charset="utf-8">
+       </head>
+       <body>
+         <script src="devtools_page.js"></script>
+       </body>
+      </html>`,
+      "devtools_page.js": devtools_page,
+      "devtools_panel.html":  `<!DOCTYPE html>
+      <html>
+       <head>
+         <meta charset="utf-8">
+       </head>
+       <body>
+         DEVTOOLS PANEL
+         <script src="devtools_panel.js"></script>
+       </body>
+      </html>`,
+      "devtools_panel.js": devtools_panel,
+    },
+  });
+
+  await extension.startup();
+
+
+  let target = gDevTools.getTargetForTab(tab);
+
+  const toolbox = await gDevTools.showToolbox(target, "webconsole");
+  info("developer toolbox opened");
+
+  await extension.awaitMessage("devtools_panel_created");
+
+  const toolboxAdditionalTools = toolbox.getAdditionalTools();
+
+  is(toolboxAdditionalTools.length, 1,
+     "Got the expected number of toolbox specific panel registered.");
+
+  const panelDef = toolboxAdditionalTools[0];
+  const panelId = panelDef.id;
+
+  info("Selecting the addon devtools panel");
+  await gDevTools.showToolbox(target, panelId);
+
+  info("Wait for the panel to show and load for the first time");
+  const panelShownURL = await extension.awaitMessage("devtools_panel_shown");
+
+  const {
+    panelLoadedURL,
+    hasDevToolsAPINamespace,
+  } = await extension.awaitMessage("devtools_panel_loaded");
+
+  is(panelShownURL, panelLoadedURL, "Got the expected panel URL on the first load");
+  ok(hasDevToolsAPINamespace, "The devtools panel has the devtools API on the first load");
+
+  const originalToolboxHostType = toolbox.hostType;
+
+  info("Switch the toolbox from docked on bottom to docked on side");
+  toolbox.switchHost("side");
+
+  info("Wait for the panel to emit hide, show and load messages once docked on side");
+  await extension.awaitMessage("devtools_panel_hidden");
+  const dockedOnSideShownURL = await extension.awaitMessage("devtools_panel_shown");
+
+  is(dockedOnSideShownURL, panelShownURL,
+     "Got the expected panel url once the panel shown event has been emitted on toolbox host changed");
+
+  const dockedOnSideLoaded = await extension.awaitMessage("devtools_panel_loaded");
+
+  is(dockedOnSideLoaded.panelLoadedURL, panelShownURL,
+     "Got the expected panel url once the panel has been reloaded on toolbox host changed");
+  ok(dockedOnSideLoaded.hasDevToolsAPINamespace,
+     "The devtools panel has the devtools API once the toolbox host has been changed");
+
+  info("Switch the toolbox from docked on bottom to the original dock mode");
+  toolbox.switchHost(originalToolboxHostType);
+
+  info("Wait for the panel test messages once toolbox dock mode has been restored");
+  await extension.awaitMessage("devtools_panel_hidden");
+  await extension.awaitMessage("devtools_panel_shown");
+  await extension.awaitMessage("devtools_panel_loaded");
+
+  await gDevTools.closeToolbox(target);
+  await target.destroy();
+
+  await extension.unload();
+
+  await BrowserTestUtils.removeTab(tab);
+});
--- a/browser/components/extensions/test/browser/browser_ext_identity_indication.js
+++ b/browser/components/extensions/test/browser/browser_ext_identity_indication.js
@@ -84,14 +84,15 @@ add_task(async function testIdentityIndi
 
   await extension.startup();
 
   confirmDefaults();
 
   let url = await extension.awaitMessage("url");
   await BrowserTestUtils.withNewTab({gBrowser, url}, async function() {
     confirmExtensionPage();
+    is(gURLBar.value, "", "The URL bar is blank");
   });
 
   await extension.unload();
 
   confirmDefaults();
 });
--- a/browser/components/extensions/test/browser/browser_ext_url_overrides_newtab.js
+++ b/browser/components/extensions/test/browser/browser_ext_url_overrides_newtab.js
@@ -40,8 +40,57 @@ add_task(async function test_sending_mes
 
   let url = await extension.awaitMessage("from-newtab-page");
   ok(url.endsWith(NEWTAB_URI_1),
      "Newtab url is overriden by the extension.");
 
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
   await extension.unload();
 });
+
+/**
+ * Ensure we don't show the extension URL in the URL bar temporarily in new tabs
+ * while we're switching remoteness (when the URL we're loading and the
+ * default content principal are different).
+ */
+add_task(async function dontTemporarilyShowAboutExtensionPath() {
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      name: "Test Extension",
+      applications: {
+        gecko: {
+          id: "newtab@mochi.test",
+        },
+      },
+      chrome_url_overrides: {
+        newtab: "newtab.html",
+      },
+    },
+    background() {
+      browser.test.sendMessage("url", browser.runtime.getURL("newtab.html"));
+    },
+    files: {
+      "newtab.html": "<h1>New tab!</h1>",
+    },
+    useAddonManager: "temporary",
+  });
+
+  await extension.startup();
+  let url = await extension.awaitMessage("url");
+
+  let wpl = {
+    onLocationChange() {
+      is(gURLBar.value, "", "URL bar value should stay empty.");
+    },
+  };
+  gBrowser.addProgressListener(wpl);
+
+  let tab = await BrowserTestUtils.openNewForegroundTab({gBrowser, url});
+
+  gBrowser.removeProgressListener(wpl);
+  is(gURLBar.value, "", "URL bar value should be empty.");
+  ContentTask.spawn(tab.linkedBrowser, null, function() {
+    is(content.document.body.textContent, "New tab!", "New tab page is loaded.");
+  });
+
+  await BrowserTestUtils.removeTab(tab);
+  await extension.unload();
+});
--- a/browser/components/nsBrowserContentHandler.js
+++ b/browser/components/nsBrowserContentHandler.js
@@ -1,16 +1,18 @@
 /* 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/. */
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/AppConstants.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "HeadlessShell",
+                                  "resource:///modules/HeadlessShell.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "LaterRun",
                                   "resource:///modules/LaterRun.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
                                   "resource:///modules/RecentWindow.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ShellService",
                                   "resource:///modules/ShellService.jsm");
@@ -461,16 +463,20 @@ nsBrowserContentHandler.prototype = {
               "  --new-window <url> Open <url> in a new window.\n" +
               "  --new-tab <url>    Open <url> in a new tab.\n" +
               "  --private-window <url> Open <url> in a new private window.\n";
     if (AppConstants.platform == "win") {
       info += "  --preferences      Open Options dialog.\n";
     } else {
       info += "  --preferences      Open Preferences dialog.\n";
     }
+    if (AppConstants.platform == "win" || AppConstants.MOZ_WIDGET_GTK) {
+      info += "  --screenshot [<path>] Save screenshot to <path> or in working directory.\n";
+      info += "  --window-size width[,height] Width and optionally height of screenshot.\n";
+    }
     info += "  --search <term>    Search <term> with your default search engine.\n";
     return info;
   },
 
   /* nsIBrowserHandler */
 
   get defaultArgs() {
     var prefb = Services.prefs;
@@ -736,16 +742,21 @@ nsDefaultCommandLineHandler.prototype = 
       while ((ar = cmdLine.handleFlagWithParam("url", false))) {
         var uri = resolveURIInternal(cmdLine, ar);
         urilist.push(uri);
       }
     } catch (e) {
       Components.utils.reportError(e);
     }
 
+    if (cmdLine.findFlag("screenshot", true) != -1) {
+      HeadlessShell.handleCmdLineArgs(cmdLine, urilist.filter(shouldLoadURI).map(u => u.spec));
+      return;
+    }
+
     for (let i = 0; i < cmdLine.length; ++i) {
       var curarg = cmdLine.getArgument(i);
       if (curarg.match(/^-/)) {
         Components.utils.reportError("Warning: unrecognized command line flag " + curarg + "\n");
         // To emulate the pre-nsICommandLine behavior, we ignore
         // the argument after an unrecognized flag.
         ++i;
       } else {
--- a/browser/components/preferences/colors.xul
+++ b/browser/components/preferences/colors.xul
@@ -80,18 +80,18 @@
         </hbox>
       </groupbox>
     </hbox>
 #ifdef XP_WIN
     <vbox align="start">
 #else
     <vbox>
 #endif
-      <label accesskey="&overrideDefaultPageColors.accesskey;"
-             control="useDocumentColors">&overrideDefaultPageColors.label;</label>
+      <label accesskey="&overrideDefaultPageColors2.accesskey;"
+             control="useDocumentColors">&overrideDefaultPageColors2.label;</label>
       <hbox>
         <menulist id="useDocumentColors" preference="browser.display.document_color_use" flex="1">
           <menupopup>
             <menuitem label="&overrideDefaultPageColors.always.label;"
                       value="2" id="documentColorAlways"/>
             <menuitem label="&overrideDefaultPageColors.auto.label;"
                       value="0" id="documentColorAutomatic"/>
             <menuitem label="&overrideDefaultPageColors.never.label;"
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -1,16 +1,21 @@
 /* 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/. */
 
 /* import-globals-from preferences.js */
 /* import-globals-from ../../../../toolkit/mozapps/preferences/fontbuilder.js */
 /* import-globals-from ../../../base/content/aboutDialog-appUpdater.js */
 
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionPreferencesManager",
+                                  "resource://gre/modules/ExtensionPreferencesManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
+                                  "resource://gre/modules/AddonManager.jsm");
+
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/Downloads.jsm");
 Components.utils.import("resource://gre/modules/FileUtils.jsm");
 Components.utils.import("resource:///modules/ShellService.jsm");
 Components.utils.import("resource:///modules/TransientPrefs.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/AppConstants.jsm");
 Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
@@ -231,16 +236,18 @@ var gMainPane = {
         gMainPane.setDefaultBrowser);
     }
     setEventListener("useCurrent", "command",
       gMainPane.setHomePageToCurrent);
     setEventListener("useBookmark", "command",
       gMainPane.setHomePageToBookmark);
     setEventListener("restoreDefaultHomePage", "command",
       gMainPane.restoreDefaultHomePage);
+    setEventListener("disableHomePageExtension", "command",
+                     gMainPane.makeDisableControllingExtension("homepage_override"));
     setEventListener("chooseLanguage", "command",
       gMainPane.showLanguages);
     setEventListener("translationAttributionImage", "click",
       gMainPane.openTranslationProviderAttribution);
     setEventListener("translateButton", "command",
       gMainPane.showTranslationExceptions);
     setEventListener("font.language.group", "change",
       gMainPane._rebuildFonts);
@@ -615,16 +622,29 @@ var gMainPane = {
    *   The deprecated option is not exposed in UI; however, if the user has it
    *   selected and doesn't change the UI for this preference, the deprecated
    *   option is preserved.
    */
 
   syncFromHomePref() {
     let homePref = document.getElementById("browser.startup.homepage");
 
+    // Set the "Use Current Page(s)" button's text and enabled state.
+    this._updateUseCurrentButton();
+
+    // This is an async task.
+    handleControllingExtension("homepage_override")
+      .then((isControlled) => {
+        // Disable or enable the inputs based on if this is controlled by an extension.
+        document.querySelectorAll("#browserHomePage, .homepage-button")
+          .forEach((button) => {
+            button.disabled = isControlled;
+          });
+      });
+
     // If the pref is set to about:home or about:newtab, set the value to ""
     // to show the placeholder text (about:home title) rather than
     // exposing those URLs to users.
     let defaultBranch = Services.prefs.getDefaultBranch("");
     let defaultValue = defaultBranch.getComplexValue("browser.startup.homepage",
       Ci.nsIPrefLocalizedString).data;
     let currentValue = homePref.value.toLowerCase();
     if (currentValue == "about:home" ||
@@ -690,27 +710,31 @@ var gMainPane = {
       homePage.value = rv.urls.join("|");
     }
   },
 
   /**
    * Switches the "Use Current Page" button between its singular and plural
    * forms.
    */
-  _updateUseCurrentButton() {
+  async _updateUseCurrentButton() {
     let useCurrent = document.getElementById("useCurrent");
-
-
     let tabs = this._getTabsForHomePage();
 
     if (tabs.length > 1)
       useCurrent.label = useCurrent.getAttribute("label2");
     else
       useCurrent.label = useCurrent.getAttribute("label1");
 
+    // If the homepage is controlled by an extension then you can't use this.
+    if (await getControllingExtensionId("homepage_override")) {
+      useCurrent.disabled = true;
+      return;
+    }
+
     // In this case, the button's disabled state is set by preferences.xml.
     let prefName = "pref.browser.homepage.disable_button.current_page";
     if (document.getElementById(prefName).locked)
       return;
 
     useCurrent.disabled = !tabs.length
   },
 
@@ -744,16 +768,24 @@ var gMainPane = {
   /**
    * Restores the default home page as the user's home page.
    */
   restoreDefaultHomePage() {
     var homePage = document.getElementById("browser.startup.homepage");
     homePage.value = homePage.defaultValue;
   },
 
+  makeDisableControllingExtension(pref) {
+    return async function disableExtension() {
+      let id = await getControllingExtensionId(pref);
+      let addon = await AddonManager.getAddonByID(id);
+      addon.userDisabled = true;
+    };
+  },
+
   /**
    * Utility function to enable/disable the button specified by aButtonID based
    * on the value of the Boolean preference specified by aPreferenceID.
    */
   updateButtons(aButtonID, aPreferenceID) {
     var button = document.getElementById(aButtonID);
     var preference = document.getElementById(aPreferenceID);
     button.disabled = preference.value != true;
@@ -2566,16 +2598,76 @@ function getLocalHandlerApp(aFile) {
   var localHandlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
     createInstance(Ci.nsILocalHandlerApp);
   localHandlerApp.name = getFileDisplayName(aFile);
   localHandlerApp.executable = aFile;
 
   return localHandlerApp;
 }
 
+let extensionControlledContentIds = {
+  "homepage_override": "browserHomePageExtensionContent",
+};
+
+/**
+  * Check if a pref is being managed by an extension.
+  */
+function getControllingExtensionId(settingName) {
+  return ExtensionPreferencesManager.getControllingExtensionId(settingName);
+}
+
+function getControllingExtensionEl(settingName) {
+  return document.getElementById(extensionControlledContentIds[settingName]);
+}
+
+async function handleControllingExtension(prefName) {
+  let controllingExtensionId = await getControllingExtensionId(prefName);
+
+  if (controllingExtensionId) {
+    showControllingExtension(prefName, controllingExtensionId);
+  } else {
+    hideControllingExtension(prefName);
+  }
+
+  return !!controllingExtensionId;
+}
+
+async function showControllingExtension(settingName, extensionId) {
+  let extensionControlledContent = getControllingExtensionEl(settingName);
+  // Tell the user what extension is controlling the homepage.
+  let addon = await AddonManager.getAddonByID(extensionId);
+  const defaultIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
+  let stringParts = document
+    .getElementById("bundlePreferences")
+    .getString(`extensionControlled.${settingName}`)
+    .split("%S");
+  let description = extensionControlledContent.querySelector("description");
+
+  // Remove the old content from the description.
+  while (description.firstChild) {
+    description.firstChild.remove();
+  }
+
+  // Populate the description.
+  description.appendChild(document.createTextNode(stringParts[0]));
+  let image = document.createElement("image");
+  image.setAttribute("src", addon.iconURL || defaultIcon);
+  image.classList.add("extension-controlled-icon");
+  description.appendChild(image);
+  description.appendChild(document.createTextNode(` ${addon.name}`));
+  description.appendChild(document.createTextNode(stringParts[1]));
+
+  // Show the controlling extension row and hide the old label.
+  extensionControlledContent.hidden = false;
+}
+
+function hideControllingExtension(settingName) {
+  getControllingExtensionEl(settingName).hidden = true;
+}
+
 /**
  * An enumeration of items in a JS array.
  *
  * FIXME: use ArrayConverter once it lands (bug 380839).
  *
  * @constructor
  */
 function ArrayEnumerator(aItems) {
--- a/browser/components/preferences/in-content/main.xul
+++ b/browser/components/preferences/in-content/main.xul
@@ -343,16 +343,23 @@
 </groupbox>
 
 <!-- Home Page -->
 <groupbox id="homepageGroup"
           data-category="paneGeneral"
           hidden="true">
   <caption><label>&homepage2.label;</label></caption>
 
+  <hbox id="browserHomePageExtensionContent" align="center">
+    <description control="disableHomePageExtension" flex="1" />
+    <button id="disableHomePageExtension"
+            class="extension-controlled-button accessory-button"
+            label="&disableExtension.label;" />
+  </hbox>
+
   <vbox>
     <textbox id="browserHomePage"
              class="uri-element"
              type="autocomplete"
              autocompletesearch="unifiedcomplete"
              onsyncfrompreference="return gMainPane.syncFromHomePref();"
              onsynctopreference="return gMainPane.syncToHomePref(this.value);"
              placeholder="&abouthome.pageTitle;"
@@ -567,17 +574,17 @@
       <spacer flex="1" />
       <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
       <hbox>
         <button id="colors"
                 class="accessory-button"
                 icon="select-color"
                 label="&colors.label;"
                 accesskey="&colors.accesskey;"
-                searchkeywords="&overrideDefaultPageColors.label;
+                searchkeywords="&overrideDefaultPageColors2.label;
                                 &overrideDefaultPageColors.always.label;
                                 &overrideDefaultPageColors.auto.label;
                                 &overrideDefaultPageColors.never.label;
                                 &color;
                                 &textColor2.label;
                                 &backgroundColor2.label;
                                 &useSystemColors.label;
                                 &underlineLinks.label;
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..9aff671021e1a247201555ccdbe751f83c5df5aa
GIT binary patch
literal 5156
zc$|e;2T&B<vYuthIY>@R1`#BuCA%n@g#{%E$dWUXbA}~@h$2W(BnzU5h@?eClCb0)
z1<6@*eCxgS>fYa~SFcZ<>FGZG^_l52JyqRsEif)I004vlC>X4jOdeVZr2qgaia#0v
za74Ph346HNgQO_BcH`l?_sEEu{#cV~sKX4fT=~x=#K&IC9X)5U4A)*oM+E@hr;=Pe
z!o$iCq`H9)00i;^KzK9&oMBbrs{r650s!k)03eqI0Q4R&zv#<j7x1mM?!kcTKck?j
zBm=7<@Yc{#BUr#=0*SM&2|G9d0J)q7OvTWDaw|K`!+gZ2LusY?*!O{8m!Qc97n@2O
z=8k7P)AJldx)DUVsu>bn+BgWDX!fF{L|1hpk!@}2Y98hWkTwoD8b<?>?WpP?{?vy0
z>pVJITY+QYd6>pC+6yhgo+n}5&+ZRyH~Z;bk7o)Y9c!P2#gw&(pCJ#op~s;uJJUfJ
z+2gNx=4iZ&u-K)b@rhwnxsOP(Zkiw^lZq;TZoxZ(H)eQ9=bWj&p7~>`5iVS7o?xQR
z6)HObT>6RsOv>q)khj!iL`)P!Ex4i2%;4GfoOn8=a}LK7%{t&hv1-b^+LAcm@!MOB
z`k^3wxG9p=kBT#2^?gFK^T?bH%%xFTl%|)gwA}gIDFKWPSidWO6oFf=L|{?f+@hcK
zes835ktEH*374NXy$Cf89$sR+_pV%6JmvQy`nZhsUbF(mkCL5!P<(G`vo4Gc6HnY9
zv3}V#d*}8&O{D2DJd{c<o5}Oxb2Pp*@%}qylf32JQpK{>8c4|AawVUF1YboU5$Y)_
zT@v+{F-~6j1xmg^MWjc4b^>OP;QG$+$E^h{k41&8fG(Y{(3W;=3Rp9q$QpY_)rx<0
zONEUA3nJrmhW%hF;2sF>TE*ecW-7a?Q6s#CZ-O7qPf8Nr)5WE1{R^j)G+w5UOm=^y
zP@;k^y<|EqC<>QR<E;xr!#yfxVa(w}LRGYw3SEcdwIekbt`wgO@p<TRt2}HQzY&KF
zEJIj?RG6L`M(ImNC%Exyg$A;j8FB8v@qd+5S<5TU+CdpXjedcaQHX9xZ92;du+|N}
z1+Ml}Q}Aa)`=F<Z81PmRw$Q-!s1#bVlNadH#o2_;x(G+S@toMsI@YnKOtdsIAoAvx
z@*Hl59Q}fv=gWm!VZm@8bBPQAOyv<xdmQ@yB?>2g`L4&XD95octl0>1n$8(aGS<La
zsQdcVMh|WjURmGFM_7Ls++zF~;k(3XO!Ol!8fZ}45@qC2wzdVYpI5ck-+_&SXfvwK
zI5;<OSIFN-OicuTrNLDjW&```ecfhBTV!uj_Pt6}M}4JlPR}B-o7_#w_7WQVJZlYO
z!z<SPx-3%6xzGRo;oJK&kLrwQZ-^(y>pciJNL$_Y*XiRdEe?OB!Qi57OfIJ|FwH=l
zE^GZG3Kg8OS68YEkL_%;eo3HaqigSlH0)pFyhXso`{<qJK<k3Q1`a>5W*y4K?xN|s
z(BlI%&RobNdh^Pn=iB|&J<2;vD)UllxT>;nr7p!!rA~EA@(@8~-ee;B!-`tH_|B5>
zaw4X~@EH5FT?dcOD*X(TlGyi_M){sB_97X&pn3s#!x06piZbPDpy3YaQ>z2>R0kcg
z%bmlD`&(xa!mL_5=x(~XmHQA*5-X=if<4y-PbUvQxdYAu1P*@8m5~#)f>Vi3Re|c?
zRQX8=s-_*0&V|(XWD|-%aD`q)-f*f%+A5@&MNj_VgjwJydNxml;)W?7;v;_$G_%7S
zzEp1x-xww?eK-?qhyT6{$s9qplC#A>UQpJWEVWC*tHc2U;EAOaG`lao2vx+CMP5DU
z<qc<6)$DeT=;@Q5()V}~t?8T|zvPJ*+NEHb6Gamr=|sv!5jEWfYloi04f7Er@8UZL
z&=~l0v$fx^Ak@0t#11@n*}x_j_te>xDNSyDm&Z3<^N$HowV)Uu8V={eSF<PFXW*2+
zOn<^oL~{1JT$uh1wEEc>eW!@FPOa}K6|YPtSo4J_B!7|Hl1@}lz>qm3th4Yz%!40b
zY0`4Zdg{^Im7EdoJ*D!%R7h^Qz|V?FJ;(W!qh~T4aI@sBRPlgtXEm7N)(q#XssW~#
zKiPA8lle{6KDk_a@+u|WPUu`Lz3-D^fRuM6_O{c~dw(mSKKCn_jf92Atnha@1EoS6
z4F7S6P57WWdrXxE@mXZ~EWu;%r3tiWWPCCHu<4kL8x5yr$*y^j(yAA~5?&xkk3;zD
zigL5MR7kGNQYl(^p4QM3p{{;ETx^~Mb?epcumW(&8__~68roaF+2;{VMk$IyD!vdm
z<v%Sfoz2%R30n%xBhM%0$zHCTfShD{a$>lq2_7@UB~<pK&=iJ;o`q|^wD%T!CvH_0
zmQl@N8rk<%r>IWJP23qM$UCW7j%nwc7Q#|II6&Ny>QtO<wjcJAn#)J8zA>w?L7JVQ
zwG<(xrEU+LchnA<ZD#7=ELhHjdz`R-^rB+2w)hlAY}QbYhdDH?yd>Inve0Uihbg}y
z<PrNY=xGk9y$}7O#^T=eK$2kRb-hecCW@Xn6|$4KQOvELI_cM2P6uN!*icGdH-#7h
zr#H%-^{*X8Nt1m(Y~VV6^Fg4`kyLcIlMtryego!><+7;7$}z~0*oWw_VB&8%bx({h
zyDV~;jNK4j0_l@6547YHba>u(72<YrjbCw%8!D}rw3>DnSv=`LIAxn@NJyWQA2Cw>
z=7ZENt-QOg9Ji<JNR>{_9kr8)Dx`w<7KeR@xDnC_w?2Kkc;h)T0h#3?n$2WYitJ&|
zW-=DGcvENhs~CX?sIx=0x$nOh_p-t~wEppX0lTwo`!AVxTYu9;d`1^10P2x6KMuN>
z-4?40#$T(tSfLrMlc~yq1#p@<G99{57LfcRYUPeXW1NR%^c&Y|_ENn&rav&sAa05d
zFz~Wq19oyj3H47heEm!48lBgmE%QY@WV64^_wLo_llQnCzV6=yvIq0lCYo+#I)qG-
z=gNdocl0EPR;GAt@nku?-Imr0<4vM*8?AKYYM?n~j<8&IJkuJnDtw*f2TKuctUa)|
ze*NBLVe)RHV~s~b&xfVB81{88tS@MY)v*{&?%-?-5?V9H^PLW`YHf+wf}#Y3r=Cr;
zI6Ho+*;3FUdwg*qE!{DUZxuB>SLVcVIF!9iCt?Ad;ZKl|vd5&ZGh0tX@;BJyZjM#l
zlu|FN<$I^k>EM)hF#G9P?A%YtC!y!l={*Y3>zG!A4@<A_#lp-uS8kT^NU=Aj&$g_|
z)eIZNwCPBOdG>A1Nt>OG^U;SdXbU0yXmW^bcN=$gO?WQfxz8mjR<Dt^88V|Zoe7Jg
zam#0d)YiFb6$f>Q@kxbbC`@b2a5dbmT}&BKxfp2LJX?pfCx`9nemQTm&1N&V)$FEA
zkkN+@hxPAlg-WeLPe^D|46wgFXCW@de&hWc<_Yi2rgW`iT8r^R)3+zD(pY7_48t^U
z8byJo<{H{}X2zC`##+QbZjB8qc=~HY7w<Z3`4Q*)_YLl0MvABf%4imK+x7g`tsYaP
z&<rbnsw)8bRb^4Q5%y0hH)G0L?*oAZofNx6!}1cLk_1+meJ6=sR)nD`pIhnO$}r=&
zv*Y2ajrFNo?r$$w!~^D(?Av=cTZ?sR7aIwK?UL-?A-8l0<D7|22>O7&z=*;^N6_tS
z1&G+4Boi|8+>+A~h8ojM#vJ!37~;}|em=<`y?tI$W^zhAqj!aDLYVF`645^&U!t1h
z4MK!-YJ*Q%s!@BtlYkT8Kyn^P$ol!AT0q!OGINvSjJ!$Ft3A;by4?_4ujidC{jugY
zy7F*$S17SMw3tJ_<|=OX;}@-VGO7c1JBN;jiPAl9{MA^Lc`&1{2Htu}7t45!Xl>n=
zooC}{N%J`<)%)e_iCz%#MNcvrZ24gd!u*J*PAs%6{yXLENHZz0pOX&<(a1QwUFh9+
zp$5V|&MXJgKkHb#qSvR-e`i9O_}tI6i69Kc7S|F#W6P77@oU2)Mg5Ix(&5ovtP+CM
z3G+JnI#Pp%@I=ox6mz!h;aQ0+jrhq&JowW~%)>;x*iVy&2KVkJ>1xQjsRe(y@D%(h
z!}E&|k5VerFo$vS!}Y1Nmvz29=x49cn;_qx$E=|OGnWfJD%a^W=_w42<tFMf-QNu1
z+$`-C*?86|85C!!Y1>!3zY2DCwbt@-zdfkcMK7<)aE%$eo%v88s`Angf{xw$59Zr{
zs;%q1;jXBrR~fp4dl{F7>WM91p4X{RxSHD8;>dM4bu6|YQ+fe+m|HFux>zMmx=g}X
z&DBXB*DfE8*9qb^ubFpm?3O{blC#(rxPr4a>H3Q}7pz|&KCpT%3fEe;iu7Lj^dJg(
z{yd&}tyEywE|5c}Fapjy6u0-4Rb{TV0ns4c^laPd9+(|*qpbo(@6u6$LMB*Kp1f(8
z)YzIm$e#1t753y}eenZ!(|K|6ZS>dpV!Q&md(*o$79*Q2#2w57KPbVj*U%Jt(y&WG
zs@tmNH8I+ls>74ABGl$vM$g(`+6!3JO*$$+K9%JvZA^-EoNprur#~&<9GXT}QvBei
zlXCPc)}1G7vwOTM%fhtwEB<49e2F#BNPR*>*qF#8*5#=JW(9HsGZBh3VU|!_$Jk(=
z7y7vTN72=Sr8Hj~9N1dZg(I8qxR<Tx5^m}8-Z`8+8e$ugr3F6t#kMmT)>^=t`7Blw
zA`H>*d`b70`y|dY=%_xIe%qa>=TKB=Kvl04yg0y*X~%JO651OnX_?8T2LaR+eVDsZ
zQVx_0LD%&GtC;)%`!sKl$C;I#QSUAspE5JGwWygSiB?K&(O7yNb+*Z>$1OU4TI5Te
zulN|tY9d$Lc@e4!qXcD~(?mQ;gKu{2+#%PBd;2l4Yqm!B>Lno{vXM?+td`lUaJB5b
z?62kKl5|leWk>k|XSh9Swozn%swI!vbnwfZTUDZfQAy4+Ey^o!MknkuYA3iQ^|m=}
zUelwxu;cG+*ELnGmd3xXF1qD*8m?;<Cp>qah%G&%=$pvu>j_c1OjU!g%Y5Q`7~h)x
zqK_l^?X<@>$`Xxdvlo`!FzNR;)cP7*Sn_zQ8GGA2^0t*jKDNa&KuknbT1Z4vNKDF5
zL|jf%R8B-vP((~lL?mR*1oaPqtGkV(UBKS}5jk=3e*?$`J`Z65fQFhbtV-1?5{@mf
zZ~UvkP6+{P2^?KjH)1725VnX0SOC{YZjN@gUf#k^UhZy2hC~4PR$`RNpXuXI0)TK2
zv3CE08>RLVM~P5I?<mGBnST^2BG3y-HBd!OeIC9@BV)VS95iNlQIG3u*F4i@{IsaL
zdx4q13&i=#@3p|Yk_nG)9EwOtSDzVScX($C@vD24)FtsWT+VNq`q^7jZdpMWQ^EO7
z@pA(1lWqxKKBw;P5#Ha@=t5GT2i=YX!xCXy87qQ`UlK{xd1>VC4E}zB`r&3frW*W8
zO8|}-6C&*onl(1LZf|~&nOaQeY%&~oJ?kzmpJ?R4+mTRq0)qde$IwA$#F2r0twS6D
zAi`=5)Kp+PYQnBIMFu*~(;}2j8xKg=cw!FM^R$Rui6W$M2Mq#EKaP$Ya5*N~HO?N4
z`7QZACtDiLNI$fjeLf2++{CecIgR=rOp^Yn{D34VA}`^yF$2qHh9G7ae4L*e8N)^E
zF^ia3of)Bi4LPV{VX<sgrnPXWf_s4Eb&ZkVIhCVYr8bd_t+W)hUh<!!a9t|uzGoe^
z^zXMaghRykl4}L2+{G_b1nPxG2CUbbl}u6zwBO*mB%f+{WdL`EY0rwlqj$d9g6mR`
z=gU^elDrN&1p^9-XsqE}mP!iK=KHy0%_;#KAtlzEw$v_3rn)f9yp3edJR7EtmF;2P
zll>I^;R9Imfv{%Q@x1Bhw~mLUph^S#QQmop1I>xg<gP*8Qs>rl#iz`81lbw*=~>Ex
zH=Yk_WOL#vhSn2sXfng~C+mw3aX>>7KZv<YYkYHFAB_(Fl%ZR+I94+3-q6g<U{)VE
z2~(Mb-WZ^Nk7zRL-=ULiJm;%-6-LBv95tH@?O>X}Pzj6h#jOK!_)uA*`<;1(Vqw5P
zPfkQX%2)y`N&e{oVW;&Yq_h2FcON$!?B}c1hY{?<XpHo9>5ai}>I%hUIx#vz>e_ln
z{YL7V+I_mWyKnbu=>v&NOMJy|iuoN)!GF2WP;}#y!b*R(_@hO-yLsEXdH;XDv1=N=
zg8KZr!riy{^*g)tv^8|}2S4Ity@POW0RMM$>!0+$t*$>PHt_$Iu?+vR#ohvMA|7G=
z{FT3W|IXjPa`*2C`8xytLpGHEl@EWz{to|th>iNM!}uHecWD1ZA=LjJXt);MpJpC_
OhaKYBC!C`GBmEB;!Sz!B
--- a/browser/components/preferences/in-content/tests/browser.ini
+++ b/browser/components/preferences/in-content/tests/browser.ini
@@ -1,13 +1,14 @@
 [DEFAULT]
 support-files =
   head.js
   privacypane_tests_perwindow.js
   site_data_test.html
+  addons/set_homepage.xpi
   offline/offline.html
   offline/manifest.appcache
 
 [browser_applications_selection.js]
 skip-if = os == 'linux' # bug 1382057
 [browser_advanced_update.js]
 skip-if = !updater
 [browser_basic_rebuild_fonts_test.js]
--- a/browser/components/preferences/in-content/tests/browser_homepages_filter_aboutpreferences.js
+++ b/browser/components/preferences/in-content/tests/browser_homepages_filter_aboutpreferences.js
@@ -1,9 +1,9 @@
-add_task(async function() {
+add_task(async function testSetHomepageUseCurrent() {
   is(gBrowser.currentURI.spec, "about:blank", "Test starts with about:blank open");
   await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home");
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
   let doc = gBrowser.contentDocument;
   is(gBrowser.currentURI.spec, "about:preferences#general",
      "#general should be in the URI for about:preferences");
   let oldHomepagePref = Services.prefs.getCharPref("browser.startup.homepage");
 
@@ -13,8 +13,99 @@ add_task(async function() {
   is(gBrowser.tabs.length, 3, "Three tabs should be open");
   is(Services.prefs.getCharPref("browser.startup.homepage"), "about:blank|about:home",
      "about:blank and about:home should be the only homepages set");
 
   Services.prefs.setCharPref("browser.startup.homepage", oldHomepagePref);
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
+
+const TEST_DIR = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
+const CHROME_URL_ROOT = TEST_DIR + "/";
+
+function getSupportsFile(path) {
+  let cr = Cc["@mozilla.org/chrome/chrome-registry;1"]
+    .getService(Ci.nsIChromeRegistry);
+  let uri = Services.io.newURI(CHROME_URL_ROOT + path);
+  let fileurl = cr.convertChromeURL(uri);
+  return fileurl.QueryInterface(Ci.nsIFileURL);
+}
+
+function installAddon() {
+  let filePath = getSupportsFile("addons/set_homepage.xpi").file;
+  return new Promise((resolve, reject) => {
+    AddonManager.getInstallForFile(filePath, install => {
+      if (!install) {
+        throw new Error(`An install was not created for ${filePath}`);
+      }
+      install.addListener({
+        onDownloadFailed: reject,
+        onDownloadCancelled: reject,
+        onInstallFailed: reject,
+        onInstallCancelled: reject,
+        onInstallEnded: resolve
+      });
+      install.install();
+    });
+  });
+}
+
+function waitForMessageChange(cb) {
+  return new Promise((resolve) => {
+    let target = gBrowser.contentDocument.getElementById("browserHomePageExtensionContent");
+    let observer = new MutationObserver(() => {
+      if (cb(target)) {
+        observer.disconnect();
+        resolve();
+      }
+    });
+    observer.observe(target, { attributes: true, attributeFilter: ["hidden"] });
+  });
+}
+
+function waitForMessageHidden() {
+  return waitForMessageChange(target => target.hidden);
+}
+
+function waitForMessageShown() {
+  return waitForMessageChange(target => !target.hidden);
+}
+
+add_task(async function testExtensionControlledHomepage() {
+  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
+  let doc = gBrowser.contentDocument;
+  is(gBrowser.currentURI.spec, "about:preferences#general",
+     "#general should be in the URI for about:preferences");
+  let homepagePref = () => Services.prefs.getCharPref("browser.startup.homepage");
+  let originalHomepagePref = homepagePref();
+  let extensionHomepage = "https://developer.mozilla.org/";
+  let controlledContent = doc.getElementById("browserHomePageExtensionContent");
+
+  // The homepage is set to the default and editable.
+  ok(originalHomepagePref != extensionHomepage, "homepage is empty by default");
+  is(doc.getElementById("browserHomePage").disabled, false, "The homepage input is enabled");
+  is(controlledContent.hidden, true, "The extension controlled row is hidden");
+
+  // Install an extension that will set the homepage.
+  await installAddon();
+  await waitForMessageShown();
+
+  // The homepage has been set by the extension, the user is notified and it isn't editable.
+  let controlledLabel = controlledContent.querySelector("description");
+  is(homepagePref(), extensionHomepage, "homepage is set by extension");
+  // There are two spaces before "set_homepage" because it's " <image /> set_homepage".
+  is(controlledLabel.textContent, "An extension,  set_homepage, controls your home page.",
+     "The user is notified that an extension is controlling the homepage");
+  is(controlledContent.hidden, false, "The extension controlled row is hidden");
+  is(doc.getElementById("browserHomePage").disabled, true, "The homepage input is disabled");
+
+  // Disable the extension.
+  doc.getElementById("disableHomePageExtension").click();
+
+  await waitForMessageHidden();
+
+  is(homepagePref(), originalHomepagePref, "homepage is set back to default");
+  is(doc.getElementById("browserHomePage").disabled, false, "The homepage input is enabled");
+  is(controlledContent.hidden, true, "The extension controlled row is hidden");
+
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/shell/HeadlessShell.jsm
@@ -0,0 +1,161 @@
+/* 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";
+
+let EXPORTED_SYMBOLS = ["HeadlessShell"];
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/osfile.jsm");
+
+const Ci = Components.interfaces;
+
+function loadContentWindow(webNavigation, uri) {
+  return new Promise((resolve, reject) => {
+    webNavigation.loadURI(uri, Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null);
+    let docShell = webNavigation.QueryInterface(Ci.nsIInterfaceRequestor)
+                                .getInterface(Ci.nsIDocShell);
+    let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+                              .getInterface(Ci.nsIWebProgress);
+    let progressListener = {
+      onLocationChange(progress, request, location, flags) {
+        // Ignore inner-frame events
+        if (progress != webProgress) {
+          return;
+        }
+        // Ignore events that don't change the document
+        if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
+          return;
+        }
+        let contentWindow = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+                                    .getInterface(Ci.nsIDOMWindow);
+        webProgress.removeProgressListener(progressListener);
+        contentWindow.addEventListener("load", (event) => {
+          resolve(contentWindow);
+        }, { once: true });
+      },
+      QueryInterface: XPCOMUtils.generateQI(["nsIWebProgressListener",
+                                             "nsISupportsWeakReference"])
+    };
+    webProgress.addProgressListener(progressListener,
+                                    Ci.nsIWebProgress.NOTIFY_LOCATION);
+  });
+}
+
+async function takeScreenshot(fullWidth, fullHeight, contentWidth, contentHeight, path, url) {
+  try {
+    let windowlessBrowser = Services.appShell.createWindowlessBrowser(false);
+    var webNavigation = windowlessBrowser.QueryInterface(Ci.nsIWebNavigation);
+    let contentWindow = await loadContentWindow(webNavigation, url);
+    contentWindow.resizeTo(contentWidth, contentHeight);
+
+    let canvas = contentWindow.document.createElementNS("http://www.w3.org/1999/xhtml", "html:canvas");
+    let context = canvas.getContext("2d");
+    let width = fullWidth ? contentWindow.innerWidth + contentWindow.scrollMaxX - contentWindow.scrollMinX
+                          : contentWindow.innerWidth;
+    let height = fullHeight ? contentWindow.innerHeight + contentWindow.scrollMaxY - contentWindow.scrollMinY
+                            : contentWindow.innerHeight;
+    canvas.width = width;
+    canvas.height = height;
+    context.drawWindow(contentWindow, 0, 0, width, height, "rgb(255, 255, 255)");
+
+    function getBlob() {
+      return new Promise(resolve => canvas.toBlob(resolve));
+    }
+
+    function readBlob(blob) {
+      return new Promise(resolve => {
+        let reader = new FileReader();
+        reader.onloadend = () => resolve(reader);
+        reader.readAsArrayBuffer(blob);
+      });
+    }
+
+    let blob = await getBlob();
+    let reader = await readBlob(blob);
+    await OS.File.writeAtomic(path, new Uint8Array(reader.result), {flush: true});
+    dump("Screenshot saved to: " + path + "\n");
+  } catch (e) {
+    dump("Failure taking screenshot: " + e + "\n");
+  } finally {
+    if (webNavigation) {
+      webNavigation.close();
+    }
+  }
+}
+
+let HeadlessShell = {
+  async handleCmdLineArgs(cmdLine, URLlist) {
+    try {
+      // Don't quit even though we don't create a window
+      Services.startup.enterLastWindowClosingSurvivalArea();
+
+      // Default options
+      let fullWidth = true;
+      let fullHeight = true;
+      // Most common screen resolution of Firefox users
+      let contentWidth = 1366;
+      let contentHeight = 768;
+
+      // Parse `window-size`
+      try {
+        var dimensionsStr = cmdLine.handleFlagWithParam("window-size", true);
+      } catch (e) {
+        dump("expected format: --window-size width[,height]\n");
+        return;
+      }
+      if (dimensionsStr) {
+        let success;
+        let dimensions = dimensionsStr.split(",", 2);
+        if (dimensions.length == 1) {
+          success = dimensions[0] > 0;
+          if (success) {
+            fullWidth = false;
+            fullHeight = true;
+            contentWidth = dimensions[0];
+          }
+        } else {
+          success = dimensions[0] > 0 && dimensions[1] > 0;
+          if (success) {
+            fullWidth = false;
+            fullHeight = false;
+            contentWidth = dimensions[0];
+            contentHeight = dimensions[1];
+          }
+        }
+
+        if (!success) {
+          dump("expected format: --window-size width[,height]\n");
+          return;
+        }
+      }
+
+      // Only command line argument left should be `screenshot`
+      // There could still be URLs however
+      try {
+        var path = cmdLine.handleFlagWithParam("screenshot", true);
+        if (!cmdLine.length && !URLlist.length) {
+          URLlist.push(path); // Assume the user wanted to specify a URL
+          path = OS.Path.join(cmdLine.workingDirectory.path, "screenshot.png");
+        }
+      } catch (e) {
+        path = OS.Path.join(cmdLine.workingDirectory.path, "screenshot.png");
+        cmdLine.handleFlag("screenshot", true); // Remove `screenshot`
+      }
+
+      for (let i = 0; i < cmdLine.length; ++i) {
+        URLlist.push(cmdLine.getArgument(i)); // Assume that all remaining arguments are URLs
+      }
+
+      if (URLlist.length == 1) {
+        await takeScreenshot(fullWidth, fullHeight, contentWidth, contentHeight, path, URLlist[0]);
+      } else {
+        dump("expected exactly one URL when using `screenshot`\n");
+      }
+    } finally {
+      Services.startup.exitLastWindowClosingSurvivalArea();
+    }
+  }
+};
--- a/browser/components/shell/moz.build
+++ b/browser/components/shell/moz.build
@@ -6,16 +6,17 @@
 
 # For BinaryPath::GetLong for Windows
 LOCAL_INCLUDES += [
     '/xpcom/build'
 ]
 
 XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
 
 JAR_MANIFESTS += ['jar.mn']
 
 XPIDL_SOURCES += [
     'nsIShellService.idl',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
@@ -49,16 +50,17 @@ if SOURCES:
     FINAL_LIBRARY = 'browsercomps'
 
 EXTRA_COMPONENTS += [
     'nsSetDefaultBrowser.js',
     'nsSetDefaultBrowser.manifest',
 ]
 
 EXTRA_JS_MODULES += [
+    'HeadlessShell.jsm',
     'ShellService.jsm',
 ]
 
 for var in ('MOZ_APP_NAME', 'MOZ_APP_VERSION'):
     DEFINES[var] = '"%s"' % CONFIG[var]
 
 CXXFLAGS += CONFIG['TK_CFLAGS']
 
new file mode 100644
--- /dev/null
+++ b/browser/components/shell/test/chrome.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+support-files = headless.html
+
+[test_headless_screenshot.html]
+skip-if = (os != 'win' && os != 'linux')
new file mode 100644
--- /dev/null
+++ b/browser/components/shell/test/headless.html
@@ -0,0 +1,6 @@
+<html>
+<head><meta content="text/html; charset=utf-8" http-equiv="Content-Type"></head>
+<body style="background-color: rgb(0, 255, 0); color: rgb(0, 0, 255)">
+Hi
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/components/shell/test/test_headless_screenshot.html
@@ -0,0 +1,124 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1378010
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1378010</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+  Components.utils.import("resource://gre/modules/Services.jsm");
+  Components.utils.import("resource://gre/modules/Subprocess.jsm");
+  Components.utils.import("resource://gre/modules/osfile.jsm");
+
+  const screenshotPath = OS.Path.join(OS.Constants.Path.tmpDir, "headless_test_screenshot.png");
+
+  async function runFirefox(args) {
+    const Ci = Components.interfaces;
+    const XRE_EXECUTABLE_FILE = "XREExeF";
+    const firefoxExe = Services.dirsvc.get(XRE_EXECUTABLE_FILE, Ci.nsIFile).path;
+    const NS_APP_PREFS_50_FILE = "PrefF";
+    const mochiPrefsFile = Services.dirsvc.get(NS_APP_PREFS_50_FILE, Ci.nsIFile);
+    const mochiPrefsPath = mochiPrefsFile.path;
+    const mochiPrefsName = mochiPrefsFile.leafName;
+    const profilePath = OS.Path.join(OS.Constants.Path.tmpDir, "headless_test_screenshot_profile");
+    const prefsPath = OS.Path.join(profilePath, mochiPrefsName);
+    const firefoxArgs = ["-profile", profilePath, "-no-remote"];
+
+    await OS.File.makeDir(profilePath);
+    await OS.File.copy(mochiPrefsPath, prefsPath);
+    let proc = await Subprocess.call({
+      command: firefoxExe,
+      arguments: firefoxArgs.concat(args),
+    });
+    let stdout;
+    while ((stdout = await proc.stdout.readString())) {
+      dump(">>> " + stdout + "\n");
+    }
+    let {exitCode} = await proc.wait();
+    is(exitCode, 0, "Firefox process should exit with code 0");
+    await OS.File.removeDir(profilePath);
+  }
+
+  async function testFileCreationPositive(args, path) {
+    await runFirefox(args);
+
+    let saved = await OS.File.exists(path);
+    ok(saved, "A screenshot should be saved as " + path);
+    if (!saved) {
+      return;
+    }
+
+    let info = await OS.File.stat(path);
+    ok(info.size > 0, "Screenshot should not be an empty file");
+    await OS.File.remove(path);
+  }
+
+  async function testFileCreationNegative(args, path) {
+    await runFirefox(args);
+
+    let saved = await OS.File.exists(path);
+    ok(!saved, "A screenshot should not be saved");
+    await OS.File.remove(path, { ignoreAbsent: true });
+  }
+
+  async function testWindowSizePositive(width, height) {
+    let size = width + "";
+    if (height) {
+      size += "," + height;
+    }
+
+    await runFirefox(["-url", "http://mochi.test:8888/headless.html", "-screenshot", screenshotPath, "-window-size", size]);
+
+    let saved = await OS.File.exists(screenshotPath);
+    ok(saved, "A screenshot should be saved in the tmp directory");
+    if (!saved) {
+      return;
+    }
+
+    let data = await OS.File.read(screenshotPath);
+    await new Promise((resolve, reject) => {
+      let blob = new Blob([data], { type: "image/png" });
+      let reader = new FileReader();
+      reader.onloadend = function() {
+        let screenshot = new Image();
+        screenshot.onloadend = function() {
+          is(screenshot.width, width, "Screenshot should be " + width + " pixels wide");
+          if (height) {
+            is(screenshot.height, height, "Screenshot should be " + height + " pixels tall");
+          }
+          resolve();
+        };
+        screenshot.src = reader.result;
+      };
+      reader.readAsDataURL(blob);
+    });
+    await OS.File.remove(screenshotPath);
+  }
+
+  (async function() {
+    SimpleTest.waitForExplicitFinish();
+    await testFileCreationPositive(["-url", "http://mochi.test:8888/headless.html", "-screenshot", screenshotPath], screenshotPath);
+    await testFileCreationPositive(["-screenshot", "http://mochi.test:8888/headless.html"], "screenshot.png");
+    await testFileCreationPositive(["http://mochi.test:8888/headless.html", "-screenshot"], "screenshot.png");
+    await testFileCreationNegative(["-screenshot"], "screenshot.png");
+    await testFileCreationNegative(["http://mochi.test:8888/headless.html", "http://mochi.test:8888/headless.html", "-screenshot"], "screenshot.png");
+    await testWindowSizePositive(800, 600);
+    await testWindowSizePositive(1234);
+    await testFileCreationNegative(["-url", "http://mochi.test:8888/headless.html", "-screenshot", screenshotPath, "-window-size", "hello"], screenshotPath);
+    await testFileCreationNegative(["-url", "http://mochi.test:8888/headless.html", "-screenshot", screenshotPath, "-window-size", "800,"], screenshotPath);
+    SimpleTest.finish();
+  })();
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1378010">Mozilla Bug 1378010</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -95,16 +95,23 @@ when there are no windows but Firefox is
 <!ENTITY toolbarContextMenu.bookmarkAllTabs.label "Bookmark All Tabs…">
 <!ENTITY toolbarContextMenu.bookmarkAllTabs.accesskey "T">
 <!ENTITY toolbarContextMenu.undoCloseTab.label "Undo Close Tab">
 <!ENTITY toolbarContextMenu.undoCloseTab.accesskey "U">
 
 <!ENTITY pageSourceCmd.label "Page Source">
 <!ENTITY pageSourceCmd.accesskey "o">
 <!ENTITY pageSourceCmd.commandkey "u">
+<!-- LOCALIZATION NOTE (pageSourceCmd.SafariCommandKey should match the
+Option+Command keyboard shortcut letter that Safari and Chrome use for "View
+Source" on macOS. pageSourceCmd.commandkey above is Firefox's official keyboard
+shortcut shown in the GUI. SafariCommandKey is an alias provided for the
+convenience of Safari and Chrome users on macOS. See bug 1398988. -->
+<!ENTITY pageSourceCmd.SafariCommandKey "u">
+
 <!ENTITY pageInfoCmd.label "Page Info">
 <!ENTITY pageInfoCmd.accesskey "I">
 <!ENTITY pageInfoCmd.commandkey "i">
 <!ENTITY mirrorTabCmd.label "Mirror Tab">
 <!ENTITY mirrorTabCmd.accesskey "m">
 <!-- LOCALIZATION NOTE (enterFullScreenCmd.label, exitFullScreenCmd.label):
 These should match what Safari and other Apple applications use on OS X Lion. -->
 <!ENTITY enterFullScreenCmd.label "Enter Full Screen">
--- a/browser/locales/en-US/chrome/browser/preferences/colors.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/colors.dtd
@@ -1,18 +1,18 @@
 <!-- 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/. -->
 
 <!ENTITY  colorsDialog.title              "Colors">
 <!ENTITY  window.width                    "38em">
 <!ENTITY  window.macWidth                 "41em">
 
-<!ENTITY  overrideDefaultPageColors.label        "Override the colors specified by the page with your selections above:">
-<!ENTITY  overrideDefaultPageColors.accesskey    "O">
+<!ENTITY  overrideDefaultPageColors2.label       "Override the colors specified by the page with your selections above">
+<!ENTITY  overrideDefaultPageColors2.accesskey   "O">
 
 <!ENTITY  overrideDefaultPageColors.always.label "Always">
 <!ENTITY  overrideDefaultPageColors.auto.label   "Only with High Contrast themes">
 <!ENTITY  overrideDefaultPageColors.never.label  "Never">
 
 <!ENTITY  color                           "Text and Background">
 <!ENTITY  textColor2.label                "Text">
 <!ENTITY  textColor2.accesskey            "T">
--- a/browser/locales/en-US/chrome/browser/preferences/main.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/main.dtd
@@ -15,16 +15,18 @@
 <!ENTITY useCurrentPage.label      "Use Current Page">
 <!ENTITY useCurrentPage.accesskey  "C">
 <!ENTITY useMultiple.label         "Use Current Pages">
 <!ENTITY chooseBookmark.label      "Use Bookmark…">
 <!ENTITY chooseBookmark.accesskey  "B">
 <!ENTITY restoreDefault.label      "Restore to Default">
 <!ENTITY restoreDefault.accesskey  "R">
 
+<!ENTITY disableExtension.label    "Disable Extension">
+
 <!ENTITY downloads.label     "Downloads">
 
 <!ENTITY saveTo.label "Save files to">
 <!ENTITY saveTo.accesskey "v">
 <!ENTITY chooseFolderWin.label        "Browse…">
 <!ENTITY chooseFolderWin.accesskey    "o">
 <!ENTITY chooseFolderMac.label        "Choose…">
 <!ENTITY chooseFolderMac.accesskey    "e">
--- a/browser/locales/en-US/chrome/browser/preferences/preferences.properties
+++ b/browser/locales/en-US/chrome/browser/preferences/preferences.properties
@@ -272,8 +272,12 @@ searchInput.labelUnix=Find in Preference
 searchResults.sorryMessageWin=Sorry! There are no results in Options for “%S”.
 searchResults.sorryMessageUnix=Sorry! There are no results in Preferences for “%S”.
 # LOCALIZATION NOTE (searchResults.needHelp2): %1$S is a link to SUMO, %2$S is
 # the browser name
 searchResults.needHelp2=Need help? Visit <html:a id="need-help-link" target="_blank" href="%1$S">%2$S Support</html:a>
 
 # LOCALIZATION NOTE %S is the default value of the `dom.ipc.processCount` pref.
 defaultContentProcessCount=%S (default)
+
+# LOCALIZATION NOTE (extensionControlled.homepage_override):
+# This string is shown to notify the user that their home page is being controlled by an extension.
+extensionControlled.homepage_override = An extension, %S, controls your home page.
--- a/browser/themes/shared/customizableui/customizeMode.inc.css
+++ b/browser/themes/shared/customizableui/customizeMode.inc.css
@@ -25,17 +25,17 @@
 }
 
 #customization-palette,
 #customization-empty {
   padding: 5px 20px 20px;
 }
 
 #customization-header {
-  font-weight: 600;
+  font-weight: 500;
   font-size: 1.2em;
   margin: 20px 20px 15px;
 }
 
 #customization-header:-moz-lwtheme {
   text-shadow: 0 0 1em var(--toolbar-non-lwt-bgcolor),
                0 0 1em var(--toolbar-non-lwt-bgcolor),
                0 0 .5em var(--toolbar-non-lwt-bgcolor);
@@ -414,30 +414,46 @@ toolbarpaletteitem[place=toolbar] > tool
 .customization-lwtheme-menu-footeritem:first-child {
   border-inline-end: 1px solid var(--panel-separator-color);
 }
 
 #customization-panelWrapper > .panel-arrowcontent {
   color: var(--arrowpanel-color);
   background: var(--arrowpanel-background);
   background-clip: padding-box;
-  border: 1px solid var(--arrowpanel-border-color);
-  box-shadow: 0 0 10px hsla(0,0%,0%,.2);
 %ifdef XP_MACOSX
+  /* Native styling adds more 'oompf' to the popup box-shadow, so simulate that
+   * as best as we can here: */
+  border: 1px solid var(--arrowpanel-dimmed-even-further);
+  box-shadow: 0 4px 10px hsla(0,0%,0%,.3);
   -moz-appearance: none;
   border-radius: var(--arrowpanel-border-radius);
+%else
+  border: 1px solid var(--arrowpanel-border-color);
+  box-shadow: 0 0 4px hsla(0,0%,0%,.2);
 %endif
 }
 
 #customization-panelWrapper > .panel-arrowbox {
   position: relative;
   height: 10px;
   margin-bottom: -1px;
 }
 
+/* The overflow button icon _looks_ disabled, but is also shown as [open]. */
+#nav-bar[customizing] > .overflow-button {
+  /* This color is the hard-coded #4c4c4c at 40% opacity as found in toolbarbutton-icons.inc.css */
+  fill: hsla(0,0%,30%,.4);
+}
+
+#nav-bar[customizing] > .overflow-button > .toolbarbutton-icon {
+  background-color: var(--toolbarbutton-active-background);
+  opacity: 1;
+}
+
 #customization-panelWrapper > .panel-arrowbox > .panel-arrow[side="top"] {
 %ifdef XP_MACOSX
   list-style-image: var(--panel-arrow-image-vertical,
                         url("chrome://global/skin/arrow/panelarrow-vertical.png"));
   /* The OS X image is 2px narrower than the windows/linux one.
    * Add padding to compensate: */
   padding: 0 1px;
   /* specify width for hidpi image to fit correctly */
@@ -449,24 +465,24 @@ toolbarpaletteitem[place=toolbar] > tool
   /* The arrow needs to point to the overflow button. The numbers involved
    * are:
    * overflow button width: (16px + 2 * 2px padding
    * + 2 * toolbarbutton-inner-padding)
    * hamburger button width: (16px + 2 * 5px padding
    * + 2 * toolbarbutton-inner-padding)
    * hamburger button border + margin: 1px + 2px
    * The total desired offset from the right edge of the window is thus:
-   * 10px + toolbarbutton-inner-padding (center of overflow button) +
+   * 10px + 1 * toolbarbutton-inner-padding (center of overflow button) +
    * 29px + 2 * toolbarbutton-inner-padding
-   * The #customization-panel-container has a 25px margin, so that leaves:
-   * 14px + 3 * toolbarbutton-inner-padding
+   * The #customization-panel-container has a 20px margin, so that leaves:
+   * 19px + 3 * toolbarbutton-inner-padding
    * Finally, we need to center the arrow, which is 20px wide, so subtract
    * 10px.
    */
-  margin-inline-end: calc(4px + 3 * var(--toolbarbutton-inner-padding));
+  margin-inline-end: calc(9px + 3 * var(--toolbarbutton-inner-padding));
   vertical-align: top;
 }
 
 %ifdef XP_MACOSX
 @media (min-resolution: 2dppx) {
   #customization-panelWrapper > .panel-arrowbox > .panel-arrow[side="top"] {
     list-style-image: var(--panel-arrow-image-vertical,
                           url("chrome://global/skin/arrow/panelarrow-vertical@2x.png"));
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -154,16 +154,23 @@ separator.thin:not([orient="vertical"]) 
 .homepage-button:first-of-type {
   margin-inline-start: 0;
 }
 
 .homepage-button:last-of-type {
   margin-inline-end: 0;
 }
 
+.extension-controlled-icon {
+  height: 20px;
+  margin-bottom: 6px;
+  vertical-align: bottom;
+  width: 20px;
+}
+
 #getStarted {
   font-size: 90%;
 }
 
 #downloadFolder {
   margin-inline-start: 0;
 }
 
--- a/devtools/client/commandline/test/browser_gcli_async.js
+++ b/devtools/client/commandline/test/browser_gcli_async.js
@@ -47,17 +47,17 @@ exports.testBasic = function (options) {
       check: {
         input:  "tsslow ",
         hints:         "Shalom",
         markup: "VVVVVVV",
         cursor: 7,
         current: "hello",
         status: "ERROR",
         predictions: [
-          "Shalom", "Namasté", "Hallo", "Dydd-da", "Chào", "Hej",
+          "Shalom", "Namast\u{00E9}", "Hallo", "Dydd-da", "Ch\u{00E0}o", "Hej",
           "Saluton", "Sawubona"
         ],
         unassigned: [ ],
         args: {
           command: { name: "tsslow" },
           hello: {
             arg: "",
             status: "INCOMPLETE"
@@ -69,17 +69,17 @@ exports.testBasic = function (options) {
       setup:    "tsslow S",
       check: {
         input:  "tsslow S",
         hints:          "halom",
         markup: "VVVVVVVI",
         cursor: 8,
         current: "hello",
         status: "ERROR",
-        predictions: [ "Shalom", "Saluton", "Sawubona", "Namasté" ],
+        predictions: [ "Shalom", "Saluton", "Sawubona", "Namast\u{00E9}" ],
         unassigned: [ ],
         args: {
           command: { name: "tsslow" },
           hello: {
             arg: " S",
             status: "INCOMPLETE"
           },
         }
--- a/devtools/client/commandline/test/head.js
+++ b/devtools/client/commandline/test/head.js
@@ -12,17 +12,17 @@ const TEST_BASE_HTTPS = "https://example
 
 var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 var { console } = require("resource://gre/modules/Console.jsm");
 var flags = require("devtools/shared/flags");
 
 // Import the GCLI test helper
 var testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
 Services.scriptloader.loadSubScript(testDir + "/helpers.js", this);
-Services.scriptloader.loadSubScript(testDir + "/mockCommands.js", this);
+Services.scriptloader.loadSubScript(testDir + "/mockCommands.js", this, "UTF-8");
 
 flags.testing = true;
 SimpleTest.registerCleanupFunction(() => {
   flags.testing = false;
 });
 
 function whenDelayedStartupFinished(aWindow, aCallback) {
   Services.obs.addObserver(function observer(aSubject, aTopic) {
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -1542,16 +1542,21 @@ Toolbox.prototype = {
 
   /**
    * Unregister and unload an additional tool from this particular toolbox.
    *
    * @param {string} toolId
    *        the id of the additional tool to unregister and remove.
    */
   removeAdditionalTool(toolId) {
+    // Early exit if the toolbox is already destroying itself.
+    if (this._destroyer) {
+      return;
+    }
+
     if (!this.hasAdditionalTool(toolId)) {
       throw new Error("Tool definition not registered to this toolbox: " +
                       toolId);
     }
 
     this.additionalToolDefinitions.delete(toolId);
     this.visibleAdditionalTools = this.visibleAdditionalTools
                                       .filter(id => id !== toolId);
--- a/devtools/client/inspector/grids/grid-inspector.js
+++ b/devtools/client/inspector/grids/grid-inspector.js
@@ -112,17 +112,21 @@ GridInspector.prototype = {
   destroy() {
     this.highlighters.off("grid-highlighter-hidden", this.onHighlighterChange);
     this.highlighters.off("grid-highlighter-shown", this.onHighlighterChange);
     this.inspector.sidebar.off("select", this.onSidebarSelect);
     this.inspector.off("new-root", this.onNavigate);
 
     this.inspector.reflowTracker.untrackReflows(this, this.onReflow);
 
-    this.swatchColorPickerTooltip.destroy();
+    // The color picker may not be ready as `init` function is async,
+    // and we do not wait for its completion before calling destroy in tests
+    if (this.swatchColorPickerTooltip) {
+      this.swatchColorPickerTooltip.destroy();
+    }
 
     this.document = null;
     this.highlighters = null;
     this.inspector = null;
     this.layoutInspector = null;
     this.store = null;
     this.swatchColorPickerTooltip = null;
     this.walker = null;
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -18,17 +18,16 @@ const {Task} = require("devtools/shared/
 const Telemetry = require("devtools/client/shared/telemetry");
 const HighlightersOverlay = require("devtools/client/inspector/shared/highlighters-overlay");
 const ReflowTracker = require("devtools/client/inspector/shared/reflow-tracker");
 const Store = require("devtools/client/inspector/store");
 
 loader.lazyRequireGetter(this, "initCssProperties", "devtools/shared/fronts/css-properties", true);
 loader.lazyRequireGetter(this, "HTMLBreadcrumbs", "devtools/client/inspector/breadcrumbs", true);
 loader.lazyRequireGetter(this, "KeyShortcuts", "devtools/client/shared/key-shortcuts");
-loader.lazyRequireGetter(this, "GridInspector", "devtools/client/inspector/grids/grid-inspector");
 loader.lazyRequireGetter(this, "InspectorSearch", "devtools/client/inspector/inspector-search", true);
 loader.lazyRequireGetter(this, "ToolSidebar", "devtools/client/inspector/toolsidebar", true);
 loader.lazyRequireGetter(this, "MarkupView", "devtools/client/inspector/markup/markup");
 
 loader.lazyRequireGetter(this, "nodeConstants", "devtools/shared/dom-node-constants");
 loader.lazyRequireGetter(this, "Menu", "devtools/client/framework/menu");
 loader.lazyRequireGetter(this, "MenuItem", "devtools/client/framework/menu-item");
 loader.lazyRequireGetter(this, "ExtensionSidebar", "devtools/client/inspector/extensions/extension-sidebar");
@@ -622,22 +621,38 @@ Inspector.prototype = {
       INSPECTOR_L10N.getStr("inspector.sidebar.ruleViewTitle"),
       defaultTab == "ruleview");
 
     this.sidebar.addExistingTab(
       "computedview",
       INSPECTOR_L10N.getStr("inspector.sidebar.computedViewTitle"),
       defaultTab == "computedview");
 
-    // Grid and layout panels aren't lazy-loaded as their module end up
-    // calling inspector.addSidebarTab
-    this.gridInspector = new GridInspector(this, this.panelWin);
-
-    const LayoutView = this.browserRequire("devtools/client/inspector/layout/layout");
-    this.layoutview = new LayoutView(this, this.panelWin);
+    // Inject a lazy loaded react tab by exposing a fake React object
+    // with a lazy defined Tab thanks to `panel` being a function
+    let layoutId = "layoutview";
+    let layoutTitle = INSPECTOR_L10N.getStr("inspector.sidebar.layoutViewTitle2");
+    this.sidebar.addTab(
+      layoutId,
+      layoutTitle,
+      {
+        props: {
+          id: layoutId,
+          title: layoutTitle
+        },
+        panel: () => {
+          if (!this.layoutview) {
+            const LayoutView =
+              this.browserRequire("devtools/client/inspector/layout/layout");
+            this.layoutview = new LayoutView(this, this.panelWin);
+          }
+          return this.layoutview.provider;
+        }
+      },
+      defaultTab == layoutId);
 
     if (this.target.form.animationsActor) {
       this.sidebar.addFrameTab(
         "animationinspector",
         INSPECTOR_L10N.getStr("inspector.sidebar.animationInspectorTitle"),
         "chrome://devtools/content/animationinspector/animation-inspector.xhtml",
         defaultTab == "animationinspector");
     }
@@ -1048,20 +1063,16 @@ Inspector.prototype = {
     this.target.off("thread-resumed", this.updateDebuggerPausedWarning);
     this._toolbox.off("select", this.updateDebuggerPausedWarning);
 
     for (let [, panel] of this._panels) {
       panel.destroy();
     }
     this._panels.clear();
 
-    if (this.gridInspector) {
-      this.gridInspector.destroy();
-    }
-
     if (this.layoutview) {
       this.layoutview.destroy();
     }
 
     if (this.fontinspector) {
       this.fontinspector.destroy();
     }
 
--- a/devtools/client/inspector/layout/layout.js
+++ b/devtools/client/inspector/layout/layout.js
@@ -16,16 +16,18 @@ const INSPECTOR_L10N =
   new LocalizationHelper("devtools/client/locales/inspector.properties");
 
 // @remove after release 56 (See Bug 1355747)
 const PROMOTE_COUNT_PREF = "devtools.promote.layoutview";
 
 // @remove after release 56 (See Bug 1355747)
 const GRID_LINK = "https://www.mozilla.org/en-US/developer/css-grid/?utm_source=gridtooltip&utm_medium=devtools&utm_campaign=cssgrid_layout";
 
+loader.lazyRequireGetter(this, "GridInspector", "devtools/client/inspector/grids/grid-inspector");
+
 function LayoutView(inspector, window) {
   this.document = window.document;
   this.inspector = inspector;
   this.store = inspector.store;
 
   this.onPromoteLearnMoreClick = this.onPromoteLearnMoreClick.bind(this);
 
   this.init();
@@ -45,27 +47,28 @@ LayoutView.prototype = {
 
     let {
       onHideBoxModelHighlighter,
       onShowBoxModelEditor,
       onShowBoxModelHighlighter,
       onToggleGeometryEditor,
     } = this.inspector.getPanel("boxmodel").getComponentProps();
 
+    this.gridInspector = new GridInspector(this.inspector, this.inspector.panelWin);
     let {
       getSwatchColorPickerTooltip,
       onSetGridOverlayColor,
       onShowGridAreaHighlight,
       onShowGridCellHighlight,
       onShowGridLineNamesHighlight,
       onToggleGridHighlighter,
       onToggleShowGridAreas,
       onToggleShowGridLineNumbers,
       onToggleShowInfiniteLines,
-    } = this.inspector.gridInspector.getComponentProps();
+    } = this.gridInspector.getComponentProps();
 
     let {
       onPromoteLearnMoreClick,
     } = this;
 
     let app = App({
       getSwatchColorPickerTooltip,
       setSelectedNode,
@@ -96,30 +99,26 @@ LayoutView.prototype = {
       store: this.store,
       title: INSPECTOR_L10N.getStr("inspector.sidebar.layoutViewTitle2"),
       // @remove after release 56 (See Bug 1355747)
       badge: Services.prefs.getIntPref(PROMOTE_COUNT_PREF) > 0 ?
         INSPECTOR_L10N.getStr("inspector.sidebar.newBadge") : null,
       showBadge: () => Services.prefs.getIntPref(PROMOTE_COUNT_PREF) > 0,
     }, app);
 
-    let defaultTab = Services.prefs.getCharPref("devtools.inspector.activeSidebar");
-
-    this.inspector.addSidebarTab(
-      "layoutview",
-      INSPECTOR_L10N.getStr("inspector.sidebar.layoutViewTitle2"),
-      provider,
-      defaultTab == "layoutview"
-    );
+    // Expose the provider to let inspector.js use it in setupSidebar.
+    this.provider = provider;
   },
 
   /**
    * Destruction function called when the inspector is destroyed. Cleans up references.
    */
   destroy() {
+    this.gridInspector.destroy();
+
     this.document = null;
     this.inspector = null;
     this.store = null;
   },
 
   onPromoteLearnMoreClick() {
     let browserWin = this.inspector.target.tab.ownerDocument.defaultView;
     browserWin.openUILinkIn(GRID_LINK, "current");
--- a/devtools/client/inspector/test/shared-head.js
+++ b/devtools/client/inspector/test/shared-head.js
@@ -48,24 +48,26 @@ var openInspector = Task.async(function*
  * @return a promise that resolves when the inspector is ready and the tab is
  * visible and ready
  */
 var openInspectorSidebarTab = Task.async(function* (id) {
   let {toolbox, inspector, testActor} = yield openInspector();
 
   info("Selecting the " + id + " sidebar");
 
+  let onSidebarSelect = inspector.sidebar.once("select");
   if (id === "computedview" || id === "layoutview") {
     // The layout and computed views should wait until the box-model widget is ready.
     let onBoxModelViewReady = inspector.once("boxmodel-view-updated");
     inspector.sidebar.select(id);
     yield onBoxModelViewReady;
   } else {
     inspector.sidebar.select(id);
   }
+  yield onSidebarSelect;
 
   return {
     toolbox,
     inspector,
     testActor
   };
 });
 
@@ -129,17 +131,17 @@ function openLayoutView() {
       };
     }
     mockHighlighter(data.toolbox);
 
     return {
       toolbox: data.toolbox,
       inspector: data.inspector,
       boxmodel: data.inspector.getPanel("boxmodel"),
-      gridInspector: data.inspector.gridInspector,
+      gridInspector: data.inspector.layoutview.gridInspector,
       testActor: data.testActor
     };
   });
 }
 
 /**
  * Select the rule view sidebar tab on an already opened inspector panel.
  *
--- a/devtools/client/shared/components/tabs/tabs.js
+++ b/devtools/client/shared/components/tabs/tabs.js
@@ -329,26 +329,33 @@ define(function (require, exports, modul
           // content of non-selected tab. It's faster (not sure why)
           // than display:none and visibility:collapse.
           let style = {
             visibility: selected ? "visible" : "hidden",
             height: selected ? "100%" : "0",
             width: selected ? "100%" : "0",
           };
 
+          // Allows lazy loading panels by creating them only if they are selected,
+          // then store a copy of the lazy created panel in `tab.panel`.
+          if (typeof tab.panel == "function" && selected) {
+            tab.panel = tab.panel(tab);
+          }
+          let panel = tab.panel || tab;
+
           return (
             DOM.div({
               id: id ? id + "-panel" : "panel-" + index,
               key: index,
               style: style,
               className: selected ? "tab-panel-box" : "tab-panel-box hidden",
               role: "tabpanel",
               "aria-labelledby": id ? id + "-tab" : "tab-" + index,
             },
-              (selected || this.state.created[index]) ? tab : null
+              (selected || this.state.created[index]) ? panel : null
             )
           );
         });
 
       return (
         DOM.div({className: "panels"},
           panels
         )
--- a/dom/animation/EffectCompositor.cpp
+++ b/dom/animation/EffectCompositor.cpp
@@ -977,16 +977,26 @@ EffectCompositor::PreTraverseInSubtree(S
                                        Element* aRoot)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mPresContext->RestyleManager()->IsServo());
   MOZ_ASSERT(!aRoot || nsComputedDOMStyle::GetPresShellForContent(aRoot),
              "Traversal root, if provided, should be bound to a display "
              "document");
 
+  // Convert the root element to the parent element if the root element is
+  // pseudo since we check each element in mElementsToRestyle is in the subtree
+  // of the root element later in this function, but for pseudo elements the
+  // element in mElementsToRestyle is the parent of the pseudo.
+  if (aRoot &&
+      (aRoot->IsGeneratedContentContainerForBefore() ||
+       aRoot->IsGeneratedContentContainerForAfter())) {
+    aRoot = aRoot->GetParentElement();
+  }
+
   AutoRestore<bool> guard(mIsInPreTraverse);
   mIsInPreTraverse = true;
 
   // We need to force flush all throttled animations if we also have
   // non-animation restyles (since we'll want the up-to-date animation style
   // when we go to process them so we can trigger transitions correctly), and
   // if we are currently flushing all throttled animation restyles.
   bool flushThrottledRestyles =
--- a/dom/base/nsDeprecatedOperationList.h
+++ b/dom/base/nsDeprecatedOperationList.h
@@ -29,17 +29,16 @@ DEPRECATED_OPERATION(LenientThis)
 DEPRECATED_OPERATION(GetPreventDefault)
 DEPRECATED_OPERATION(GetSetUserData)
 DEPRECATED_OPERATION(MozGetAsFile)
 DEPRECATED_OPERATION(UseOfCaptureEvents)
 DEPRECATED_OPERATION(UseOfReleaseEvents)
 DEPRECATED_OPERATION(UseOfDOM3LoadMethod)
 DEPRECATED_OPERATION(ChromeUseOfDOM3LoadMethod)
 DEPRECATED_OPERATION(ShowModalDialog)
-DEPRECATED_OPERATION(Window_Content)
 DEPRECATED_OPERATION(SyncXMLHttpRequest)
 DEPRECATED_OPERATION(Window_Cc_ontrollers)
 DEPRECATED_OPERATION(ImportXULIntoContent)
 DEPRECATED_OPERATION(PannerNodeDoppler)
 DEPRECATED_OPERATION(NavigatorGetUserMedia)
 DEPRECATED_OPERATION(WebrtcDeprecatedPrefix)
 DEPRECATED_OPERATION(RTCPeerConnectionGetStreams)
 DEPRECATED_OPERATION(AppCache)
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -1229,27 +1229,16 @@ public:
   {
     MOZ_ASSERT(IsOuterWindow());
     mozilla::IgnoredErrorResult ignored;
     nsCOMPtr<nsPIDOMWindowOuter> win =
       GetContentInternal(ignored, mozilla::dom::CallerType::System);
     return win.forget();
   }
 
-  void Get_content(JSContext* aCx,
-                   JS::MutableHandle<JSObject*> aRetval,
-                   mozilla::dom::SystemCallerGuarantee aCallerType,
-                   mozilla::ErrorResult& aError)
-  {
-    if (mDoc) {
-      mDoc->WarnOnceAbout(nsIDocument::eWindow_Content);
-    }
-    GetContent(aCx, aRetval, aCallerType, aError);
-  }
-
   already_AddRefed<mozilla::dom::Promise>
   CreateImageBitmap(JSContext* aCx,
                     const mozilla::dom::ImageBitmapSource& aImage,
                     mozilla::ErrorResult& aRv);
 
   already_AddRefed<mozilla::dom::Promise>
   CreateImageBitmap(JSContext* aCx,
                     const mozilla::dom::ImageBitmapSource& aImage,
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -2453,16 +2453,17 @@ WebGLContext::ValidateArrayBufferView(co
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // XPCOM goop
 
 void
 WebGLContext::UpdateMaxDrawBuffers()
 {
+    gl->MakeCurrent();
     mGLMaxColorAttachments = gl->GetIntAs<uint32_t>(LOCAL_GL_MAX_COLOR_ATTACHMENTS);
     mGLMaxDrawBuffers = gl->GetIntAs<uint32_t>(LOCAL_GL_MAX_DRAW_BUFFERS);
 
     // WEBGL_draw_buffers:
     // "The value of the MAX_COLOR_ATTACHMENTS_WEBGL parameter must be greater than or
     //  equal to that of the MAX_DRAW_BUFFERS_WEBGL parameter."
     mGLMaxDrawBuffers = std::min(mGLMaxDrawBuffers, mGLMaxColorAttachments);
 }
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -176,18 +176,16 @@ MozGetAsFileWarning=The non-standard moz
 # LOCALIZATION NOTE: Do not translate "captureEvents()" or "addEventListener()"
 UseOfCaptureEventsWarning=Use of captureEvents() is deprecated. To upgrade your code, use the DOM 2 addEventListener() method. For more help http://developer.mozilla.org/en/docs/DOM:element.addEventListener
 # LOCALIZATION NOTE: Do not translate "releaseEvents()" or "removeEventListener()"
 UseOfReleaseEventsWarning=Use of releaseEvents() is deprecated. To upgrade your code, use the DOM 2 removeEventListener() method. For more help http://developer.mozilla.org/en/docs/DOM:element.removeEventListener
 # LOCALIZATION NOTE: Do not translate "document.load()" or "XMLHttpRequest"
 UseOfDOM3LoadMethodWarning=Use of document.load() is deprecated. To upgrade your code, use the DOM XMLHttpRequest object. For more help https://developer.mozilla.org/en/XMLHttpRequest
 # LOCALIZATION NOTE: Do not translate "window.showModalDialog()" or "window.open()"
 ShowModalDialogWarning=Use of window.showModalDialog() is deprecated. Use window.open() instead. For more help https://developer.mozilla.org/en-US/docs/Web/API/Window.open
-# LOCALIZATION NOTE: Do not translate "window._content" or "window.content"
-Window_ContentWarning=window._content is deprecated.  Please use window.content instead.
 # LOCALIZATION NOTE: Do not translate "XMLHttpRequest"
 SyncXMLHttpRequestWarning=Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user’s experience. For more help http://xhr.spec.whatwg.org/
 ImplicitMetaViewportTagFallback=No meta-viewport tag found. Please explicitly specify one to prevent unexpected behavioural changes in future versions. For more help https://developer.mozilla.org/en/docs/Mozilla/Mobile/Viewport_meta_tag
 # LOCALIZATION NOTE: Do not translate "window.controllers/Controllers"
 Window_Cc_ontrollersWarning=window.controllers/Controllers is deprecated. Do not use it for UA detection.
 ImportXULIntoContentWarning=Importing XUL nodes into a content document is deprecated. This functionality may be removed soon.
 XMLDocumentLoadPrincipalMismatch=Use of document.load forbidden on Documents that come from other Windows. Only the Window in which a Document was created is allowed to call .load on that Document. Preferably, use XMLHttpRequest instead.
 # LOCALIZATION NOTE: Do not translate "IndexedDB".
--- a/dom/tests/browser/browser.ini
+++ b/dom/tests/browser/browser.ini
@@ -50,17 +50,16 @@ support-files =
 run-if = e10s
 [browser_largeAllocation_win32.js]
 skip-if = !e10s || os != "win" || processor != "x86" # Large-Allocation requires e10s
 [browser_largeAllocation_non_win32.js]
 skip-if = !e10s || (os == "win" && processor == "x86") # Large-Allocation requires e10s
 [browser_localStorage_e10s.js]
 skip-if = !e10s # This is a test of e10s functionality.
 [browser_localStorage_privatestorageevent.js]
-[browser_test__content.js]
 [browser_test_focus_after_modal_state.js]
 support-files =
   focus_after_prompt.html
 [browser_test_new_window_from_content.js]
 tags = openwindow
 skip-if = toolkit == 'android'  || (os == "linux" && debug) # see bug 1261495 for Linux debug time outs
 support-files =
   test_new_window_from_content_child.html
deleted file mode 100644
--- a/dom/tests/browser/browser_test__content.js
+++ /dev/null
@@ -1,4 +0,0 @@
-function test() {
-    is(window._content, window.content,
-       "_content needs to work, since extensions use it");
-}
--- a/dom/vr/test/reftest/reftest.list
+++ b/dom/vr/test/reftest/reftest.list
@@ -1,8 +1,10 @@
 # WebVR Reftests
 default-preferences pref(dom.vr.puppet.enabled,true) pref(dom.vr.test.enabled,true) pref(dom.vr.require-gesture,false) pref(dom.vr.puppet.submitframe,1)
 
-# VR SubmitFrame is only implemented for D3D11 now.
+# VR SubmitFrame is only implemented for D3D11 and MacOSX now.
 # We need to continue to investigate why these reftests can be run well in local,
-# but will be suspended until terminating on reftest debug build.
-skip-if(!winWidget||!layersGPUAccelerated||isDebugBuild) == draw_rect.html wrapper.html?draw_rect.png
-skip-if(!winWidget||!layersGPUAccelerated||isDebugBuild) == change_size.html wrapper.html?change_size.png
+# but will be suspended until terminating on reftest D3D11 debug build.
+skip-if(Android||gtkWidget||(winWidget&&isDebugBuild)||!layersGPUAccelerated) == draw_rect.html wrapper.html?draw_rect.png
+# On MacOSX platform, getting different color interpolation result.
+# For lower resolution Mac hardware, we need to adjust it to fuzzy-if(cocoaWidget,1,1200).
+fuzzy-if(cocoaWidget,1,600) skip-if(Android||gtkWidget||(winWidget&&isDebugBuild)||!layersGPUAccelerated) == change_size.html wrapper.html?change_size.png
--- a/dom/webidl/Window.webidl
+++ b/dom/webidl/Window.webidl
@@ -333,18 +333,16 @@ partial interface Window {
    */
   [Throws, ChromeOnly, UnsafeInPrerendering] WindowProxy? openDialog(optional DOMString url = "",
                                                                    optional DOMString name = "",
                                                                    optional DOMString options = "",
                                                                    any... extraArguments);
 
   [Replaceable, Throws, NeedsCallerType] readonly attribute object? content;
 
-  [ChromeOnly, Throws, NeedsCallerType] readonly attribute object? __content;
-
   [Throws, ChromeOnly] any getInterface(IID iid);
 
   /**
    * Same as nsIDOMWindow.windowRoot, useful for event listener targeting.
    */
   [ChromeOnly, Throws]
   readonly attribute WindowRoot? windowRoot;
 };
--- a/gfx/layers/wr/WebRenderLayerManager.cpp
+++ b/gfx/layers/wr/WebRenderLayerManager.cpp
@@ -224,26 +224,32 @@ WebRenderLayerManager::CreateWebRenderCo
       if (eventRegions->IsEmpty()) {
         item->Destroy(aDisplayListBuilder);
         continue;
       }
     }
 
     // Peek ahead to the next item and try merging with it or swapping with it
     // if necessary.
-    nsDisplayItem* aboveItem;
-    while ((aboveItem = aDisplayList->GetBottom()) != nullptr) {
-      if (aboveItem->TryMerge(item)) {
-        aDisplayList->RemoveBottom();
-        item->Destroy(aDisplayListBuilder);
-        item = aboveItem;
-        itemType = item->GetType();
-      } else {
+    AutoTArray<nsDisplayItem*, 1> mergedItems;
+    mergedItems.AppendElement(item);
+    for (nsDisplayItem* peek = item->GetAbove(); peek; peek = peek->GetAbove()) {
+      if (!item->CanMerge(peek)) {
         break;
       }
+
+      mergedItems.AppendElement(peek);
+
+      // Move the iterator forward since we will merge this item.
+      item = peek;
+    }
+
+    if (mergedItems.Length() > 1) {
+      item = aDisplayListBuilder->MergeItems(mergedItems);
+      MOZ_ASSERT(item && itemType == item->GetType());
     }
 
     nsDisplayList* itemSameCoordinateSystemChildren
       = item->GetSameCoordinateSystemChildren();
     if (item->ShouldFlattenAway(aDisplayListBuilder)) {
       aDisplayList->AppendToBottom(itemSameCoordinateSystemChildren);
       item->Destroy(aDisplayListBuilder);
       continue;
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -2001,16 +2001,17 @@ gfxFont::DrawGlyphs(const gfxShapedText 
 void
 gfxFont::DrawEmphasisMarks(const gfxTextRun* aShapedText, gfxPoint* aPt,
                            uint32_t aOffset, uint32_t aCount,
                            const EmphasisMarkDrawParams& aParams)
 {
     gfxFloat& inlineCoord = aParams.isVertical ? aPt->y : aPt->x;
     gfxTextRun::Range markRange(aParams.mark);
     gfxTextRun::DrawParams params(aParams.context);
+    params.textDrawer = aParams.textDrawer;
 
     gfxFloat clusterStart = -std::numeric_limits<gfxFloat>::infinity();
     bool shouldDrawEmphasisMark = false;
     for (uint32_t i = 0, idx = aOffset; i < aCount; ++i, ++idx) {
         if (aParams.spacing) {
             inlineCoord += aParams.direction * aParams.spacing[i].mBefore;
         }
         if (aShapedText->IsClusterStart(idx) ||
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -70,16 +70,19 @@ class gfxMathTable;
 
 struct gfxTextRunDrawCallbacks;
 
 namespace mozilla {
 class SVGContextPaint;
 namespace gfx {
 class GlyphRenderingOptions;
 } // namespace gfx
+namespace layout {
+class TextDrawTarget;
+} // namespace layout
 } // namespace mozilla
 
 struct gfxFontStyle {
     gfxFontStyle();
     gfxFontStyle(uint8_t aStyle, uint16_t aWeight, int16_t aStretch,
                  gfxFloat aSize, nsIAtom *aLanguage, bool aExplicitLanguage,
                  float aSizeAdjust, bool aSystemFont,
                  bool aPrinterFont,
@@ -2305,16 +2308,17 @@ struct MOZ_STACK_CLASS FontDrawParams {
     mozilla::gfx::DrawOptions drawOptions;
     bool                      isVerticalFont;
     bool                      haveSVGGlyphs;
     bool                      haveColorGlyphs;
 };
 
 struct MOZ_STACK_CLASS EmphasisMarkDrawParams {
     gfxContext* context;
+    mozilla::layout::TextDrawTarget* textDrawer = nullptr;
     gfxFont::Spacing* spacing;
     gfxTextRun* mark;
     gfxFloat advance;
     gfxFloat direction;
     bool isVertical;
 };
 
 #endif
--- a/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -602,17 +602,17 @@ gfxTextRun::Draw(Range aRange, gfxPoint 
                  "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or GLYPH_STROKE_UNDERNEATH");
     NS_ASSERTION(aParams.drawMode == DrawMode::GLYPH_PATH || !aParams.callbacks,
                  "callback must not be specified unless using GLYPH_PATH");
 
     bool skipDrawing = mSkipDrawing;
     if (aParams.drawMode & DrawMode::GLYPH_FILL) {
         Color currentColor;
         if (aParams.context->GetDeviceColor(currentColor) &&
-            currentColor.a == 0) {
+            currentColor.a == 0 && !aParams.textDrawer) {
             skipDrawing = true;
         }
     }
 
     gfxFloat direction = GetDirection();
 
     if (skipDrawing) {
         // We don't need to draw anything;
@@ -719,24 +719,27 @@ gfxTextRun::Draw(Range aRange, gfxPoint 
 
     if (aParams.advanceWidth) {
         *aParams.advanceWidth = advance;
     }
 }
 
 // This method is mostly parallel to Draw().
 void
-gfxTextRun::DrawEmphasisMarks(gfxContext *aContext, gfxTextRun* aMark,
+gfxTextRun::DrawEmphasisMarks(gfxContext *aContext,
+                              mozilla::layout::TextDrawTarget* aTextDrawer,
+                              gfxTextRun* aMark,
                               gfxFloat aMarkAdvance, gfxPoint aPt,
                               Range aRange, PropertyProvider* aProvider) const
 {
     MOZ_ASSERT(aRange.end <= GetLength());
 
     EmphasisMarkDrawParams params;
     params.context = aContext;
+    params.textDrawer = aTextDrawer;
     params.mark = aMark;
     params.advance = aMarkAdvance;
     params.direction = GetDirection();
     params.isVertical = IsVertical();
 
     gfxFloat& inlineCoord = params.isVertical ? aPt.y : aPt.x;
     gfxFloat direction = params.direction;
 
--- a/gfx/thebes/gfxTextRun.h
+++ b/gfx/thebes/gfxTextRun.h
@@ -37,16 +37,19 @@ class gfxUserFontEntry;
 class gfxUserFontSet;
 class nsIAtom;
 class nsLanguageAtomService;
 class gfxMissingFontRecorder;
 
 namespace mozilla {
 class SVGContextPaint;
 enum class StyleHyphens : uint8_t;
+namespace layout {
+class TextDrawTarget;
+};
 };
 
 /**
  * Callback for Draw() to use when drawing text with mode
  * DrawMode::GLYPH_PATH.
  */
 struct gfxTextRunDrawCallbacks {
 
@@ -239,16 +242,17 @@ public:
         // Return the appUnitsPerDevUnit value to be used when measuring.
         // Only called if the hyphen width is requested.
         virtual uint32_t GetAppUnitsPerDevUnit() const = 0;
     };
 
     struct MOZ_STACK_CLASS DrawParams
     {
         gfxContext* context;
+        mozilla::layout::TextDrawTarget* textDrawer = nullptr;
         DrawMode drawMode = DrawMode::GLYPH_FILL;
         nscolor textStrokeColor = 0;
         gfxPattern* textStrokePattern = nullptr;
         const mozilla::gfx::StrokeOptions *strokeOpts = nullptr;
         const mozilla::gfx::DrawOptions *drawOpts = nullptr;
         PropertyProvider* provider = nullptr;
         // If non-null, the advance width of the substring is set.
         gfxFloat* advanceWidth = nullptr;
@@ -279,17 +283,19 @@ public:
      */
     void Draw(Range aRange, gfxPoint aPt, const DrawParams& aParams) const;
 
     /**
      * Draws the emphasis marks for this text run. Uses only GetSpacing
      * from aProvider. The provided point is the baseline origin of the
      * line of emphasis marks.
      */
-    void DrawEmphasisMarks(gfxContext* aContext, gfxTextRun* aMark,
+    void DrawEmphasisMarks(gfxContext* aContext,
+                           mozilla::layout::TextDrawTarget* aTextDrawer,
+                           gfxTextRun* aMark,
                            gfxFloat aMarkAdvance, gfxPoint aPt,
                            Range aRange, PropertyProvider* aProvider) const;
 
     /**
      * Computes the ReflowMetrics for a substring.
      * Uses GetSpacing from aBreakProvider.
      * @param aBoundingBoxType which kind of bounding box (loose/tight)
      */
--- a/gfx/vr/gfxVRPuppet.cpp
+++ b/gfx/vr/gfxVRPuppet.cpp
@@ -2,22 +2,26 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #if defined(XP_WIN)
 #include "CompositorD3D11.h"
 #include "TextureD3D11.h"
 #include "mozilla/gfx/DeviceManagerDx.h"
-#endif // XP_WIN
+#elif defined(XP_MACOSX)
+#include "mozilla/gfx/MacIOSurface.h"
+#endif
 
 #include "mozilla/Base64.h"
 #include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "gfxPrefs.h"
 #include "gfxUtils.h"
 #include "gfxVRPuppet.h"
+#include "VRManager.h"
 
 #include "mozilla/dom/GamepadEventTypes.h"
 #include "mozilla/dom/GamepadBinding.h"
 
 // See CompositorD3D11Shaders.h
 namespace mozilla {
 namespace layers {
 struct ShaderBytes { const void* mData; size_t mLength; };
@@ -33,29 +37,19 @@ using namespace mozilla::layers;
 
 // Reminder: changing the order of these buttons may break web content
 static const uint64_t kPuppetButtonMask[] = {
   1,
   2,
   4,
   8
 };
-
 static const uint32_t kNumPuppetButtonMask = sizeof(kPuppetButtonMask) /
                                              sizeof(uint64_t);
-
-static const uint32_t kPuppetAxes[] = {
-  0,
-  1,
-  2
-};
-
-static const uint32_t kNumPuppetAxis = sizeof(kPuppetAxes) /
-                                       sizeof(uint32_t);
-
+static const uint32_t kNumPuppetAxis = 3;
 static const uint32_t kNumPuppetHaptcs = 1;
 
 VRDisplayPuppet::VRDisplayPuppet()
  : VRDisplayHost(VRDeviceType::Puppet)
  , mIsPresenting(false)
 {
   MOZ_COUNT_CTOR_INHERITED(VRDisplayPuppet, VRDisplayHost);
 
@@ -373,17 +367,17 @@ VRDisplayPuppet::SubmitFrame(TextureSour
       VRSubmitFrameResultInfo result;
       result.mFormat = SurfaceFormat::B8G8R8A8;
       // If the original texture size is not pow of 2, CopyResource() will add padding,
       // so the size is adjusted. We have to get the correct size by (mapInfo.RowPitch /
       // the format size).
       result.mWidth = mapInfo.RowPitch / 4;
       result.mHeight = desc.Height;
       result.mFrameNum = mDisplayInfo.mFrameId;
-      nsCString rawString(Substring((char*)srcData, mapInfo.RowPitch * desc.Height));
+      nsCString rawString(Substring(srcData, mapInfo.RowPitch * desc.Height));
 
       if (Base64Encode(rawString, result.mBase64Image) != NS_OK) {
         MOZ_ASSERT(false, "Failed to encode base64 images.");
       }
       mContext->Unmap(mappedTexture, 0);
       // Dispatch the base64 encoded string to the DOM side. Then, it will be decoded
       // and convert to a PNG image there.
       vm->DispatchSubmitFrameResult(mDisplayInfo.mDisplayID, result);
@@ -469,22 +463,76 @@ VRDisplayPuppet::SubmitFrame(TextureSour
 #elif defined(XP_MACOSX)
 
 bool
 VRDisplayPuppet::SubmitFrame(MacIOSurface* aMacIOSurface,
                              const IntSize& aSize,
                              const gfx::Rect& aLeftEyeRect,
                              const gfx::Rect& aRightEyeRect)
 {
-  if (!mIsPresenting) {
+  if (!mIsPresenting || !aMacIOSurface) {
     return false;
   }
 
-  // TODO: Bug 1343730, Need to block until the next simulated
-  // vblank interval and capture frames for use in reftests.
+  VRManager* vm = VRManager::Get();
+  MOZ_ASSERT(vm);
+
+  switch (gfxPrefs::VRPuppetSubmitFrame()) {
+    case 0:
+      // The VR frame is not displayed.
+      break;
+    case 1:
+    {
+      // The frames are submitted to VR compositor are decoded
+      // into a base64Image and dispatched to the DOM side.
+      RefPtr<SourceSurface> surf = aMacIOSurface->GetAsSurface();
+      RefPtr<DataSourceSurface> dataSurf = surf ? surf->GetDataSurface() :
+                                           nullptr;
+      if (dataSurf) {
+        // Ideally, we should convert the srcData to a PNG image and decode it
+        // to a Base64 string here, but the GPU process does not have the privilege to
+        // access the image library. So, we have to convert the RAW image data
+        // to a base64 string and forward it to let the content process to
+        // do the image conversion.
+        DataSourceSurface::MappedSurface map;
+        if (!dataSurf->Map(gfx::DataSourceSurface::MapType::READ, &map)) {
+          MOZ_ASSERT(false, "Read DataSourceSurface fail.");
+          return false;
+        }
+        const uint8_t* srcData = map.mData;
+        const auto& surfSize = dataSurf->GetSize();
+        VRSubmitFrameResultInfo result;
+        result.mFormat = SurfaceFormat::B8G8R8A8;
+        result.mWidth = surfSize.width;
+        result.mHeight = surfSize.height;
+        result.mFrameNum = mDisplayInfo.mFrameId;
+        // If the original texture size is not pow of 2, the data will not be tightly strided.
+        // We have to copy the pixels by rows.
+        nsCString rawString;
+        for (int32_t i = 0; i < surfSize.height; i++) {
+          rawString += Substring((const char*)(srcData) + i * map.mStride,
+                                  surfSize.width * 4);
+        }
+        dataSurf->Unmap();
+
+        if (Base64Encode(rawString, result.mBase64Image) != NS_OK) {
+          MOZ_ASSERT(false, "Failed to encode base64 images.");
+        }
+        // Dispatch the base64 encoded string to the DOM side. Then, it will be decoded
+        // and convert to a PNG image there.
+        vm->DispatchSubmitFrameResult(mDisplayInfo.mDisplayID, result);
+      }
+      break;
+    }
+    case 2:
+    {
+      MOZ_ASSERT(false, "No support for showing VR frames on MacOSX yet.");
+      break;
+    }
+  }
 
   return false;
 }
 
 #endif
 
 void
 VRDisplayPuppet::NotifyVSync()
@@ -702,17 +750,17 @@ VRSystemManagerPuppet::HandleAxisMove(ui
   if (controller->GetAxisMove(aAxis) != aValue) {
     NewAxisMove(aControllerIdx, aAxis, aValue);
     controller->SetAxisMove(aAxis, aValue);
   }
 }
 
 void
 VRSystemManagerPuppet::HandlePoseTracking(uint32_t aControllerIdx,
-                                          const GamepadPoseState& aPose,
+                                          const dom::GamepadPoseState& aPose,
                                           VRControllerHost* aController)
 {
   MOZ_ASSERT(aController);
   if (aPose != aController->GetPose()) {
     aController->SetPose(aPose);
     NewPoseState(aControllerIdx, aPose);
   }
 }
--- a/gfx/vr/moz.build
+++ b/gfx/vr/moz.build
@@ -19,30 +19,30 @@ LOCAL_INCLUDES += [
     '/dom/base',
     '/gfx/layers/d3d11',
     '/gfx/thebes',
 ]
 
 UNIFIED_SOURCES += [
     'gfxVR.cpp',
     'gfxVROSVR.cpp',
-    'gfxVRPuppet.cpp',
     'ipc/VRLayerChild.cpp',
     'ipc/VRLayerParent.cpp',
     'ipc/VRManagerChild.cpp',
     'ipc/VRManagerParent.cpp',
     'VRDisplayClient.cpp',
     'VRDisplayPresentation.cpp',
     'VRManager.cpp',
 ]
 
 # VRDisplayHost includes MacIOSurface.h which includes Mac headers
 # which define Size and Points types in the root namespace that
 # often conflict with our own types.
 SOURCES += [
+    'gfxVRPuppet.cpp',
     'VRDisplayHost.cpp',
 ]
 
 # Build OpenVR on Windows, Linux, and macOS desktop targets
 if CONFIG['OS_TARGET'] in ('WINNT', 'Linux', 'Darwin'):
     DIRS += [
         'openvr',
     ]
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -440,39 +440,34 @@ js::DirectEval(JSContext* cx, HandleValu
 
 bool
 js::IsAnyBuiltinEval(JSFunction* fun)
 {
     return fun->maybeNative() == IndirectEval;
 }
 
 static bool
-ExecuteInNonSyntacticGlobalInternal(JSContext* cx, HandleObject global, HandleScript scriptArg,
-                                    HandleObject varEnv, HandleObject lexEnv)
+ExecuteInExtensibleLexicalEnvironment(JSContext* cx, HandleScript scriptArg, HandleObject env)
 {
     CHECK_REQUEST(cx);
-    assertSameCompartment(cx, global, varEnv, lexEnv);
-    MOZ_ASSERT(global->is<GlobalObject>());
-    MOZ_ASSERT(varEnv->is<NonSyntacticVariablesObject>());
-    MOZ_ASSERT(IsExtensibleLexicalEnvironment(lexEnv));
-    MOZ_ASSERT(lexEnv->enclosingEnvironment() == varEnv);
+    assertSameCompartment(cx, env);
+    MOZ_ASSERT(IsExtensibleLexicalEnvironment(env));
     MOZ_RELEASE_ASSERT(scriptArg->hasNonSyntacticScope());
 
     RootedScript script(cx, scriptArg);
-    Rooted<GlobalObject*> globalRoot(cx, &global->as<GlobalObject>());
     if (script->compartment() != cx->compartment()) {
         script = CloneGlobalScript(cx, ScopeKind::NonSyntactic, script);
         if (!script)
             return false;
 
         Debugger::onNewScript(cx, script);
     }
 
     RootedValue rval(cx);
-    return ExecuteKernel(cx, script, *lexEnv, UndefinedValue(),
+    return ExecuteKernel(cx, script, *env, UndefinedValue(),
                          NullFramePtr() /* evalInFrame */, rval.address());
 }
 
 JS_FRIEND_API(bool)
 js::ExecuteInGlobalAndReturnScope(JSContext* cx, HandleObject global, HandleScript scriptArg,
                                   MutableHandleObject envArg)
 {
     RootedObject varEnv(cx, NonSyntacticVariablesObject::create(cx));
@@ -480,17 +475,17 @@ js::ExecuteInGlobalAndReturnScope(JSCont
         return false;
 
     // Create lexical environment with |this| == global.
     // NOTE: This is required behavior for Gecko FrameScriptLoader
     RootedObject lexEnv(cx, LexicalEnvironmentObject::createNonSyntactic(cx, varEnv, global));
     if (!lexEnv)
         return false;
 
-    if (!ExecuteInNonSyntacticGlobalInternal(cx, global, scriptArg, varEnv, lexEnv))
+    if (!ExecuteInExtensibleLexicalEnvironment(cx, scriptArg, lexEnv))
         return false;
 
     envArg.set(lexEnv);
     return true;
 }
 
 JS_FRIEND_API(JSObject*)
 js::NewJSMEnvironment(JSContext* cx)
@@ -505,23 +500,60 @@ js::NewJSMEnvironment(JSContext* cx)
         return nullptr;
 
     return varEnv;
 }
 
 JS_FRIEND_API(bool)
 js::ExecuteInJSMEnvironment(JSContext* cx, HandleScript scriptArg, HandleObject varEnv)
 {
+    AutoObjectVector emptyChain(cx);
+    return ExecuteInJSMEnvironment(cx, scriptArg, varEnv, emptyChain);
+}
+
+JS_FRIEND_API(bool)
+js::ExecuteInJSMEnvironment(JSContext* cx, HandleScript scriptArg, HandleObject varEnv,
+                            AutoObjectVector& targetObj)
+{
     assertSameCompartment(cx, varEnv);
     MOZ_ASSERT(cx->compartment()->getNonSyntacticLexicalEnvironment(varEnv));
+    MOZ_DIAGNOSTIC_ASSERT(scriptArg->noScriptRval());
 
-    RootedObject global(cx, &varEnv->global());
-    RootedObject lexEnv(cx, JS_ExtensibleLexicalEnvironment(varEnv));
+    RootedObject env(cx, JS_ExtensibleLexicalEnvironment(varEnv));
 
-    return ExecuteInNonSyntacticGlobalInternal(cx, global, scriptArg, varEnv, lexEnv);
+    // If the Gecko subscript loader specifies target objects, we need to add
+    // them to the environment. These are added after the NSVO environment.
+    if (!targetObj.empty()) {
+        // The environment chain will be as follows:
+        //      GlobalObject / BackstagePass
+        //      LexicalEnvironmentObject[this=global]
+        //      NonSyntacticVariablesObject (the JSMEnvironment)
+        //      LexicalEnvironmentObject[this=nsvo]
+        //      WithEnvironmentObject[target=targetObj]
+        //      LexicalEnvironmentObject[this=targetObj] (*)
+        //
+        //  (*) This environment intentionally intercepts JSOP_GLOBALTHIS, but
+        //  not JSOP_FUNCTIONTHIS (which instead will fallback to the NSVO). I
+        //  don't make the rules, I just record them.
+
+        // Wrap the target objects in WithEnvironments.
+        if (!js::CreateObjectsForEnvironmentChain(cx, targetObj, env, &env))
+            return false;
+
+        // See CreateNonSyntacticEnvironmentChain
+        if (!JSObject::setQualifiedVarObj(cx, env))
+            return false;
+
+        // Create an extensible LexicalEnvironmentObject for target object
+        env = cx->compartment()->getOrCreateNonSyntacticLexicalEnvironment(cx, env);
+        if (!env)
+            return false;
+    }
+
+    return ExecuteInExtensibleLexicalEnvironment(cx, scriptArg, env);
 }
 
 JS_FRIEND_API(JSObject*)
 js::GetJSMEnvironmentOfScriptedCaller(JSContext* cx)
 {
     FrameIter iter(cx);
     if (iter.done())
         return nullptr;
@@ -531,8 +563,16 @@ js::GetJSMEnvironmentOfScriptedCaller(JS
     MOZ_RELEASE_ASSERT(!iter.isWasm());
 
     RootedObject env(cx, iter.environmentChain(cx));
     while (env && !env->is<NonSyntacticVariablesObject>())
         env = env->enclosingEnvironment();
 
     return env;
 }
+
+JS_FRIEND_API(bool)
+js::IsJSMEnvironment(JSObject* obj)
+{
+    // NOTE: This also returns true if the NonSyntacticVariablesObject was
+    // created for reasons other than the JSM loader.
+    return obj->is<NonSyntacticVariablesObject>();
+}
--- a/js/src/jsapi-tests/testExecuteInJSMEnvironment.cpp
+++ b/js/src/jsapi-tests/testExecuteInJSMEnvironment.cpp
@@ -19,16 +19,17 @@ BEGIN_TEST(testExecuteInJSMEnvironment_B
         "eval('this.e = 5');\n"
         "(0,eval)('this.f = 6');\n"
         "(function() { this.g = 7; })();\n"
         "function f_h() { this.h = 8; }; f_h();\n"
         ;
 
     JS::CompileOptions options(cx);
     options.setFileAndLine(__FILE__, __LINE__);
+    options.setNoScriptRval(true);
 
     JS::RootedScript script(cx);
     CHECK(JS::CompileForNonSyntacticScope(cx, options, src, sizeof(src)-1, &script));
 
     JS::RootedObject varEnv(cx, js::NewJSMEnvironment(cx));
     JS::RootedObject lexEnv(cx, JS_ExtensibleLexicalEnvironment(varEnv));
     CHECK(varEnv && varEnv->is<js::NonSyntacticVariablesObject>());
     CHECK(lexEnv && js::IsExtensibleLexicalEnvironment(lexEnv));
@@ -77,16 +78,17 @@ BEGIN_TEST(testExecuteInJSMEnvironment_C
     static const char src[] =
         "var output = callback();\n"
         ;
 
     CHECK(JS_DefineFunctions(cx, global, testFunctions));
 
     JS::CompileOptions options(cx);
     options.setFileAndLine(__FILE__, __LINE__);
+    options.setNoScriptRval(true);
 
     JS::RootedScript script(cx);
     CHECK(JS::CompileForNonSyntacticScope(cx, options, src, sizeof(src)-1, &script));
 
     JS::RootedObject nsvo(cx, js::NewJSMEnvironment(cx));
     CHECK(nsvo);
     CHECK(js::ExecuteInJSMEnvironment(cx, script, nsvo));
 
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -2883,26 +2883,79 @@ SetPropertyIgnoringNamedGetter(JSContext
                                JS::Handle<JS::PropertyDescriptor> ownDesc,
                                JS::ObjectOpResult& result);
 
 // This function is for one specific use case, please don't use this for anything else!
 extern JS_FRIEND_API(bool)
 ExecuteInGlobalAndReturnScope(JSContext* cx, JS::HandleObject obj, JS::HandleScript script,
                               JS::MutableHandleObject scope);
 
-// These functions are only for JSM component loader, please don't use this for anything else!
+// These functions are provided for the JSM component loader in Gecko.
+//
+// A 'JSMEnvironment' refers to an environment chain constructed for JSM loading
+// in a shared global. Internally it is a NonSyntacticVariablesObject with a
+// corresponding extensible LexicalEnvironmentObject that is accessible by
+// JS_ExtensibleLexicalEnvironment. The |this| value of that lexical environment
+// is the NSVO itself.
+//
+// Normal global environment (ES6):     JSM "global" environment:
+//
+//                                      * - extensible lexical environment
+//                                      |   (code runs in this environment;
+//                                      |    `let/const` bindings go here)
+//                                      |
+//                                      * - JSMEnvironment (=== `this`)
+//                                      |   (`var` bindings go here)
+//                                      |
+// * - extensible lexical environment   * - extensible lexical environment
+// |   (code runs in this environment;  |   (empty)
+// |    `let/const` bindings go here)   |
+// |                                    |
+// * - actual global (=== `this`)       * - shared JSM global
+//     (var bindings go here; and           (Object, Math, etc. live here)
+//      Object, Math, etc. live here)
+
+// Allocate a new environment in current compartment that is compatible with JSM
+// shared loading.
 extern JS_FRIEND_API(JSObject*)
 NewJSMEnvironment(JSContext* cx);
 
+// Execute the given script (copied into compartment if necessary) in the given
+// JSMEnvironment. The script must have been compiled for hasNonSyntacticScope.
+// The |jsmEnv| must have been previously allocated by NewJSMEnvironment.
+//
+// NOTE: The associated extensible lexical environment is reused.
 extern JS_FRIEND_API(bool)
-ExecuteInJSMEnvironment(JSContext* cx, JS::HandleScript script, JS::HandleObject nsvo);
-
+ExecuteInJSMEnvironment(JSContext* cx, JS::HandleScript script, JS::HandleObject jsmEnv);
+
+// Additionally, target objects may be specified as required by the Gecko
+// subscript loader. These are wrapped in non-syntactic WithEnvironments and
+// temporarily placed on environment chain.
+//
+// See also: JS::CloneAndExecuteScript(...)
+extern JS_FRIEND_API(bool)
+ExecuteInJSMEnvironment(JSContext* cx, JS::HandleScript script, JS::HandleObject jsmEnv,
+                        JS::AutoObjectVector& targetObj);
+
+// Used by native methods to determine the JSMEnvironment of caller if possible
+// by looking at stack frames. Returns nullptr if top frame isn't a scripted
+// caller in a JSM.
+//
+// NOTE: This may find NonSyntacticVariablesObject generated by other embedding
+// such as a Gecko FrameScript. Caller can check the compartment if needed.
 extern JS_FRIEND_API(JSObject*)
 GetJSMEnvironmentOfScriptedCaller(JSContext* cx);
 
+// Determine if obj is a JSMEnvironment
+//
+// NOTE: This may return true for an NonSyntacticVariablesObject generated by
+// other embedding such as a Gecko FrameScript. Caller can check compartment.
+extern JS_FRIEND_API(bool)
+IsJSMEnvironment(JSObject* obj);
+
 
 #if defined(XP_WIN) && defined(_WIN64)
 // Parameters use void* types to avoid #including windows.h. The return value of
 // this function is returned from the exception handler.
 typedef long
 (*JitExceptionHandler)(void* exceptionRecord,  // PEXECTION_RECORD
                        void* context);         // PCONTEXT
 
--- a/js/src/vm/EnvironmentObject.cpp
+++ b/js/src/vm/EnvironmentObject.cpp
@@ -3182,17 +3182,18 @@ js::GetDebugEnvironmentForGlobalLexicalE
 
 bool
 js::CreateObjectsForEnvironmentChain(JSContext* cx, AutoObjectVector& chain,
                                      HandleObject terminatingEnv, MutableHandleObject envObj)
 {
 #ifdef DEBUG
     for (size_t i = 0; i < chain.length(); ++i) {
         assertSameCompartment(cx, chain[i]);
-        MOZ_ASSERT(!chain[i]->is<GlobalObject>());
+        MOZ_ASSERT(!chain[i]->is<GlobalObject>() &&
+                   !chain[i]->is<NonSyntacticVariablesObject>());
     }
 #endif
 
     // Construct With object wrappers for the things on this environment chain
     // and use the result as the thing to scope the function to.
     Rooted<WithEnvironmentObject*> withEnv(cx);
     RootedObject enclosingEnv(cx, terminatingEnv);
     for (size_t i = chain.length(); i > 0; ) {
--- a/js/xpconnect/loader/mozJSComponentLoader.cpp
+++ b/js/xpconnect/loader/mozJSComponentLoader.cpp
@@ -441,19 +441,24 @@ mozJSComponentLoader::LoadModule(FileLoc
 }
 
 void
 mozJSComponentLoader::FindTargetObject(JSContext* aCx,
                                        MutableHandleObject aTargetObject)
 {
     aTargetObject.set(js::GetJSMEnvironmentOfScriptedCaller(aCx));
 
-    // The above could fail if the scripted caller is not a
-    // component/JSM (it could be a DOM scope, for instance).
-    if (!aTargetObject) {
+    // The above could fail if the scripted caller is not a component/JSM (it
+    // could be a DOM scope, for instance).
+    //
+    // If the target object was not in the JSM shared global, return the global
+    // instead. This is needed when calling the subscript loader within a frame
+    // script, since it the FrameScript NSVO will have been found.
+    if (!aTargetObject ||
+        !IsLoaderGlobal(js::GetGlobalForObjectCrossCompartment(aTargetObject))) {
         aTargetObject.set(CurrentGlobalOrNull(aCx));
     }
 }
 
 // This requires that the keys be strings and the values be pointers.
 template <class Key, class Data, class UserData>
 static size_t
 SizeOfTableExcludingThis(const nsBaseHashtable<Key, Data, UserData>& aTable,
--- a/js/xpconnect/loader/mozJSSubScriptLoader.cpp
+++ b/js/xpconnect/loader/mozJSSubScriptLoader.cpp
@@ -84,16 +84,31 @@ mozJSSubScriptLoader::mozJSSubScriptLoad
 }
 
 mozJSSubScriptLoader::~mozJSSubScriptLoader()
 {
 }
 
 NS_IMPL_ISUPPORTS(mozJSSubScriptLoader, mozIJSSubScriptLoader)
 
+#define JSSUB_CACHE_PREFIX(aType) "jssubloader/" aType
+
+static void
+SubscriptCachePath(JSContext* cx, nsIURI* uri, JS::HandleObject targetObj, nsACString& cachePath)
+{
+    // StartupCache must distinguish between non-syntactic vs global, as well as
+    // javascript version when computing the cache key.
+    bool hasNonSyntacticScope = !JS_IsGlobalObject(targetObj);
+    JSVersion version = JS_GetVersion(cx);
+    cachePath.Assign(hasNonSyntacticScope ? JSSUB_CACHE_PREFIX("non-syntactic")
+                                          : JSSUB_CACHE_PREFIX("global"));
+    cachePath.AppendPrintf("/%d", version);
+    PathifyURI(uri, cachePath);
+}
+
 static void
 ReportError(JSContext* cx, const nsACString& msg)
 {
     NS_ConvertUTF8toUTF16 ucMsg(msg);
 
     RootedValue exn(cx);
     if (xpc::NonVoidStringToJsval(cx, ucMsg, &exn)) {
         JS_SetPendingException(cx, exn);
@@ -173,47 +188,61 @@ EvalScript(JSContext* cx,
            bool startupCache,
            bool preloadCache,
            MutableHandleScript script)
 {
     if (JS_IsGlobalObject(targetObj)) {
         if (!JS::CloneAndExecuteScript(cx, script, retval)) {
             return false;
         }
+    } else if (js::IsJSMEnvironment(targetObj)) {
+        if (!ExecuteInJSMEnvironment(cx, script, targetObj)) {
+            return false;
+        }
+        retval.setUndefined();
     } else {
         JS::AutoObjectVector envChain(cx);
         if (!envChain.append(targetObj)) {
             return false;
         }
-        if (loadScope != targetObj &&
-            loadScope &&
-            !JS_IsGlobalObject(loadScope))
-        {
-            MOZ_DIAGNOSTIC_ASSERT(js::GetObjectCompartment(loadScope) ==
-                                  js::GetObjectCompartment(targetObj));
-
-            if (!envChain.append(loadScope)) {
+        if (!loadScope) {
+            // A null loadScope means we are cross-compartment. In this case, we
+            // should check the target isn't in the JSM loader shared-global or
+            // we will contaiminate all JSMs in the compartment.
+            //
+            // NOTE: If loadScope is already a shared-global JSM, we can't
+            // determine which JSM the target belongs to and have to assume it
+            // is in our JSM.
+            JSObject* targetGlobal = js::GetGlobalForObjectCrossCompartment(targetObj);
+            MOZ_DIAGNOSTIC_ASSERT(!mozJSComponentLoader::Get()->IsLoaderGlobal(targetGlobal),
+                                  "Don't load subscript into target in a shared-global JSM");
+            if (!JS::CloneAndExecuteScript(cx, envChain, script, retval)) {
                 return false;
             }
-        }
-        if (!JS::CloneAndExecuteScript(cx, envChain, script, retval)) {
-            return false;
+        } else if (JS_IsGlobalObject(loadScope)) {
+            if (!JS::CloneAndExecuteScript(cx, envChain, script, retval)) {
+                return false;
+            }
+        } else {
+            MOZ_ASSERT(js::IsJSMEnvironment(loadScope));
+            if (!js::ExecuteInJSMEnvironment(cx, script, loadScope, envChain)) {
+                return false;
+            }
+            retval.setUndefined();
         }
     }
 
     JSAutoCompartment rac(cx, targetObj);
     if (!JS_WrapValue(cx, retval)) {
         return false;
     }
 
     if (script && (startupCache || preloadCache)) {
         nsAutoCString cachePath;
-        JSVersion version = JS_GetVersion(cx);
-        cachePath.AppendPrintf("jssubloader/%d", version);
-        PathifyURI(uri, cachePath);
+        SubscriptCachePath(cx, uri, targetObj, cachePath);
 
         nsCString uriStr;
         if (preloadCache && NS_SUCCEEDED(uri->GetSpec(uriStr))) {
             // Note that, when called during startup, this will keep the
             // original JSScript object alive for an indefinite amount of time.
             // This has the side-effect of keeping the global that the script
             // was compiled for alive, too.
             //
@@ -656,20 +685,18 @@ mozJSSubScriptLoader::DoLoadSubScriptWit
 
     // Suppress caching if we're compiling as content or if we're loading a
     // blob: URI.
     bool ignoreCache = options.ignoreCache
         || !GetObjectPrincipal(targetObj)->GetIsSystemPrincipal()
         || scheme.EqualsLiteral("blob");
     StartupCache* cache = ignoreCache ? nullptr : StartupCache::GetSingleton();
 
-    JSVersion version = JS_GetVersion(cx);
     nsAutoCString cachePath;
-    cachePath.AppendPrintf("jssubloader/%d", version);
-    PathifyURI(uri, cachePath);
+    SubscriptCachePath(cx, uri, targetObj, cachePath);
 
     RootedScript script(cx);
     if (!options.ignoreCache) {
         if (!options.wantReturnValue)
             script = ScriptPreloader::GetSingleton().GetCachedScript(cx, cachePath);
         if (!script && cache)
             rv = ReadCachedScript(cache, cachePath, cx, &script);
         if (NS_FAILED(rv) || !script) {
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/tests/unit/environment_loadscript.jsm
@@ -0,0 +1,18 @@
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var EXPORTED_SYMBOLS = ["target", "bound"];
+
+var bound = "";
+var target = {};
+Services.scriptloader.loadSubScript("resource://test/environment_script.js", target);
+
+// Check global bindings
+try { void vu; bound += "vu,"; } catch (e) {}
+try { void vq; bound += "vq,"; } catch (e) {}
+try { void vl; bound += "vl,"; } catch (e) {}
+try { void gt; bound += "gt,"; } catch (e) {}
+try { void ed; bound += "ed,"; } catch (e) {}
+try { void ei; bound += "ei,"; } catch (e) {}
+try { void fo; bound += "fo,"; } catch (e) {}
+try { void fi; bound += "fi,"; } catch (e) {}
+try { void fd; bound += "fd,"; } catch (e) {}
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/tests/unit/test_SubscriptLoaderJSMEnvironment.js
@@ -0,0 +1,23 @@
+let tgt_load = {};
+let tgt_check = {};
+Components.utils.import("resource://test/environment_loadscript.jsm", tgt_load);
+Components.utils.import("resource://test/environment_checkscript.jsm", tgt_check);
+
+// Check target bindings
+var tgt_subscript_bound = "";
+for (var name of ["vu", "vq", "vl", "gt", "ed", "ei", "fo", "fi", "fd"])
+    if (tgt_load.target.hasOwnProperty(name))
+        tgt_subscript_bound += name + ",";
+
+// Expected subscript loader behavior is as follows:
+//  - Qualified vars and |this| access occur on target object
+//  - Lexical vars occur on ExtensibleLexicalEnvironment of target object
+//  - Bareword assignments and global |this| access occur on caller's global
+if (tgt_load.bound != "vu,ei,fo,fi,")
+    throw new Error("Unexpected global binding set - " + tgt_load.bound);
+if (tgt_subscript_bound != "vq,gt,ed,fd,")
+    throw new Error("Unexpected target binding set - " + tgt_subscript_bound);
+
+// Components should not share namespace
+if (tgt_check.bound != "")
+    throw new Error("Unexpected shared binding set - " + tgt_check.bound);
--- a/js/xpconnect/tests/unit/xpcshell.ini
+++ b/js/xpconnect/tests/unit/xpcshell.ini
@@ -7,16 +7,17 @@ support-files =
   bug451678_subscript.js
   component-blob.js
   component-blob.manifest
   component-file.js
   component-file.manifest
   component_import.js
   component_import.manifest
   environment_script.js
+  environment_loadscript.jsm
   environment_checkscript.jsm
   file_simple_script.js
   importer.jsm
   recursive_importA.jsm
   recursive_importB.jsm
   subScriptWithEarlyError.js
   syntax_error.jsm
 
@@ -137,9 +138,10 @@ head = head_watchdog.js
 [test_xray_SavedFrame-02.js]
 [test_xray_regexp.js]
 [test_resolve_dead_promise.js]
 [test_asyncLoadSubScriptError.js]
 [test_function_names.js]
 [test_FrameScriptEnvironment.js]
 [test_SubscriptLoaderEnvironment.js]
 [test_SubscriptLoaderSandboxEnvironment.js]
+[test_SubscriptLoaderJSMEnvironment.js]
 [test_ComponentEnvironment.js]
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -2479,22 +2479,29 @@ public:
     return sTextCombineUprightDigitsEnabled;
   }
 
   // Stylo (the Servo backend for Gecko's style system) is generally enabled
   // or disabled at compile-time. However, we provide the additional capability
   // to disable it dynamically in stylo-enabled builds via a pref.
   static bool StyloEnabled() {
 #ifdef MOZ_STYLO
-    return sStyloEnabled;
+    return sStyloEnabled && StyloSupportedInCurrentProcess();
 #else
     return false;
 #endif
   }
 
+  // Whether Stylo should be allowed to be enabled in this process.  This
+  // returns true for content processes and the non-e10s parent process.
+  static bool StyloSupportedInCurrentProcess() {
+     return XRE_IsContentProcess() ||
+            (XRE_IsParentProcess() && !XRE_IsE10sParentProcess());
+  }
+
   static bool StyleAttrWithXMLBaseDisabled() {
     return sStyleAttrWithXMLBaseDisabled;
   }
 
   static uint32_t IdlePeriodDeadlineLimit() {
     return sIdlePeriodDeadlineLimit;
   }
 
--- a/layout/base/nsStyleSheetService.cpp
+++ b/layout/base/nsStyleSheetService.cpp
@@ -253,19 +253,21 @@ nsStyleSheetService::LoadAndRegisterShee
   RefPtr<StyleSheet> geckoSheet;
   RefPtr<StyleSheet> servoSheet;
 
   rv = LoadSheet(aSheetURI, parsingMode, StyleBackendType::Gecko, &geckoSheet);
   NS_ENSURE_SUCCESS(rv, rv);
   MOZ_ASSERT(geckoSheet);
 
 #ifdef MOZ_STYLO
-  rv = LoadSheet(aSheetURI, parsingMode, StyleBackendType::Servo, &servoSheet);
-  NS_ENSURE_SUCCESS(rv, rv);
-  MOZ_ASSERT(servoSheet);
+  if (nsLayoutUtils::StyloSupportedInCurrentProcess()) {
+    rv = LoadSheet(aSheetURI, parsingMode, StyleBackendType::Servo, &servoSheet);
+    NS_ENSURE_SUCCESS(rv, rv);
+    MOZ_ASSERT(servoSheet);
+  }
 #endif
 
   mGeckoSheets[aSheetType].AppendElement(geckoSheet);
   mServoSheets[aSheetType].AppendElement(servoSheet);
 
   return NS_OK;
 }
 
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -296,17 +296,19 @@ nsLayoutStatics::Initialize()
 
   MediaDecoder::InitStatics();
 
   PromiseDebugging::Init();
 
   mozilla::dom::WebCryptoThreadPool::Initialize();
 
 #ifdef MOZ_STYLO
-  InitializeServo();
+  if (XRE_IsParentProcess() || XRE_IsContentProcess()) {
+    InitializeServo();
+  }
 #endif
 
 #ifndef MOZ_WIDGET_ANDROID
   // On Android, we instantiate it when constructing AndroidBridge.
   MediaPrefs::GetSingleton();
 #endif
 
   // This must be initialized on the main-thread.
@@ -318,18 +320,20 @@ nsLayoutStatics::Initialize()
 
 void
 nsLayoutStatics::Shutdown()
 {
   // Don't need to shutdown nsWindowMemoryReporter, that will be done by the
   // memory reporter manager.
 
 #ifdef MOZ_STYLO
-  ShutdownServo();
-  URLExtraData::ReleaseDummy();
+  if (XRE_IsParentProcess() || XRE_IsContentProcess()) {
+    ShutdownServo();
+    URLExtraData::ReleaseDummy();
+  }
 #endif
 
   nsMessageManagerScriptExecutor::Shutdown();
   nsFocusManager::Shutdown();
 #ifdef MOZ_XUL
   nsXULPopupManager::Shutdown();
 #endif
   StorageObserver::Shutdown();
--- a/layout/forms/nsButtonFrameRenderer.cpp
+++ b/layout/forms/nsButtonFrameRenderer.cpp
@@ -105,22 +105,24 @@ public:
     LayerManager* aManager,
     const ContainerLayerParameters& aContainerParameters) override;
 
   bool CanBuildWebRenderDisplayItems();
 
   virtual void Paint(nsDisplayListBuilder* aBuilder,
                      gfxContext* aCtx) override;
   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
-                           bool* aSnap) override;
+                           bool* aSnap) const override;
   NS_DISPLAY_DECL_NAME("ButtonBoxShadowOuter", TYPE_BUTTON_BOX_SHADOW_OUTER)
 };
 
 nsRect
-nsDisplayButtonBoxShadowOuter::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) {
+nsDisplayButtonBoxShadowOuter::GetBounds(nsDisplayListBuilder* aBuilder,
+                                         bool* aSnap) const
+{
   *aSnap = false;
   return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
 }
 
 void
 nsDisplayButtonBoxShadowOuter::Paint(nsDisplayListBuilder* aBuilder,
                                      gfxContext* aCtx) {
   nsRect frameRect = nsRect(ToReferenceFrame(), mFrame->GetSize());
@@ -277,21 +279,21 @@ public:
   virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                        HitTestState* aState,
                        nsTArray<nsIFrame*> *aOutFrames) override {
     aOutFrames->AppendElement(mFrame);
   }
   virtual void Paint(nsDisplayListBuilder* aBuilder,
                      gfxContext* aCtx) override;
   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
-                           bool* aSnap) override;
+                           bool* aSnap) const override;
   virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override;
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion *aInvalidRegion) override;
+                                         nsRegion *aInvalidRegion) const override;
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters) override;
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager,
                                              const ContainerLayerParameters& aContainerParameters) override;
   virtual bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
                                        const StackingContextHelper& aSc,
@@ -386,20 +388,19 @@ nsDisplayButtonBorder::CreateWebRenderCo
                                                                  mFrame,
                                                                  buttonRect);
   mBorderRenderer->CreateWebRenderCommands(aBuilder, aSc);
 
   return true;
 }
 
 void
-nsDisplayButtonBorder::ComputeInvalidationRegion(
-  nsDisplayListBuilder* aBuilder,
-  const nsDisplayItemGeometry* aGeometry,
-  nsRegion *aInvalidRegion)
+nsDisplayButtonBorder::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+                                                 const nsDisplayItemGeometry* aGeometry,
+                                                 nsRegion *aInvalidRegion) const
 {
   auto geometry =
     static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
 
   if (aBuilder->ShouldSyncDecodeImages() &&
       geometry->ShouldInvalidateToSyncDecodeImages()) {
     bool snap;
     aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
@@ -419,17 +420,19 @@ nsDisplayButtonBorder::Paint(nsDisplayLi
   // draw the border and background inside the focus and outline borders
   DrawResult result =
     mBFR->PaintBorder(aBuilder, pc, *aCtx, mVisibleRect, r);
 
   nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
 }
 
 nsRect
-nsDisplayButtonBorder::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) {
+nsDisplayButtonBorder::GetBounds(nsDisplayListBuilder* aBuilder,
+                                 bool* aSnap) const
+{
   *aSnap = false;
   return aBuilder->IsForEventDelivery() ? nsRect(ToReferenceFrame(), mFrame->GetSize())
           : mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
 }
 
 class nsDisplayButtonForeground : public nsDisplayItem {
 public:
   nsDisplayButtonForeground(nsDisplayListBuilder* aBuilder,
@@ -441,17 +444,17 @@ public:
   virtual ~nsDisplayButtonForeground() {
     MOZ_COUNT_DTOR(nsDisplayButtonForeground);
   }
 #endif
 
   nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override;
   void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                  const nsDisplayItemGeometry* aGeometry,
-                                 nsRegion *aInvalidRegion) override;
+                                 nsRegion *aInvalidRegion) const override;
   virtual void Paint(nsDisplayListBuilder* aBuilder,
                      gfxContext* aCtx) override;
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters) override;
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager,
                                              const ContainerLayerParameters& aContainerParameters) override;
@@ -468,20 +471,19 @@ private:
 
 nsDisplayItemGeometry*
 nsDisplayButtonForeground::AllocateGeometry(nsDisplayListBuilder* aBuilder)
 {
   return new nsDisplayItemGenericImageGeometry(this, aBuilder);
 }
 
 void
-nsDisplayButtonForeground::ComputeInvalidationRegion(
-  nsDisplayListBuilder* aBuilder,
-  const nsDisplayItemGeometry* aGeometry,
-  nsRegion* aInvalidRegion)
+nsDisplayButtonForeground::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+                                                     const nsDisplayItemGeometry* aGeometry,
+                                                     nsRegion* aInvalidRegion) const
 {
   auto geometry =
     static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
 
   if (aBuilder->ShouldSyncDecodeImages() &&
       geometry->ShouldInvalidateToSyncDecodeImages()) {
     bool snap;
     aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
--- a/layout/forms/nsFieldSetFrame.cpp
+++ b/layout/forms/nsFieldSetFrame.cpp
@@ -90,18 +90,19 @@ public:
     MOZ_COUNT_DTOR(nsDisplayFieldSetBorder);
   }
 #endif
   virtual void Paint(nsDisplayListBuilder* aBuilder,
                      gfxContext* aCtx) override;
   virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override;
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion *aInvalidRegion) override;
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+                                         nsRegion *aInvalidRegion) const override;
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+                           bool* aSnap) const override;
   NS_DISPLAY_DECL_NAME("FieldSetBorder", TYPE_FIELDSET_BORDER_BACKGROUND)
 };
 
 void
 nsDisplayFieldSetBorder::Paint(nsDisplayListBuilder* aBuilder,
                                gfxContext* aCtx)
 {
   image::DrawResult result = static_cast<nsFieldSetFrame*>(mFrame)->
@@ -114,33 +115,33 @@ nsDisplayItemGeometry*
 nsDisplayFieldSetBorder::AllocateGeometry(nsDisplayListBuilder* aBuilder)
 {
   return new nsDisplayItemGenericImageGeometry(this, aBuilder);
 }
 
 void
 nsDisplayFieldSetBorder::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                                    const nsDisplayItemGeometry* aGeometry,
-                                                   nsRegion *aInvalidRegion)
+                                                   nsRegion *aInvalidRegion) const
 {
   auto geometry =
     static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
 
   if (aBuilder->ShouldSyncDecodeImages() &&
       geometry->ShouldInvalidateToSyncDecodeImages()) {
     bool snap;
     aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
   }
 
   nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
 }
 
 nsRect
 nsDisplayFieldSetBorder::GetBounds(nsDisplayListBuilder* aBuilder,
-                                   bool* aSnap)
+                                   bool* aSnap) const
 {
   // Just go ahead and claim our frame's overflow rect as the bounds, because we
   // may have border-image-outset or other features that cause borders to extend
   // outside the border rect.  We could try to duplicate all the complexity
   // nsDisplayBorder has here, but keeping things in sync would be a pain, and
   // this code is not typically performance-sensitive.
   *aSnap = false;
   return Frame()->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
--- a/layout/forms/nsRangeFrame.cpp
+++ b/layout/forms/nsRangeFrame.cpp
@@ -180,48 +180,49 @@ public:
   virtual ~nsDisplayRangeFocusRing() {
     MOZ_COUNT_DTOR(nsDisplayRangeFocusRing);
   }
 #endif
 
   nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override;
   void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                  const nsDisplayItemGeometry* aGeometry,
-                                 nsRegion *aInvalidRegion) override;
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+                                 nsRegion *aInvalidRegion) const override;
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+                           bool* aSnap) const override;
   virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
   NS_DISPLAY_DECL_NAME("RangeFocusRing", TYPE_RANGE_FOCUS_RING)
 };
 
 nsDisplayItemGeometry*
 nsDisplayRangeFocusRing::AllocateGeometry(nsDisplayListBuilder* aBuilder)
 {
   return new nsDisplayItemGenericImageGeometry(this, aBuilder);
 }
 
 void
-nsDisplayRangeFocusRing::ComputeInvalidationRegion(
-  nsDisplayListBuilder* aBuilder,
-  const nsDisplayItemGeometry* aGeometry,
-  nsRegion* aInvalidRegion)
+nsDisplayRangeFocusRing::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+                                                   const nsDisplayItemGeometry* aGeometry,
+                                                   nsRegion* aInvalidRegion) const
 {
   auto geometry =
     static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
 
   if (aBuilder->ShouldSyncDecodeImages() &&
       geometry->ShouldInvalidateToSyncDecodeImages()) {
     bool snap;
     aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
   }
 
   nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
 }
 
 nsRect
-nsDisplayRangeFocusRing::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
+nsDisplayRangeFocusRing::GetBounds(nsDisplayListBuilder* aBuilder,
+                                   bool* aSnap) const
 {
   *aSnap = false;
   nsRect rect(ToReferenceFrame(), Frame()->GetSize());
 
   // We want to paint as if specifying a border for ::-moz-focus-outer
   // specifies an outline for our frame, so inflate by the border widths:
   nsStyleContext* styleContext =
     static_cast<nsRangeFrame*>(mFrame)->mOuterFocusStyle;
--- a/layout/forms/nsSelectsAreaFrame.cpp
+++ b/layout/forms/nsSelectsAreaFrame.cpp
@@ -101,17 +101,19 @@ public:
     MOZ_COUNT_CTOR(nsDisplayListFocus);
   }
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayListFocus() {
     MOZ_COUNT_DTOR(nsDisplayListFocus);
   }
 #endif
 
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override {
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+                           bool* aSnap) const override
+  {
     *aSnap = false;
     // override bounds because the list item focus ring may extend outside
     // the nsSelectsAreaFrame
     nsListControlFrame* listFrame = GetEnclosingListFrame(Frame());
     return listFrame->GetVisualOverflowRectRelativeToSelf() +
            listFrame->GetOffsetToCrossDoc(ReferenceFrame());
   }
   virtual void Paint(nsDisplayListBuilder* aBuilder,
--- a/layout/generic/TextDrawTarget.h
+++ b/layout/generic/TextDrawTarget.h
@@ -21,16 +21,41 @@ struct TextRunFragment {
 };
 
 // Only webrender handles this, so we use webrender types
 struct SelectionFragment {
   wr::ColorF color;
   wr::LayoutRect rect;
 };
 
+// Selections are used in nsTextFrame to hack in sub-frame style changes.
+// Most notably text-shadows can be changed by selections, and so we need to
+// group all the glyphs and decorations attached to a shadow. We do this by
+// having shadows apply to an entire SelectedTextRunFragment, and creating
+// one for each "piece" of selection.
+//
+// For instance, this text:
+//
+// Hello [there] my name [is Mega]man
+//          ^                ^
+//  normal selection      Ctrl+F highlight selection (yeah it's very overloaded)
+//
+// Would be broken up into 5 SelectedTextRunFragments
+//
+// ["Hello ", "there", " my name ", "is Mega", "man"]
+//
+// For almost all nsTextFrames, there will be only one SelectedTextRunFragment.
+struct SelectedTextRunFragment {
+  Maybe<SelectionFragment> selection;
+  nsTArray<wr::TextShadow> shadows;
+  nsTArray<TextRunFragment> text;
+  nsTArray<wr::Line> beforeDecorations;
+  nsTArray<wr::Line> afterDecorations;
+};
+
 // This class is fake DrawTarget, used to intercept text draw calls, while
 // also collecting up the other aspects of text natively.
 //
 // When using advanced-layers in nsDisplayText's constructor, we construct this
 // and run the full painting algorithm with this as the DrawTarget. This is
 // done to avoid having to massively refactor gecko's text painting code (which
 // has lots of components shared between other rendering algorithms).
 //
@@ -61,25 +86,37 @@ public:
   enum class Phase : uint8_t {
     eSelection, eUnderline, eOverline, eGlyphs, eEmphasisMarks, eLineThrough
   };
 
   explicit TextDrawTarget()
   : mCurrentlyDrawing(Phase::eSelection)
   {
     mCurrentTarget = gfx::Factory::CreateDrawTarget(gfx::BackendType::SKIA, IntSize(1, 1), gfx::SurfaceFormat::B8G8R8A8);
+    SetSelectionIndex(0);
   }
 
   // Prevent this from being copied
   TextDrawTarget(const TextDrawTarget& src) = delete;
   TextDrawTarget& operator=(const TextDrawTarget&) = delete;
 
   // Change the phase of text we're drawing.
   void StartDrawing(Phase aPhase) { mCurrentlyDrawing = aPhase; }
 
+  void SetSelectionIndex(size_t i) {
+    // i should only be accessed if i-1 has already been
+    MOZ_ASSERT(mParts.Length() <= i);
+
+    if (mParts.Length() == i){
+      mParts.AppendElement();
+    }
+
+    mCurrentPart = &mParts[i];
+  }
+
   // This overload just stores the glyphs/font/color.
   void
   FillGlyphs(ScaledFont* aFont,
              const GlyphBuffer& aBuffer,
              const Pattern& aPattern,
              const DrawOptions& aOptions,
              const GlyphRenderingOptions* aRenderingOptions) override
   {
@@ -99,24 +136,24 @@ public:
     if (mCurrentlyDrawing != Phase::eGlyphs &&
         mCurrentlyDrawing != Phase::eEmphasisMarks) {
       MOZ_CRASH("TextDrawTarget received glyphs in wrong phase");
     }
 
     // We need to push a new TextRunFragment whenever the font/color changes
     // (usually this implies some font fallback from mixing languages/emoji)
     TextRunFragment* fragment;
-    if (mText.IsEmpty() ||
-        mText.LastElement().font != aFont ||
-        mText.LastElement().color != colorPat->mColor) {
-      fragment = mText.AppendElement();
+    if (mCurrentPart->text.IsEmpty() ||
+        mCurrentPart->text.LastElement().font != aFont ||
+        mCurrentPart->text.LastElement().color != colorPat->mColor) {
+      fragment = mCurrentPart->text.AppendElement();
       fragment->font = aFont;
       fragment->color = colorPat->mColor;
     } else {
-      fragment = &mText.LastElement();
+      fragment = &mCurrentPart->text.LastElement();
     }
 
     nsTArray<Glyph>& glyphs = fragment->glyphs;
 
     size_t oldLength = glyphs.Length();
     glyphs.SetLength(oldLength + aBuffer.mNumGlyphs);
     PodCopy(glyphs.Elements() + oldLength, aBuffer.mGlyphs, aBuffer.mNumGlyphs);
 
@@ -129,44 +166,47 @@ public:
                            0, 0);
       for (size_t i = oldLength; i < oldLength + aBuffer.mNumGlyphs; ++i) {
         auto position = &glyphs[i].mPosition;
         *position = skew.TransformPoint(*position);
       }
     }
   }
 
-  void AppendShadow(const wr::TextShadow& aShadow) { mShadows.AppendElement(aShadow); }
+  void
+  AppendShadow(const wr::TextShadow& aShadow) {
+    mCurrentPart->shadows.AppendElement(aShadow);
+  }
 
   void
-  AppendSelection(const LayoutDeviceRect& aRect, const Color& aColor)
+  SetSelectionRect(const LayoutDeviceRect& aRect, const Color& aColor)
   {
     SelectionFragment frag;
     frag.rect = wr::ToLayoutRect(aRect);
     frag.color = wr::ToColorF(aColor);
-    mSelections.AppendElement(frag);
+    mCurrentPart->selection = Some(frag);
   }
 
   void
   AppendDecoration(const Point& aStart,
                    const Point& aEnd,
                    const float aThickness,
                    const bool aVertical,
                    const Color& aColor,
                    const uint8_t aStyle)
   {
     wr::Line* decoration;
 
     switch (mCurrentlyDrawing) {
       case Phase::eUnderline:
       case Phase::eOverline:
-        decoration = mBeforeDecorations.AppendElement();
+        decoration = mCurrentPart->beforeDecorations.AppendElement();
         break;
       case Phase::eLineThrough:
-        decoration = mAfterDecorations.AppendElement();
+        decoration = mCurrentPart->afterDecorations.AppendElement();
         break;
       default:
         MOZ_CRASH("TextDrawTarget received Decoration in wrong phase");
     }
 
     // This function is basically designed to slide into the decoration drawing
     // code of nsCSSRendering with minimum disruption, to minimize the
     // chances of implementation drift. As such, it mostly looks like a call
@@ -203,44 +243,77 @@ public:
       case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE:
       default:
         MOZ_CRASH("TextDrawTarget received unsupported line style");
     }
 
 
   }
 
-  const nsTArray<wr::TextShadow>& GetShadows() { return mShadows; }
-  const nsTArray<TextRunFragment>& GetText() { return mText; }
-  const nsTArray<SelectionFragment>& GetSelections() { return mSelections; }
-  const nsTArray<wr::Line>& GetBeforeDecorations() { return mBeforeDecorations; }
-  const nsTArray<wr::Line>& GetAfterDecorations() { return mAfterDecorations; }
+  const nsTArray<SelectedTextRunFragment>& GetParts() { return mParts; }
 
   bool
   CanSerializeFonts()
   {
-    for (const TextRunFragment& frag : GetText()) {
-      if (!frag.font->CanSerialize()) {
+    for (const SelectedTextRunFragment& part : GetParts()) {
+      for (const TextRunFragment& frag : part.text) {
+        if (!frag.font->CanSerialize()) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  // TextLayers don't support very complicated text right now. This checks
+  // if any of the problem cases exist.
+  bool
+  ContentsAreSimple()
+  {
+
+    ScaledFont* font = nullptr;
+
+    for (const SelectedTextRunFragment& part : GetParts()) {
+      // Can't handle shadows, selections, or decorations
+      if (part.shadows.Length() > 0 ||
+          part.beforeDecorations.Length() > 0 ||
+          part.afterDecorations.Length() > 0 ||
+          part.selection.isSome()) {
         return false;
       }
+
+      // Must only have one font (multiple colors is fine)
+      for (const mozilla::layout::TextRunFragment& text : part.text) {
+        if (!font) {
+          font = text.font;
+        }
+        if (font != text.font) {
+          return false;
+        }
+      }
     }
+
+    // Must have an actual font (i.e. actual text)
+    if (!font) {
+      return false;
+    }
+
     return true;
   }
 
 
 private:
   // The part of the text we're currently drawing (glyphs, underlines, etc.)
   Phase mCurrentlyDrawing;
 
-  // Properties of the whole text
-  nsTArray<wr::TextShadow> mShadows;
-  nsTArray<TextRunFragment> mText;
-  nsTArray<SelectionFragment> mSelections;
-  nsTArray<wr::Line> mBeforeDecorations;
-  nsTArray<wr::Line> mAfterDecorations;
+  // Which chunk of mParts is actively being populated
+  SelectedTextRunFragment* mCurrentPart;
+
+  // Chunks of the text, grouped by selection
+  nsTArray<SelectedTextRunFragment> mParts;
 
   // A dummy to handle parts of the DrawTarget impl we don't care for
   RefPtr<DrawTarget> mCurrentTarget;
 
   // The rest of this is dummy implementations of DrawTarget's API
 public:
   DrawTargetType GetType() const override {
     return mCurrentTarget->GetType();
--- a/layout/generic/TextOverflow.cpp
+++ b/layout/generic/TextOverflow.cpp
@@ -172,41 +172,42 @@ public:
     MOZ_COUNT_CTOR(nsDisplayTextOverflowMarker);
   }
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayTextOverflowMarker() {
     MOZ_COUNT_DTOR(nsDisplayTextOverflowMarker);
   }
 #endif
   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
-                           bool* aSnap) override {
+                           bool* aSnap) const override
+  {
     *aSnap = false;
     nsRect shadowRect =
       nsLayoutUtils::GetTextShadowRectsUnion(mRect, mFrame);
     return mRect.Union(shadowRect);
   }
 
-  virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override
+  virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const override
   {
     if (gfxPlatform::GetPlatform()->RespectsFontStyleSmoothing()) {
       // On OS X, web authors can turn off subpixel text rendering using the
       // CSS property -moz-osx-font-smoothing. If they do that, we don't need
       // to use component alpha layers for the affected text.
       if (mFrame->StyleFont()->mFont.smoothing == NS_FONT_SMOOTHING_GRAYSCALE) {
         return nsRect();
       }
     }
     bool snap;
     return GetBounds(aBuilder, &snap);
   }
 
   virtual void Paint(nsDisplayListBuilder* aBuilder,
                      gfxContext* aCtx) override;
 
-  virtual uint32_t GetPerFrameKey() override {
+  virtual uint32_t GetPerFrameKey() const override {
     return (mIndex << TYPE_BITS) | nsDisplayItem::GetPerFrameKey();
   }
   void PaintTextToContext(gfxContext* aCtx,
                           nsPoint aOffsetFromRect);
   NS_DISPLAY_DECL_NAME("TextOverflow", TYPE_TEXT_OVERFLOW)
 private:
   nsRect          mRect;   // in reference frame coordinates
   const nsStyleTextOverflowSide* mStyle;
--- a/layout/generic/nsBulletFrame.cpp
+++ b/layout/generic/nsBulletFrame.cpp
@@ -525,28 +525,27 @@ BulletRenderer::CreateWebRenderCommandsF
                                      aSc, destRect, destRect);
   }
 }
 
 class nsDisplayBullet final : public nsDisplayItem {
 public:
   nsDisplayBullet(nsDisplayListBuilder* aBuilder, nsBulletFrame* aFrame)
     : nsDisplayItem(aBuilder, aFrame)
-    , mDisableSubpixelAA(false)
   {
     MOZ_COUNT_CTOR(nsDisplayBullet);
   }
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayBullet() {
     MOZ_COUNT_DTOR(nsDisplayBullet);
   }
 #endif
 
   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
-                           bool* aSnap) override
+                           bool* aSnap) const override
   {
     *aSnap = false;
     return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
   }
 
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters) override;
@@ -565,34 +564,30 @@ public:
                        HitTestState* aState,
                        nsTArray<nsIFrame*> *aOutFrames) override {
     aOutFrames->AppendElement(mFrame);
   }
   virtual void Paint(nsDisplayListBuilder* aBuilder,
                      gfxContext* aCtx) override;
   NS_DISPLAY_DECL_NAME("Bullet", TYPE_BULLET)
 
-  virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override
+  virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const override
   {
     bool snap;
     return GetBounds(aBuilder, &snap);
   }
 
-  virtual void DisableComponentAlpha() override {
-    mDisableSubpixelAA = true;
-  }
-
   virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
   {
     return new nsDisplayBulletGeometry(this, aBuilder);
   }
 
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion *aInvalidRegion) override
+                                         nsRegion *aInvalidRegion) const override
   {
     const nsDisplayBulletGeometry* geometry = static_cast<const nsDisplayBulletGeometry*>(aGeometry);
     nsBulletFrame* f = static_cast<nsBulletFrame*>(mFrame);
 
     if (f->GetOrdinal() != geometry->mOrdinal) {
       bool snap;
       aInvalidRegion->Or(geometry->mBounds, GetBounds(aBuilder, &snap));
       return;
@@ -604,17 +599,16 @@ public:
       bool snap;
       aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
     }
 
     return nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
   }
 
 protected:
-  bool mDisableSubpixelAA;
   Maybe<BulletRenderer> mBulletRenderer;
 };
 
 LayerState
 nsDisplayBullet::GetLayerState(nsDisplayListBuilder* aBuilder,
                                LayerManager* aManager,
                                const ContainerLayerParameters& aParameters)
 {
--- a/layout/generic/nsCanvasFrame.cpp
+++ b/layout/generic/nsCanvasFrame.cpp
@@ -459,17 +459,17 @@ public:
   {
     MOZ_COUNT_CTOR(nsDisplayCanvasFocus);
   }
   virtual ~nsDisplayCanvasFocus() {
     MOZ_COUNT_DTOR(nsDisplayCanvasFocus);
   }
 
   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
-                           bool* aSnap) override
+                           bool* aSnap) const override
   {
     *aSnap = false;
     // This is an overestimate, but that's not a problem.
     nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
     return frame->CanvasArea() + ToReferenceFrame();
   }
 
   virtual void Paint(nsDisplayListBuilder* aBuilder,
--- a/layout/generic/nsCanvasFrame.h
+++ b/layout/generic/nsCanvasFrame.h
@@ -137,17 +137,18 @@ public:
   {
   }
 
   virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
                                  nsRegion* aVisibleRegion) override
   {
     return NS_GET_A(mColor) > 0;
   }
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+                           bool* aSnap) const override
   {
     nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
     *aSnap = true;
     return frame->CanvasArea() + ToReferenceFrame();
   }
   virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                        HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override
   {
@@ -190,24 +191,24 @@ class nsDisplayCanvasBackgroundImage : p
 public:
   explicit nsDisplayCanvasBackgroundImage(const InitData& aInitData)
     : nsDisplayBackgroundImage(aInitData)
   {
   }
 
   virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
 
-  virtual void NotifyRenderingChanged() override
+  virtual void NotifyRenderingChanged() const override
   {
     mFrame->DeleteProperty(nsIFrame::CachedBackgroundImageDT());
   }
 
   // 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() override { return false; }
+  virtual bool SupportsOptimizingToImage() const override { return false; }
 
   bool IsSingleFixedPositionImage(nsDisplayListBuilder* aBuilder,
                                   const nsRect& aClipRect,
                                   gfxRect* aDestRect);
 
 
   NS_DISPLAY_DECL_NAME("CanvasBackgroundImage", TYPE_CANVAS_BACKGROUND_IMAGE)
 };
--- a/layout/generic/nsColumnSetFrame.cpp
+++ b/layout/generic/nsColumnSetFrame.cpp
@@ -26,17 +26,17 @@ public:
     mBorderRenderers.Clear();
   }
 #endif
 
   /**
    * Returns the frame's visual overflow rect instead of the frame's bounds.
    */
   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
-                           bool* aSnap) override
+                           bool* aSnap) const override
   {
     *aSnap = false;
     return static_cast<nsColumnSetFrame*>(mFrame)->CalculateBounds(ToReferenceFrame());
   }
 
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters) override;
--- a/layout/generic/nsHTMLCanvasFrame.cpp
+++ b/layout/generic/nsHTMLCanvasFrame.cpp
@@ -74,17 +74,18 @@ public:
   virtual ~nsDisplayCanvas() {
     MOZ_COUNT_DTOR(nsDisplayCanvas);
   }
 #endif
 
   NS_DISPLAY_DECL_NAME("nsDisplayCanvas", TYPE_CANVAS)
 
   virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
-                                   bool* aSnap) override {
+                                   bool* aSnap) const override
+  {
     *aSnap = false;
     nsHTMLCanvasFrame* f = static_cast<nsHTMLCanvasFrame*>(Frame());
     HTMLCanvasElement* canvas =
       HTMLCanvasElement::FromContent(f->GetContent());
     nsRegion result;
     if (canvas->GetIsOpaque()) {
       // OK, the entire region painted by the canvas is opaque. But what is
       // that region? It's the canvas's "dest rect" (controlled by the
@@ -103,17 +104,18 @@ public:
                                              intrinsicSize, intrinsicRatio,
                                              f->StylePosition());
       return nsRegion(destRect.Intersect(constraintRect));
     }
     return result;
   }
 
   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
-                           bool* aSnap) override {
+                           bool* aSnap) const override
+  {
     *aSnap = true;
     nsHTMLCanvasFrame* f = static_cast<nsHTMLCanvasFrame*>(Frame());
     return f->GetInnerArea() + ToReferenceFrame();
   }
 
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager,
                                              const ContainerLayerParameters& aContainerParameters) override
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -1309,32 +1309,32 @@ public:
   virtual nsDisplayItemGeometry*
   AllocateGeometry(nsDisplayListBuilder* aBuilder) override
   {
     return new nsDisplayItemGenericImageGeometry(this, aBuilder);
   }
 
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion* aInvalidRegion) override
+                                         nsRegion* aInvalidRegion) const override
   {
     auto geometry =
       static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
 
     if (aBuilder->ShouldSyncDecodeImages() &&
         geometry->ShouldInvalidateToSyncDecodeImages()) {
       bool snap;
       aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
     }
 
     nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
   }
 
   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
-                           bool* aSnap) override
+                           bool* aSnap) const override
   {
     *aSnap = false;
     return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
   }
 
   virtual void Paint(nsDisplayListBuilder* aBuilder,
                      gfxContext* aCtx) override
   {
@@ -1561,17 +1561,17 @@ nsDisplayItemGeometry*
 nsDisplayImage::AllocateGeometry(nsDisplayListBuilder* aBuilder)
 {
   return new nsDisplayItemGenericImageGeometry(this, aBuilder);
 }
 
 void
 nsDisplayImage::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                           const nsDisplayItemGeometry* aGeometry,
-                                          nsRegion* aInvalidRegion)
+                                          nsRegion* aInvalidRegion) const
 {
   auto geometry =
     static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
 
   if (aBuilder->ShouldSyncDecodeImages() &&
       geometry->ShouldInvalidateToSyncDecodeImages()) {
     bool snap;
     aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
@@ -1583,17 +1583,17 @@ nsDisplayImage::ComputeInvalidationRegio
 already_AddRefed<imgIContainer>
 nsDisplayImage::GetImage()
 {
   nsCOMPtr<imgIContainer> image = mImage;
   return image.forget();
 }
 
 nsRect
-nsDisplayImage::GetDestRect()
+nsDisplayImage::GetDestRect() const
 {
   bool snap = true;
   const nsRect frameContentBox = GetBounds(&snap);
 
   nsImageFrame* imageFrame = static_cast<nsImageFrame*>(mFrame);
   return imageFrame->PredictedDestRect(frameContentBox);
 }
 
@@ -1656,17 +1656,17 @@ nsDisplayImage::GetLayerState(nsDisplayL
   }
 
   return LAYER_ACTIVE;
 }
 
 
 /* virtual */ nsRegion
 nsDisplayImage::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
-                                bool* aSnap)
+                                bool* aSnap) const
 {
   *aSnap = false;
   if (mImage && mImage->WillDrawOpaqueNow()) {
     const nsRect frameContentBox = GetBounds(aSnap);
     return GetDestRect().Intersect(frameContentBox);
   }
   return nsRegion();
 }
--- a/layout/generic/nsImageFrame.h
+++ b/layout/generic/nsImageFrame.h
@@ -419,47 +419,47 @@ public:
   }
   virtual ~nsDisplayImage() {
     MOZ_COUNT_DTOR(nsDisplayImage);
   }
 
   virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override;
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion* aInvalidRegion) override;
+                                         nsRegion* aInvalidRegion) const override;
   virtual void Paint(nsDisplayListBuilder* aBuilder,
                      gfxContext* aCtx) override;
 
   virtual already_AddRefed<imgIContainer> GetImage() override;
 
   /**
    * @return The dest rect we'll use when drawing this image, in app units.
    *         Not necessarily contained in this item's bounds.
    */
-  virtual nsRect GetDestRect() override;
+  virtual nsRect GetDestRect() const override;
 
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters) override;
-  nsRect GetBounds(bool* aSnap)
+  nsRect GetBounds(bool* aSnap) const
   {
     *aSnap = true;
 
     nsImageFrame* imageFrame = static_cast<nsImageFrame*>(mFrame);
     return imageFrame->GetInnerArea() + ToReferenceFrame();
   }
 
   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
-                           bool* aSnap) override
+                           bool* aSnap) const override
   {
     return GetBounds(aSnap);
   }
 
   virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
-                                   bool* aSnap) override;
+                                   bool* aSnap) const override;
 
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager,
                                              const ContainerLayerParameters& aContainerParameters) override;
   virtual bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
                                        const StackingContextHelper& aSc,
                                        nsTArray<WebRenderParentCommand>& aParentCommands,
                                        mozilla::layers::WebRenderLayerManager* aManager,
--- a/layout/generic/nsPageFrame.cpp
+++ b/layout/generic/nsPageFrame.cpp
@@ -472,17 +472,16 @@ static gfx::Matrix4x4 ComputePageTransfo
   float scale = aFrame->PresContext()->GetPageScale();
   return gfx::Matrix4x4::Scaling(scale, scale, 1);
 }
 
 class nsDisplayHeaderFooter : public nsDisplayItem {
 public:
   nsDisplayHeaderFooter(nsDisplayListBuilder* aBuilder, nsPageFrame *aFrame)
     : nsDisplayItem(aBuilder, aFrame)
-    , mDisableSubpixelAA(false)
   {
     MOZ_COUNT_CTOR(nsDisplayHeaderFooter);
   }
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayHeaderFooter() {
     MOZ_COUNT_DTOR(nsDisplayHeaderFooter);
   }
 #endif
@@ -493,26 +492,21 @@ public:
     nsPageFrame* pageFrame = do_QueryFrame(mFrame);
     MOZ_ASSERT(pageFrame, "We should have an nsPageFrame");
 #endif
     static_cast<nsPageFrame*>(mFrame)->
       PaintHeaderFooter(*aCtx, ToReferenceFrame(), mDisableSubpixelAA);
   }
   NS_DISPLAY_DECL_NAME("HeaderFooter", TYPE_HEADER_FOOTER)
 
-  virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override {
+  virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const override
+  {
     bool snap;
     return GetBounds(aBuilder, &snap);
   }
-
-  virtual void DisableComponentAlpha() override {
-    mDisableSubpixelAA = true;
-  }
-protected:
-  bool mDisableSubpixelAA;
 };
 
 //------------------------------------------------------------------------------
 void
 nsPageFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                               const nsDisplayListSet& aLists)
 {
   nsDisplayListCollection set;
--- a/layout/generic/nsPluginFrame.cpp
+++ b/layout/generic/nsPluginFrame.cpp
@@ -896,17 +896,17 @@ public:
   }
 #ifdef NS_BUILD_REFCNT_LOGGING
   ~nsDisplayPluginReadback() override {
     MOZ_COUNT_DTOR(nsDisplayPluginReadback);
   }
 #endif
 
   nsRect GetBounds(nsDisplayListBuilder* aBuilder,
-                           bool* aSnap) override;
+                   bool* aSnap) const override;
 
   NS_DISPLAY_DECL_NAME("PluginReadback", TYPE_PLUGIN_READBACK)
 
   already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager,
                                              const ContainerLayerParameters& aContainerParameters) override
   {
     return static_cast<nsPluginFrame*>(mFrame)->BuildLayer(aBuilder, aManager, this, aContainerParameters);
@@ -916,31 +916,34 @@ public:
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters) override
   {
     return LAYER_ACTIVE;
   }
 };
 
 static nsRect
-GetDisplayItemBounds(nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem, nsIFrame* aFrame)
+GetDisplayItemBounds(nsDisplayListBuilder* aBuilder,
+                     const nsDisplayItem* aItem,
+                     nsIFrame* aFrame)
 {
   // XXX For slightly more accurate region computations we should pixel-snap this
   return aFrame->GetContentRectRelativeToSelf() + aItem->ToReferenceFrame();
 }
 
 nsRect
-nsDisplayPluginReadback::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
+nsDisplayPluginReadback::GetBounds(nsDisplayListBuilder* aBuilder,
+                                   bool* aSnap) const
 {
   *aSnap = false;
   return GetDisplayItemBounds(aBuilder, this, mFrame);
 }
 
 nsRect
-nsDisplayPlugin::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
+nsDisplayPlugin::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const
 {
   *aSnap = true;
   return GetDisplayItemBounds(aBuilder, this, mFrame);
 }
 
 void
 nsDisplayPlugin::Paint(nsDisplayListBuilder* aBuilder,
                        gfxContext* aCtx)
@@ -1006,17 +1009,17 @@ nsDisplayPlugin::ComputeVisibility(nsDis
     }
   }
 
   return nsDisplayItem::ComputeVisibility(aBuilder, aVisibleRegion);
 }
 
 nsRegion
 nsDisplayPlugin::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
-                                 bool* aSnap)
+                                 bool* aSnap) const
 {
   *aSnap = false;
   nsRegion result;
   nsPluginFrame* f = static_cast<nsPluginFrame*>(mFrame);
   if (!aBuilder->IsForPluginGeometry()) {
     nsIWidget* widget = f->GetWidget();
     if (widget) {
       // Be conservative and treat plugins with widgets as not opaque,
@@ -1309,17 +1312,17 @@ nsPluginFrame::PrintPlugin(gfxContext& a
   // XXX Nav 4.x always sent a SetWindow call after print. Should we do the same?
   // XXX Calling DidReflow here makes no sense!!!
   nsDidReflowStatus status = nsDidReflowStatus::FINISHED; // should we use a special status?
   frame->DidReflow(presContext,
                    nullptr, status);  // DidReflow will take care of it
 }
 
 nsRect
-nsPluginFrame::GetPaintedRect(nsDisplayPlugin* aItem)
+nsPluginFrame::GetPaintedRect(const nsDisplayPlugin* aItem) const
 {
   if (!mInstanceOwner)
     return nsRect();
   nsRect r = GetContentRectRelativeToSelf();
   if (!mInstanceOwner->UseAsyncRendering())
     return r;
 
   nsIntSize size = mInstanceOwner->GetCurrentImageSize();
--- a/layout/generic/nsPluginFrame.h
+++ b/layout/generic/nsPluginFrame.h
@@ -173,17 +173,17 @@ public:
   LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                            LayerManager* aManager);
 
   /**
    * Get the rectangle (relative to this frame) which it will paint. Normally
    * the frame's content-box but may be smaller if the plugin is rendering
    * asynchronously and has a different-sized image temporarily.
    */
-  nsRect GetPaintedRect(nsDisplayPlugin* aItem);
+  nsRect GetPaintedRect(const nsDisplayPlugin* aItem) const;
 
   /**
    * If aSupports has a nsPluginFrame, then prepare it for a DocShell swap.
    * @see nsSubDocumentFrame::BeginSwapDocShells.
    * There will be a call to EndSwapDocShells after we were moved to the
    * new view tree.
    */
   static void BeginSwapDocShells(nsISupports* aSupports, void*);
@@ -356,19 +356,20 @@ public:
     aBuilder->SetContainsPluginItem();
   }
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayPlugin() {
     MOZ_COUNT_DTOR(nsDisplayPlugin);
   }
 #endif
 
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+                           bool* aSnap) const override;
   virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
-                                   bool* aSnap) override;
+                                   bool* aSnap) const override;
   virtual void Paint(nsDisplayListBuilder* aBuilder,
                      gfxContext* aCtx) override;
   virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
                                  nsRegion* aVisibleRegion) override;
 
   NS_DISPLAY_DECL_NAME("Plugin", TYPE_PLUGIN)
 
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -4950,17 +4950,18 @@ public:
                 const Maybe<bool>& aIsSelected);
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayText() {
     MOZ_COUNT_DTOR(nsDisplayText);
   }
 #endif
 
   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
-                           bool* aSnap) override {
+                           bool* aSnap) const override
+  {
     *aSnap = false;
     return mBounds;
   }
   virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                        HitTestState* aState,
                        nsTArray<nsIFrame*> *aOutFrames) override {
     if (nsRect(ToReferenceFrame(), mFrame->GetSize()).Intersects(aRect)) {
       aOutFrames->AppendElement(mFrame);
@@ -4976,17 +4977,17 @@ public:
                                        const StackingContextHelper& aSc,
                                        nsTArray<WebRenderParentCommand>& aParentCommands,
                                        WebRenderLayerManager* aManager,
                                        nsDisplayListBuilder* aDisplayListBuilder) override;
   virtual void Paint(nsDisplayListBuilder* aBuilder,
                      gfxContext* aCtx) override;
   NS_DISPLAY_DECL_NAME("Text", TYPE_TEXT)
 
-  virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override
+  virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const override
   {
     if (gfxPlatform::GetPlatform()->RespectsFontStyleSmoothing()) {
       // On OS X, web authors can turn off subpixel text rendering using the
       // CSS property -moz-osx-font-smoothing. If they do that, we don't need
       // to use component alpha layers for the affected text.
       if (mFrame->StyleFont()->mFont.smoothing == NS_FONT_SMOOTHING_GRAYSCALE) {
         return nsRect();
       }
@@ -4994,21 +4995,17 @@ public:
     bool snap;
     return GetBounds(aBuilder, &snap);
   }
 
   virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override;
 
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion *aInvalidRegion) override;
-
-  virtual void DisableComponentAlpha() override {
-    mDisableSubpixelAA = true;
-  }
+                                         nsRegion *aInvalidRegion) const override;
 
   void RenderToContext(gfxContext* aCtx, TextDrawTarget* aTextDrawer, nsDisplayListBuilder* aBuilder, bool aIsRecording = false);
 
   bool CanApplyOpacity() const override
   {
     nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
     if (f->IsSelected()) {
       return false;
@@ -5047,21 +5044,18 @@ public:
     int32_t totalContentLength;
     f->ToCString(buf, &totalContentLength);
 
     aStream << buf.get() << "\")";
 #endif
   }
 
   RefPtr<TextDrawTarget> mTextDrawer;
-
   nsRect mBounds;
-
   float mOpacity;
-  bool mDisableSubpixelAA;
 };
 
 class nsDisplayTextGeometry : public nsCharClipGeometry
 {
 public:
   nsDisplayTextGeometry(nsDisplayText* aItem, nsDisplayListBuilder* aBuilder)
     : nsCharClipGeometry(aItem, aBuilder)
     , mOpacity(aItem->mOpacity)
@@ -5083,17 +5077,17 @@ nsDisplayItemGeometry*
 nsDisplayText::AllocateGeometry(nsDisplayListBuilder* aBuilder)
 {
   return new nsDisplayTextGeometry(this, aBuilder);
 }
 
 void
 nsDisplayText::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion *aInvalidRegion)
+                                         nsRegion *aInvalidRegion) const
 {
   const nsDisplayTextGeometry* geometry = static_cast<const nsDisplayTextGeometry*>(aGeometry);
   nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
 
   nsTextFrame::TextDecorations decorations;
   f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, decorations);
 
   bool snap;
@@ -5117,17 +5111,16 @@ GetTextCombineScaleFactor(nsTextFrame* a
   float factor = aFrame->GetProperty(TextCombineScaleFactorProperty());
   return factor ? factor : 1.0f;
 }
 
 nsDisplayText::nsDisplayText(nsDisplayListBuilder* aBuilder, nsTextFrame* aFrame,
                              const Maybe<bool>& aIsSelected)
   : nsCharClipDisplayItem(aBuilder, aFrame)
   , mOpacity(1.0f)
-  , mDisableSubpixelAA(false)
 {
   MOZ_COUNT_CTOR(nsDisplayText);
   mIsFrameSelected = aIsSelected;
   mBounds = mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
     // Bug 748228
   mBounds.Inflate(mFrame->PresContext()->AppUnitsPerDevPixel());
 
   if (gfxPrefs::LayersAllowTextLayers() &&
@@ -5156,39 +5149,17 @@ nsDisplayText::GetLayerState(nsDisplayLi
 
   // If we're using the webrender backend, then we're good to go!
   if (aManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) {
     return mozilla::LAYER_ACTIVE;
   }
 
   // If we're using the TextLayer backend, then we need to make sure
   // the input is plain enough for it to handle
-
-  // Can't handle shadows, selections, or decorations
-  if (mTextDrawer->GetShadows().Length() > 0 ||
-      mTextDrawer->GetSelections().Length() > 0 ||
-      mTextDrawer->GetBeforeDecorations().Length() > 0 ||
-      mTextDrawer->GetAfterDecorations().Length() > 0) {
-    return mozilla::LAYER_NONE;
-  }
-
-  // Must only have one font (multiple colors is fine)
-  ScaledFont* font = nullptr;
-
-  for (const mozilla::layout::TextRunFragment& text : mTextDrawer->GetText()) {
-    if (!font) {
-      font = text.font;
-    }
-    if (font != text.font) {
-      return mozilla::LAYER_NONE;
-    }
-  }
-
-  // Must have an actual font (i.e. actual text)
-  if (!font) {
+  if (!mTextDrawer->ContentsAreSimple()) {
     return mozilla::LAYER_NONE;
   }
 
   return mozilla::LAYER_ACTIVE;
 }
 
 void
 nsDisplayText::Paint(nsDisplayListBuilder* aBuilder,
@@ -5232,48 +5203,52 @@ nsDisplayText::CreateWebRenderCommands(m
   wr::LayoutRect wrClipRect = aSc.ToRelativeLayoutRect(clipRect); // wr::ToLayoutRect(clipRect);
   wr::LayoutRect wrBoundsRect = aSc.ToRelativeLayoutRect(boundsRect); //wr::ToLayoutRect(boundsRect);
 
   // Drawing order: selections, shadows,
   //                underline, overline, [grouped in one array]
   //                text, emphasisText,  [grouped in one array]
   //                lineThrough
 
-  for (const mozilla::layout::SelectionFragment& selection:
-       mTextDrawer->GetSelections()) {
-    aBuilder.PushRect(selection.rect, wrClipRect, selection.color);
-  }
-
-  // WR takes the shadows in CSS-order (reverse of rendering order),
-  // because the drawing of a shadow actually occurs when it's popped.
-  for (const wr::TextShadow& shadow : mTextDrawer->GetShadows()) {
-    aBuilder.PushTextShadow(wrBoundsRect, wrClipRect, shadow);
-  }
-
-  for (const wr::Line& decoration: mTextDrawer->GetBeforeDecorations()) {
-    aBuilder.PushLine(wrClipRect, decoration);
-  }
-
-  for (const mozilla::layout::TextRunFragment& text: mTextDrawer->GetText()) {
-    // mOpacity is set after we do our analysis, so we need to apply it here.
-    // mOpacity is only non-trivial when we have "pure" text, so we don't
-    // ever need to apply it to shadows or decorations.
-    auto color = text.color;
-    color.a *= mOpacity;
-
-    aManager->WrBridge()->PushGlyphs(aBuilder, text.glyphs, text.font,
-                                     color, aSc, boundsRect, clipRect);
-  }
-
-  for (const wr::Line& decoration: mTextDrawer->GetAfterDecorations()) {
-    aBuilder.PushLine(wrClipRect, decoration);
-  }
-
-  for (size_t i = 0; i < mTextDrawer->GetShadows().Length(); ++i) {
-    aBuilder.PopTextShadow();
+  for (auto& part : mTextDrawer->GetParts()) {
+    if (part.selection) {
+      auto selection = part.selection.value();
+      aBuilder.PushRect(selection.rect, wrClipRect, selection.color);
+    }
+  }
+
+  for (auto& part : mTextDrawer->GetParts()) {
+    // WR takes the shadows in CSS-order (reverse of rendering order),
+    // because the drawing of a shadow actually occurs when it's popped.
+    for (const wr::TextShadow& shadow : part.shadows) {
+      aBuilder.PushTextShadow(wrBoundsRect, wrClipRect, shadow);
+    }
+
+    for (const wr::Line& decoration : part.beforeDecorations) {
+      aBuilder.PushLine(wrClipRect, decoration);
+    }
+
+    for (const mozilla::layout::TextRunFragment& text : part.text) {
+      // mOpacity is set after we do our analysis, so we need to apply it here.
+      // mOpacity is only non-trivial when we have "pure" text, so we don't
+      // ever need to apply it to shadows or decorations.
+      auto color = text.color;
+      color.a *= mOpacity;
+
+      aManager->WrBridge()->PushGlyphs(aBuilder, text.glyphs, text.font,
+                                       color, aSc, boundsRect, clipRect);
+    }
+
+    for (const wr::Line& decoration : part.afterDecorations) {
+      aBuilder.PushLine(wrClipRect, decoration);
+    }
+
+    for (size_t i = 0; i < part.shadows.Length(); ++i) {
+      aBuilder.PopTextShadow();
+    }
   }
 
   return true;
 }
 
 already_AddRefed<layers::Layer>
 nsDisplayText::BuildLayer(nsDisplayListBuilder* aBuilder,
                           LayerManager* aManager,
@@ -5293,29 +5268,36 @@ nsDisplayText::BuildLayer(nsDisplayListB
     layer = aManager->CreateTextLayer();
   }
 
   // GetLayerState has guaranteed to us that we have exactly one font
   // so this will be overwritten by the time we use it.
   ScaledFont* font = nullptr;
 
   nsTArray<GlyphArray> allGlyphs;
-  allGlyphs.SetCapacity(mTextDrawer->GetText().Length());
-  for (const mozilla::layout::TextRunFragment& text : mTextDrawer->GetText()) {
-    if (!font) {
-      font = text.font;
-    }
-
-    GlyphArray* glyphs = allGlyphs.AppendElement();
-    glyphs->glyphs() = text.glyphs;
-
-    // Apply folded alpha (only applies to glyphs)
-    auto color = text.color;
-    color.a *= mOpacity;
-    glyphs->color() = color;
+  size_t totalLength = 0;
+  for (auto& part : mTextDrawer->GetParts()) {
+    totalLength += part.text.Length();
+  }
+  allGlyphs.SetCapacity(totalLength);
+
+  for (auto& part : mTextDrawer->GetParts()) {
+    for (const mozilla::layout::TextRunFragment& text : part.text) {
+      if (!font) {
+        font = text.font;
+      }
+
+      GlyphArray* glyphs = allGlyphs.AppendElement();
+      glyphs->glyphs() = text.glyphs;
+
+      // Apply folded alpha (only applies to glyphs)
+      auto color = text.color;
+      color.a *= mOpacity;
+      glyphs->color() = color;
+    }
   }
 
   MOZ_ASSERT(font);
 
   layer->SetGlyphs(Move(allGlyphs));
   layer->SetScaledFont(font);
 
   auto A2D = mFrame->PresContext()->AppUnitsPerDevPixel();
@@ -6608,18 +6590,23 @@ nsTextFrame::PaintTextWithSelectionColor
   TextRangeStyle rangeStyle;
   // Draw background colors
   if (anyBackgrounds && !aParams.IsGenerateTextMask()) {
     int32_t appUnitsPerDevPixel =
       aParams.textPaintStyle->PresContext()->AppUnitsPerDevPixel();
     SelectionIterator iterator(prevailingSelections, contentRange,
                                *aParams.provider, mTextRun, startIOffset);
     SelectionType selectionType;
+    size_t selectionIndex = 0;
     while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
                                    &selectionType, &rangeStyle)) {
+      if (aParams.textDrawer) {
+        aParams.textDrawer->SetSelectionIndex(selectionIndex);
+      }
+
       nscolor foreground, background;
       GetSelectionTextColors(selectionType, *aParams.textPaintStyle,
                              rangeStyle, &foreground, &background);
       // Draw background color
       gfxFloat advance = hyphenWidth +
         mTextRun->GetAdvanceWidth(range, aParams.provider);
       if (NS_GET_A(background) > 0) {
         nsRect bgRect;
@@ -6631,25 +6618,26 @@ nsTextFrame::PaintTextWithSelectionColor
           bgRect = nsRect(aParams.framePt.x + offs, aParams.framePt.y,
                           advance, GetSize().height);
         }
 
         LayoutDeviceRect selectionRect =
           LayoutDeviceRect::FromAppUnits(bgRect, appUnitsPerDevPixel);
 
         if (aParams.textDrawer) {
-          aParams.textDrawer->AppendSelection(selectionRect,
-                                              ToDeviceColor(background));
+          aParams.textDrawer->SetSelectionRect(selectionRect,
+                                               ToDeviceColor(background));
         } else {
           PaintSelectionBackground(
             *aParams.context->GetDrawTarget(), background, aParams.dirtyRect,
             selectionRect, aParams.callbacks);
         }
       }
       iterator.UpdateWithAdvance(advance);
+      ++selectionIndex;
     }
   }
 
   if (aParams.IsPaintBGColor()) {
     return true;
   }
 
   gfxFloat advance;
@@ -6667,18 +6655,24 @@ nsTextFrame::PaintTextWithSelectionColor
   shadowParams.provider = aParams.provider;
   shadowParams.clipEdges = &aClipEdges;
 
   // Draw text
   const nsStyleText* textStyle = StyleText();
   SelectionIterator iterator(prevailingSelections, contentRange,
                              *aParams.provider, mTextRun, startIOffset);
   SelectionType selectionType;
+
+  size_t selectionIndex = 0;
   while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
                                  &selectionType, &rangeStyle)) {
+    if (aParams.textDrawer) {
+      aParams.textDrawer->SetSelectionIndex(selectionIndex);
+    }
+
     nscolor foreground, background;
     if (aParams.IsGenerateTextMask()) {
       foreground = NS_RGBA(0, 0, 0, 255);
     } else {
       GetSelectionTextColors(selectionType, *aParams.textPaintStyle,
                              rangeStyle, &foreground, &background);
     }
 
@@ -6707,16 +6701,17 @@ nsTextFrame::PaintTextWithSelectionColor
     // Draw text segment
     params.textColor = foreground;
     params.textStrokeColor = aParams.textPaintStyle->GetWebkitTextStrokeColor();
     params.textStrokeWidth = aParams.textPaintStyle->GetWebkitTextStrokeWidth();
     params.drawSoftHyphen = hyphenWidth > 0;
     DrawText(range, textBaselinePt, params);
     advance += hyphenWidth;
     iterator.UpdateWithAdvance(advance);
+    ++selectionIndex;
   }
   return true;
 }
 
 void
 nsTextFrame::PaintTextSelectionDecorations(
     const PaintTextSelectionParams& aParams,
     const UniquePtr<SelectionDetails>& aDetails,
@@ -6782,18 +6777,22 @@ nsTextFrame::PaintTextSelectionDecoratio
   if (verticalRun) {
     pt.x = (aParams.textBaselinePt.x - mAscent) / app;
   } else {
     pt.y = (aParams.textBaselinePt.y - mAscent) / app;
   }
   gfxFloat decorationOffsetDir = mTextRun->IsSidewaysLeft() ? -1.0 : 1.0;
   SelectionType nextSelectionType;
   TextRangeStyle selectedStyle;
+  size_t selectionIndex = 0;
   while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
                                  &nextSelectionType, &selectedStyle)) {
+    if (aParams.textDrawer) {
+      aParams.textDrawer->SetSelectionIndex(selectionIndex);
+    }
     gfxFloat advance = hyphenWidth +
       mTextRun->GetAdvanceWidth(range, aParams.provider);
     if (nextSelectionType == aSelectionType) {
       if (verticalRun) {
         pt.y = (aParams.framePt.y + iOffset -
                (mTextRun->IsInlineReversed() ? advance : 0)) / app;
       } else {
         pt.x = (aParams.framePt.x + iOffset -
@@ -6803,16 +6802,17 @@ nsTextFrame::PaintTextSelectionDecoratio
       gfxFloat xInFrame = pt.x - (aParams.framePt.x / app);
       DrawSelectionDecorations(
         aParams.context, aParams.dirtyRect, aSelectionType,
         *aParams.textPaintStyle, selectedStyle, pt, xInFrame,
         width, mAscent / app, decorationMetrics, aParams.callbacks,
         verticalRun, decorationOffsetDir, kDecoration);
     }
     iterator.UpdateWithAdvance(advance);
+    ++selectionIndex;
   }
 }
 
 bool
 nsTextFrame::PaintTextWithSelection(
     const PaintTextSelectionParams& aParams,
     const nsCharClipDisplayItem::ClipEdges& aClipEdges)
 {
@@ -6845,17 +6845,19 @@ nsTextFrame::PaintTextWithSelection(
       PaintTextSelectionDecorations(aParams, details, selectionType);
     }
   }
 
   return true;
 }
 
 void
-nsTextFrame::DrawEmphasisMarks(gfxContext* aContext, WritingMode aWM,
+nsTextFrame::DrawEmphasisMarks(gfxContext* aContext,
+                               TextDrawTarget* aTextDrawer,
+                               WritingMode aWM,
                                const gfxPoint& aTextBaselinePt,
                                const gfxPoint& aFramePt, Range aRange,
                                const nscolor* aDecorationOverrideColor,
                                PropertyProvider* aProvider)
 {
   const EmphasisMarkInfo* info = GetProperty(EmphasisMarkProperty());
   if (!info) {
     return;
@@ -6882,22 +6884,24 @@ nsTextFrame::DrawEmphasisMarks(gfxContex
   } else {
     if (aWM.IsVerticalRL()) {
       pt.x -= info->baselineOffset;
     } else {
       pt.x += info->baselineOffset;
     }
   }
   if (!isTextCombined) {
-    mTextRun->DrawEmphasisMarks(aContext, info->textRun.get(), info->advance,
-                                pt, aRange, aProvider);
+    mTextRun->DrawEmphasisMarks(aContext, aTextDrawer, info->textRun.get(),
+                                info->advance, pt, aRange, aProvider);
   } else {
     pt.y += (GetSize().height - info->advance) / 2;
+    gfxTextRun::DrawParams params(aContext);
+    params.textDrawer = aTextDrawer;
     info->textRun->Draw(Range(info->textRun.get()), pt,
-                        gfxTextRun::DrawParams(aContext));
+                        params);
   }
 }
 
 nscolor
 nsTextFrame::GetCaretColorAt(int32_t aOffset)
 {
   NS_PRECONDITION(aOffset >= 0, "aOffset must be positive");
 
@@ -7281,33 +7285,34 @@ nsTextFrame::PaintText(const PaintTextPa
 
 static void
 DrawTextRun(const gfxTextRun* aTextRun,
             const gfxPoint& aTextBaselinePt,
             gfxTextRun::Range aRange,
             const nsTextFrame::DrawTextRunParams& aParams)
 {
   gfxTextRun::DrawParams params(aParams.context);
+  params.textDrawer = aParams.textDrawer;
   params.provider = aParams.provider;
   params.advanceWidth = aParams.advanceWidth;
   params.contextPaint = aParams.contextPaint;
   params.callbacks = aParams.callbacks;
   if (aParams.callbacks) {
     aParams.callbacks->NotifyBeforeText(aParams.textColor);
     params.drawMode = DrawMode::GLYPH_PATH;
     aTextRun->Draw(aRange, aTextBaselinePt, params);
     aParams.callbacks->NotifyAfterText();
   } else {
-    if (NS_GET_A(aParams.textColor) != 0) {
+    if (NS_GET_A(aParams.textColor) != 0 || aParams.textDrawer) {
       aParams.context->SetColor(Color::FromABGR(aParams.textColor));
     } else {
       params.drawMode = DrawMode::GLYPH_STROKE;
     }
 
-    if (NS_GET_A(aParams.textStrokeColor) != 0 &&
+    if ((NS_GET_A(aParams.textStrokeColor) != 0 || aParams.textDrawer) &&
         aParams.textStrokeWidth != 0.0f) {
       StrokeOptions strokeOpts;
       params.drawMode |= DrawMode::GLYPH_STROKE;
       params.textStrokeColor = aParams.textStrokeColor;
       strokeOpts.mLineWidth = aParams.textStrokeWidth;
       params.strokeOpts = &strokeOpts;
       aTextRun->Draw(aRange, aTextBaselinePt, params);
     } else {
@@ -7488,17 +7493,17 @@ nsTextFrame::DrawTextRunAndDecorations(R
       // and *then* line-throughs
       DrawTextRun(aRange, aTextBaselinePt, aParams);
     }
 
     // Emphasis marks
     if (aParams.textDrawer) {
       aParams.textDrawer->StartDrawing(TextDrawTarget::Phase::eEmphasisMarks);
     }
-    DrawEmphasisMarks(aParams.context, wm,
+    DrawEmphasisMarks(aParams.context, aParams.textDrawer, wm,
                       aTextBaselinePt, aParams.framePt, aRange,
                       aParams.decorationOverrideColor, aParams.provider);
 
     // Line-throughs
     if (aParams.textDrawer && aDecorations.mStrikes.Length() > 0) {
       aParams.textDrawer->StartDrawing(TextDrawTarget::Phase::eLineThrough);
     }
     params.decoration = NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH;
--- a/layout/generic/nsTextFrame.h
+++ b/layout/generic/nsTextFrame.h
@@ -533,16 +533,17 @@ public:
     SelectionTypeMask* aAllSelectionTypeMask,
     const nsCharClipDisplayItem::ClipEdges& aClipEdges);
   // helper: paint text decorations for text selected by aSelectionType
   void PaintTextSelectionDecorations(const PaintTextSelectionParams& aParams,
                                      const mozilla::UniquePtr<SelectionDetails>& aDetails,
                                      SelectionType aSelectionType);
 
   void DrawEmphasisMarks(gfxContext* aContext,
+                         TextDrawTarget* aTextDrawer,
                          mozilla::WritingMode aWM,
                          const gfxPoint& aTextBaselinePt,
                          const gfxPoint& aFramePt,
                          Range aRange,
                          const nscolor* aDecorationOverrideColor,
                          PropertyProvider* aProvider);
 
   nscolor GetCaretColorAt(int32_t aOffset) override;
--- a/layout/generic/nsVideoFrame.cpp
+++ b/layout/generic/nsVideoFrame.cpp
@@ -493,17 +493,18 @@ public:
   // It would be great if we could override GetOpaqueRegion to return nonempty here,
   // but it's probably not safe to do so in general. Video frames are
   // updated asynchronously from decoder threads, and it's possible that
   // we might have an opaque video frame when GetOpaqueRegion is called, but
   // when we come to paint, the video frame is transparent or has gone
   // away completely (e.g. because of a decoder error). The problem would
   // be especially acute if we have off-main-thread rendering.
 
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+                           bool* aSnap) const override
   {
     *aSnap = true;
     nsIFrame* f = Frame();
     return f->GetContentRectRelativeToSelf() + ToReferenceFrame();
   }
 
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager,
--- a/layout/mathml/nsMathMLChar.cpp
+++ b/layout/mathml/nsMathMLChar.cpp
@@ -1862,17 +1862,19 @@ public:
     MOZ_COUNT_CTOR(nsDisplayMathMLCharForeground);
   }
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayMathMLCharForeground() {
     MOZ_COUNT_DTOR(nsDisplayMathMLCharForeground);
   }
 #endif
 
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override {
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+                           bool* aSnap) const override
+  {
     *aSnap = false;
     nsRect rect;
     mChar->GetRect(rect);
     nsPoint offset = ToReferenceFrame() + rect.TopLeft();
     nsBoundingMetrics bm;
     mChar->GetBoundingMetrics(bm);
     nsRect temp(offset.x + bm.leftBearing, offset.y,
                 bm.rightBearing - bm.leftBearing, bm.ascent + bm.descent);
@@ -1884,23 +1886,23 @@ public:
   virtual void Paint(nsDisplayListBuilder* aBuilder,
                      gfxContext* aCtx) override
   {
     mChar->PaintForeground(mFrame, *aCtx, ToReferenceFrame(), mIsSelected);
   }
 
   NS_DISPLAY_DECL_NAME("MathMLCharForeground", TYPE_MATHML_CHAR_FOREGROUND)
 
-  virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override
+  virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const override
   {
     bool snap;
     return GetBounds(aBuilder, &snap);
   }
 
-  virtual uint32_t GetPerFrameKey() override {
+  virtual uint32_t GetPerFrameKey() const override {
     return (mIndex << TYPE_BITS) | nsDisplayItem::GetPerFrameKey();
   }
 
 private:
   nsMathMLChar* mChar;
   uint32_t      mIndex;
   bool          mIsSelected;
 };
--- a/layout/mathml/nsMathMLFrame.cpp
+++ b/layout/mathml/nsMathMLFrame.cpp
@@ -333,17 +333,17 @@ public:
     MOZ_COUNT_CTOR(nsDisplayMathMLBar);
   }
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayMathMLBar() {
     MOZ_COUNT_DTOR(nsDisplayMathMLBar);
   }
 #endif
 
-  virtual uint32_t GetPerFrameKey() override {
+  virtual uint32_t GetPerFrameKey() const override {
     return (mIndex << TYPE_BITS) | nsDisplayItem::GetPerFrameKey();
   }
 
   virtual void Paint(nsDisplayListBuilder* aBuilder,
                      gfxContext* aCtx) override;
   NS_DISPLAY_DECL_NAME("MathMLBar", TYPE_MATHML_BAR)
 private:
   nsRect    mRect;
--- a/layout/mathml/nsMathMLmencloseFrame.cpp
+++ b/layout/mathml/nsMathMLmencloseFrame.cpp
@@ -756,17 +756,17 @@ public:
     MOZ_COUNT_CTOR(nsDisplayNotation);
   }
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayNotation() {
     MOZ_COUNT_DTOR(nsDisplayNotation);
   }
 #endif
 
-  virtual uint32_t GetPerFrameKey() override {
+  virtual uint32_t GetPerFrameKey() const override {
     return (mType << TYPE_BITS) | nsDisplayItem::GetPerFrameKey();
   }
 
   virtual void Paint(nsDisplayListBuilder* aBuilder,
                      gfxContext* aCtx) override;
   NS_DISPLAY_DECL_NAME("MathMLMencloseNotation", TYPE_MATHML_MENCLOSE_NOTATION)
 
 private:
--- a/layout/mathml/nsMathMLmtableFrame.cpp
+++ b/layout/mathml/nsMathMLmtableFrame.cpp
@@ -275,31 +275,32 @@ public:
 
   nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
   {
     return new nsDisplayItemGenericImageGeometry(this, aBuilder);
   }
 
   void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                  const nsDisplayItemGeometry* aGeometry,
-                                 nsRegion* aInvalidRegion) override
+                                 nsRegion* aInvalidRegion) const override
   {
     auto geometry =
       static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
 
     if (aBuilder->ShouldSyncDecodeImages() &&
         geometry->ShouldInvalidateToSyncDecodeImages()) {
       bool snap;
       aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
     }
 
     nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
   }
 
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+                           bool* aSnap) const override
   {
     *aSnap = true;
     nsStyleBorder styleBorder = *mFrame->StyleBorder();
     nsMathMLmtdFrame* frame = static_cast<nsMathMLmtdFrame*>(mFrame);
     ApplyBorderToStyle(frame, styleBorder);
     nsRect bounds = CalculateBounds(styleBorder).GetBounds();
     nsMargin overflow = ComputeBorderOverflow(frame, styleBorder);
     bounds.Inflate(overflow);
--- a/layout/painting/FrameLayerBuilder.cpp
+++ b/layout/painting/FrameLayerBuilder.cpp
@@ -1101,16 +1101,23 @@ public:
     CollectOldLayers();
   }
 
   /**
    * This is the method that actually walks a display list and builds
    * the child layers.
    */
   void ProcessDisplayItems(nsDisplayList* aList);
+  void ProcessDisplayItems(nsDisplayList* aList,
+                           AnimatedGeometryRoot* aLastAnimatedGeometryRoot,
+                           const ActiveScrolledRoot* aLastASR,
+                           const nsPoint& aLastAGRTopLeft,
+                           nsPoint& aTopLeft,
+                           int32_t aMaxLayers,
+                           int& aLayerCount);
   /**
    * This finalizes all the open PaintedLayers by popping every element off
    * mPaintedLayerDataStack, then sets the children of the container layer
    * to be all the layers in mNewChildLayers in that order and removes any
    * layers as children of the container that aren't in mNewChildLayers.
    * @param aTextContentFlags if any child layer has CONTENT_COMPONENT_ALPHA,
    * set *aTextContentFlags to CONTENT_COMPONENT_ALPHA
    */
@@ -3989,55 +3996,78 @@ ContainerState::ProcessDisplayItems(nsDi
     if (ChooseAnimatedGeometryRoot(*aList, &lastAnimatedGeometryRoot, &lastASR)) {
       lastAGRTopLeft = (*lastAnimatedGeometryRoot)->GetOffsetToCrossDoc(mContainerReferenceFrame);
     }
   }
 
   int32_t maxLayers = gfxPrefs::MaxActiveLayers();
   int layerCount = 0;
 
-  nsDisplayList savedItems;
-  nsDisplayItem* item;
-  while ((item = aList->RemoveBottom()) != nullptr) {
+  ProcessDisplayItems(aList, lastAnimatedGeometryRoot, lastASR,
+                      lastAGRTopLeft, topLeft, maxLayers, layerCount);
+}
+
+void
+ContainerState::ProcessDisplayItems(nsDisplayList* aList,
+                                    AnimatedGeometryRoot* aLastAnimatedGeometryRoot,
+                                    const ActiveScrolledRoot* aLastASR,
+                                    const nsPoint& aLastAGRTopLeft,
+                                    nsPoint& aTopLeft,
+                                    int32_t aMaxLayers,
+                                    int& aLayerCount)
+{
+  for (nsDisplayItem* i = aList->GetBottom(); i; i = i->GetAbove()) {
+    nsDisplayItem* item = i;
+    MOZ_ASSERT(item);
+
     DisplayItemType itemType = item->GetType();
 
     // If the item is a event regions item, but is empty (has no regions in it)
     // then we should just throw it out
     if (itemType == DisplayItemType::TYPE_LAYER_EVENT_REGIONS) {
       nsDisplayLayerEventRegions* eventRegions =
         static_cast<nsDisplayLayerEventRegions*>(item);
+
       if (eventRegions->IsEmpty()) {
-        item->Destroy(mBuilder);
         continue;
       }
     }
 
-    // Peek ahead to the next item and try merging with it or swapping with it
-    // if necessary.
-    nsDisplayItem* aboveItem;
-    while ((aboveItem = aList->GetBottom()) != nullptr) {
-      if (aboveItem->TryMerge(item)) {
-        aList->RemoveBottom();
-        item->Destroy(mBuilder);
-        item = aboveItem;
-        itemType = item->GetType();
-      } else {
+    // Peek ahead to the next item and see if it can be merged with the current
+    // item. We create a list of consecutive items that can be merged together.
+    AutoTArray<nsDisplayItem*, 1> mergedItems;
+    mergedItems.AppendElement(item);
+    for (nsDisplayItem* peek = item->GetAbove(); peek; peek = peek->GetAbove()) {
+      if (!item->CanMerge(peek)) {
         break;
       }
+
+      mergedItems.AppendElement(peek);
+
+      // Move the iterator forward since we will merge this item.
+      i = peek;
     }
 
-    nsDisplayList* itemSameCoordinateSystemChildren
-      = item->GetSameCoordinateSystemChildren();
+    if (mergedItems.Length() > 1) {
+      // We have items that can be merged together. Merge them into a temporary
+      // item and process that item immediately.
+      item = mBuilder->MergeItems(mergedItems);
+      MOZ_ASSERT(item && itemType == item->GetType());
+    }
+
+    nsDisplayList* childItems = item->GetSameCoordinateSystemChildren();
+
     if (item->ShouldFlattenAway(mBuilder)) {
-      aList->AppendToBottom(itemSameCoordinateSystemChildren);
-      item->Destroy(mBuilder);
+      MOZ_ASSERT(childItems);
+      ProcessDisplayItems(childItems, aLastAnimatedGeometryRoot, aLastASR,
+                          aLastAGRTopLeft, aTopLeft, aMaxLayers, aLayerCount);
       continue;
     }
 
-    savedItems.AppendToTop(item);
+    MOZ_ASSERT(item->GetType() != DisplayItemType::TYPE_WRAP_LIST);
 
     NS_ASSERTION(mAppUnitsPerDevPixel == AppUnitsPerDevPixel(item),
       "items in a container layer should all have the same app units per dev pixel");
 
     if (mBuilder->NeedToForceTransparentSurfaceForItem(item)) {
       aList->SetNeedsTransparentSurface();
     }
 
@@ -4054,19 +4084,19 @@ ContainerState::ProcessDisplayItems(nsDi
     }
 
     bool forceInactive;
     AnimatedGeometryRoot* animatedGeometryRoot;
     const ActiveScrolledRoot* itemASR = nullptr;
     const DisplayItemClipChain* layerClipChain = nullptr;
     if (mFlattenToSingleLayer && layerState != LAYER_ACTIVE_FORCE) {
       forceInactive = true;
-      animatedGeometryRoot = lastAnimatedGeometryRoot;
-      itemASR = lastASR;
-      topLeft = lastAGRTopLeft;
+      animatedGeometryRoot = aLastAnimatedGeometryRoot;
+      itemASR = aLastASR;
+      aTopLeft = aLastAGRTopLeft;
       item->FuseClipChainUpTo(mBuilder, mContainerASR);
     } else {
       forceInactive = false;
       if (mManager->IsWidgetLayerManager()) {
         animatedGeometryRoot = item->GetAnimatedGeometryRoot();
         itemASR = item->GetActiveScrolledRoot();
         const DisplayItemClipChain* itemClipChain = item->GetClipChain();
         if (itemClipChain && itemClipChain->mASR == itemASR &&
@@ -4078,17 +4108,17 @@ ContainerState::ProcessDisplayItems(nsDi
       } else {
         // For inactive layer subtrees, splitting content into PaintedLayers
         // based on animated geometry roots is pointless. It's more efficient
         // to build the minimum number of layers.
         animatedGeometryRoot = mContainerAnimatedGeometryRoot;
         itemASR = mContainerASR;
         item->FuseClipChainUpTo(mBuilder, mContainerASR);
       }
-      topLeft = (*animatedGeometryRoot)->GetOffsetToCrossDoc(mContainerReferenceFrame);
+      aTopLeft = (*animatedGeometryRoot)->GetOffsetToCrossDoc(mContainerReferenceFrame);
     }
 
     const ActiveScrolledRoot* scrollMetadataASR =
         layerClipChain ? ActiveScrolledRoot::PickDescendant(itemASR, layerClipChain->mASR) : itemASR;
 
     bool snap;
     nsRect itemContent = item->GetBounds(mBuilder, &snap);
     if (itemType == DisplayItemType::TYPE_LAYER_EVENT_REGIONS) {
@@ -4135,28 +4165,28 @@ ContainerState::ProcessDisplayItems(nsDi
     // We haven't computed visibility at this point, so item->GetVisibleRect()
     // is just the dirty rect that item was initialized with. We intersect it
     // with the clipped item bounds to get a tighter visible rect.
     if (!prerenderedTransform) {
       itemVisibleRect = itemVisibleRect.Intersect(
         ScaleToOutsidePixels(item->GetVisibleRect(), false));
     }
 
-    if (maxLayers != -1 && layerCount >= maxLayers) {
+    if (aMaxLayers != -1 && aLayerCount >= aMaxLayers) {
       forceInactive = true;
     }
 
     // Assign the item to a layer
     if (layerState == LAYER_ACTIVE_FORCE ||
         (layerState == LAYER_INACTIVE && !mManager->IsWidgetLayerManager()) ||
         (!forceInactive &&
          (layerState == LAYER_ACTIVE_EMPTY ||
           layerState == LAYER_ACTIVE))) {
 
-      layerCount++;
+      aLayerCount++;
 
       // LAYER_ACTIVE_EMPTY means the layer is created just for its metadata.
       // We should never see an empty layer with any visible content!
       NS_ASSERTION(layerState != LAYER_ACTIVE_EMPTY ||
                    itemVisibleRect.IsEmpty(),
                    "State is LAYER_ACTIVE_EMPTY but visible rect is not.");
 
       // As long as the new layer isn't going to be a PaintedLayer,
@@ -4351,22 +4381,21 @@ ContainerState::ProcessDisplayItems(nsDi
       }
 
       if (item->GetType() == DisplayItemType::TYPE_MASK) {
         MOZ_ASSERT(itemClip.GetRoundedRectCount() == 0);
 
         nsDisplayMask* maskItem = static_cast<nsDisplayMask*>(item);
         SetupMaskLayerForCSSMask(ownLayer, maskItem);
 
-        nsDisplayItem* next = aList->GetBottom();
-        if (next && next->GetType() == DisplayItemType::TYPE_SCROLL_INFO_LAYER) {
+        if (i->GetAbove() &&
+            i->GetAbove()->GetType() == DisplayItemType::TYPE_SCROLL_INFO_LAYER) {
           // Since we do build a layer for mask, there is no need for this
           // scroll info layer anymore.
-          aList->RemoveBottom();
-          next->Destroy(mBuilder);
+          i = i->GetAbove();
         }
       }
 
       // Convert the visible rect to a region and give the item
       // a chance to try restrict it further.
       nsIntRegion itemVisibleRegion = itemVisibleRect;
       nsRegion tightBounds = item->GetTightBounds(mBuilder, &snap);
       if (!tightBounds.IsEmpty()) {
@@ -4465,17 +4494,17 @@ ContainerState::ProcessDisplayItems(nsDi
       mLayerBuilder->AddLayerDisplayItem(ownLayer, item, layerState, nullptr);
     } else {
       PaintedLayerData* paintedLayerData =
         mPaintedLayerDataTree.FindPaintedLayerFor(animatedGeometryRoot, itemASR, layerClipChain,
                                                   itemVisibleRect,
                                                   item->Frame()->In3DContextAndBackfaceIsHidden(),
                                                   [&]() {
           return NewPaintedLayerData(item, animatedGeometryRoot, itemASR, layerClipChain, scrollMetadataASR,
-                                     topLeft);
+                                     aTopLeft);
         });
 
       if (itemType == DisplayItemType::TYPE_LAYER_EVENT_REGIONS) {
         nsDisplayLayerEventRegions* eventRegions =
             static_cast<nsDisplayLayerEventRegions*>(item);
         paintedLayerData->AccumulateEventRegions(this, eventRegions);
       } else {
         // check to see if the new item has rounded rect clips in common with
@@ -4483,35 +4512,32 @@ ContainerState::ProcessDisplayItems(nsDi
         if (mManager->IsWidgetLayerManager()) {
           paintedLayerData->UpdateCommonClipCount(itemClip);
         }
         paintedLayerData->Accumulate(this, item, itemVisibleRect, itemClip, layerState, aList);
 
         if (!paintedLayerData->mLayer) {
           // Try to recycle the old layer of this display item.
           RefPtr<PaintedLayer> layer =
-            AttemptToRecyclePaintedLayer(animatedGeometryRoot, item, topLeft);
+            AttemptToRecyclePaintedLayer(animatedGeometryRoot, item, aTopLeft);
           if (layer) {
             paintedLayerData->mLayer = layer;
 
             NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, layer) < 0,
                          "Layer already in list???");
             mNewChildLayers[paintedLayerData->mNewChildLayersIndex].mLayer = layer.forget();
           }
         }
       }
     }
 
-    if (itemSameCoordinateSystemChildren &&
-        itemSameCoordinateSystemChildren->NeedsTransparentSurface()) {
+    if (childItems && childItems->NeedsTransparentSurface()) {
       aList->SetNeedsTransparentSurface();
     }
   }
-
-  aList->AppendToTop(&savedItems);
 }
 
 void
 ContainerState::InvalidateForLayerChange(nsDisplayItem* aItem, PaintedLayer* aNewLayer)
 {
   NS_ASSERTION(aItem->GetPerFrameKey(),
                "Display items that render using Thebes must have a key");
   nsDisplayItemGeometry* oldGeometry = nullptr;
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -851,16 +851,49 @@ nsDisplayListBuilder::AddAnimationsAndTr
 
   bool pending = !aBuilder;
   AnimationInfo& animationInfo = aLayer->GetAnimationInfo();
   AddAnimationsForProperty(aFrame, aBuilder, aItem, aProperty,
                            animationInfo, pending);
   animationInfo.TransferMutatedFlagToLayer(aLayer);
 }
 
+nsDisplayItem*
+nsDisplayListBuilder::MergeItems(nsTArray<nsDisplayItem*>& aMergedItems)
+{
+  // For merging, we create a temporary item by cloning the last item of the
+  // mergeable items list. This ensures that the temporary item will have the
+  // correct frame and bounds.
+  nsDisplayItem* merged = nullptr;
+
+  for (nsDisplayItem* item : Reversed(aMergedItems)) {
+    MOZ_ASSERT(item);
+
+    if (!merged) {
+      // Create the temporary item.
+      merged = item->Clone(this);
+      MOZ_ASSERT(merged);
+
+      AddTemporaryItem(merged);
+    } else {
+      // Merge the item properties (frame/bounds/etc) with the previously
+      // created temporary item.
+      MOZ_ASSERT(merged->CanMerge(item));
+      merged->Merge(item);
+    }
+
+    // Create nsDisplayWrapList that points to the internal display list of the
+    // item we are merging. This nsDisplayWrapList is added to the display list
+    // of the temporary item.
+    merged->MergeDisplayListFromItem(this, item);
+  }
+
+  return merged;
+}
+
 void
 nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter::InsertScrollFrame(nsIScrollableFrame* aScrollableFrame)
 {
   MOZ_ASSERT(!mUsed);
   size_t descendantsEndIndex = mBuilder->mActiveScrolledRoots.Length();
   const ActiveScrolledRoot* parentASR = mBuilder->mCurrentActiveScrolledRoot;
   const ActiveScrolledRoot* asr = mBuilder->AllocateActiveScrolledRoot(parentASR, aScrollableFrame);
   mBuilder->mCurrentActiveScrolledRoot = asr;
@@ -1124,16 +1157,19 @@ nsDisplayListBuilder::~nsDisplayListBuil
                "All frames should have been unmarked");
   NS_ASSERTION(mPresShellStates.Length() == 0,
                "All presshells should have been exited");
   NS_ASSERTION(!mCurrentTableItem, "No table item should be active");
 
   for (ActiveScrolledRoot* asr : mActiveScrolledRoots) {
     asr->ActiveScrolledRoot::~ActiveScrolledRoot();
   }
+  for (nsDisplayItem* i : mTemporaryItems) {
+    i->Destroy(this);
+  }
   for (DisplayItemClipChain* c : mClipChainsToDestroy) {
     c->DisplayItemClipChain::~DisplayItemClipChain();
   }
 
   MOZ_COUNT_DTOR(nsDisplayListBuilder);
 }
 
 uint32_t
@@ -2614,16 +2650,17 @@ nsDisplayItem::nsDisplayItem(nsDisplayLi
 nsDisplayItem::nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                              const ActiveScrolledRoot* aActiveScrolledRoot)
   : mFrame(aFrame)
   , mClipChain(aBuilder->ClipState().GetCurrentCombinedClipChain(aBuilder))
   , mClip(DisplayItemClipChain::ClipForASR(mClipChain, aActiveScrolledRoot))
   , mActiveScrolledRoot(aActiveScrolledRoot)
   , mAnimatedGeometryRoot(nullptr)
   , mForceNotVisible(aBuilder->IsBuildingInvisibleItems())
+  , mDisableSubpixelAA(false)
 #ifdef MOZ_DUMP_PAINTING
   , mPainted(false)
 #endif
 {
   mReferenceFrame = aBuilder->FindReferenceFrameFor(aFrame, &mToReferenceFrame);
   // This can return the wrong result if the item override ShouldFixToViewport(),
   // the item needs to set it again in its constructor.
   mAnimatedGeometryRoot = aBuilder->FindAnimatedGeometryRootFor(aFrame);
@@ -2791,17 +2828,17 @@ nsDisplayItem::IntersectClip(nsDisplayLi
   // we supply nullptr as the common ancestor so that CreateClipChainIntersection
   // clones the whole chain.
   const DisplayItemClipChain* ancestorClip =
     mClipChain ? FindCommonAncestorClipForIntersection(mClipChain, aOther) : nullptr;
   SetClipChain(aBuilder->CreateClipChainIntersection(ancestorClip, mClipChain, aOther));
 }
 
 nsRect
-nsDisplayItem::GetClippedBounds(nsDisplayListBuilder* aBuilder)
+nsDisplayItem::GetClippedBounds(nsDisplayListBuilder* aBuilder) const
 {
   bool snap;
   nsRect r = GetBounds(aBuilder, &snap);
   return GetClip().ApplyNonRoundedIntersection(r);
 }
 
 already_AddRefed<Layer>
 nsDisplayItem::BuildDisplayItemLayer(nsDisplayListBuilder* aBuilder,
@@ -2822,17 +2859,18 @@ nsDisplayItem::BuildDisplayItemLayer(nsD
   aManager->TrackDisplayItemLayer(layer);
   layer->SetDisplayItem(this, aBuilder);
   layer->SetBaseTransform(gfx::Matrix4x4::Translation(aContainerParameters.mOffset.x,
                                                       aContainerParameters.mOffset.y, 0));
   return layer.forget();
 }
 
 nsRect
-nsDisplaySolidColor::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
+nsDisplaySolidColor::GetBounds(nsDisplayListBuilder* aBuilder,
+                               bool* aSnap) const
 {
   *aSnap = true;
   return mBounds;
 }
 
 LayerState
 nsDisplaySolidColor::GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
@@ -2909,17 +2947,18 @@ nsDisplaySolidColor::CreateWebRenderComm
   aBuilder.PushRect(transformedRect,
                     transformedRect,
                     wr::ToColorF(ToDeviceColor(mColor)));
 
   return true;
 }
 
 nsRect
-nsDisplaySolidColorRegion::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
+nsDisplaySolidColorRegion::GetBounds(nsDisplayListBuilder* aBuilder,
+                                     bool* aSnap) const
 {
   *aSnap = true;
   return mRegion.GetBounds();
 }
 
 void
 nsDisplaySolidColorRegion::Paint(nsDisplayListBuilder* aBuilder,
                                  gfxContext* aCtx)
@@ -3422,17 +3461,17 @@ nsDisplayBackgroundImage::CanOptimizeToI
   if (!allowPartialImages && !mFillRect.Contains(mDestRect)) {
     return false;
   }
 
   return nsDisplayImageContainer::CanOptimizeToImageLayer(aManager, aBuilder);
 }
 
 nsRect
-nsDisplayBackgroundImage::GetDestRect()
+nsDisplayBackgroundImage::GetDestRect() const
 {
   return mDestRect;
 }
 
 already_AddRefed<imgIContainer>
 nsDisplayBackgroundImage::GetImage()
 {
   nsCOMPtr<imgIContainer> image = mImage;
@@ -3631,17 +3670,17 @@ nsDisplayBackgroundImage::ComputeVisibil
 
   // Return false if the background was propagated away from this
   // frame. We don't want this display item to show up and confuse
   // anything.
   return mBackgroundStyle;
 }
 
 /* static */ nsRegion
-nsDisplayBackgroundImage::GetInsideClipRegion(nsDisplayItem* aItem,
+nsDisplayBackgroundImage::GetInsideClipRegion(const nsDisplayItem* aItem,
                                               StyleGeometryBox aClip,
                                               const nsRect& aRect,
                                               const nsRect& aBackgroundRect)
 {
   nsRegion result;
   if (aRect.IsEmpty())
     return result;
 
@@ -3661,17 +3700,18 @@ nsDisplayBackgroundImage::GetInsideClipR
     clipRect.Deflate(border);
   }
 
   return clipRect.Intersect(aRect);
 }
 
 nsRegion
 nsDisplayBackgroundImage::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
-                                          bool* aSnap) {
+                                          bool* aSnap) const
+{
   nsRegion result;
   *aSnap = false;
 
   if (!mBackgroundStyle)
     return result;
 
   *aSnap = true;
 
@@ -3691,41 +3731,42 @@ nsDisplayBackgroundImage::GetOpaqueRegio
       result = GetInsideClipRegion(this, layer.mClip, mBounds, mBackgroundRect);
     }
   }
 
   return result;
 }
 
 Maybe<nscolor>
-nsDisplayBackgroundImage::IsUniform(nsDisplayListBuilder* aBuilder) {
+nsDisplayBackgroundImage::IsUniform(nsDisplayListBuilder* aBuilder) const
+{
   if (!mBackgroundStyle) {
     return Some(NS_RGBA(0,0,0,0));
   }
   return Nothing();
 }
 
 nsRect
-nsDisplayBackgroundImage::GetPositioningArea()
+nsDisplayBackgroundImage::GetPositioningArea() const
 {
   if (!mBackgroundStyle) {
     return nsRect();
   }
   nsIFrame* attachedToFrame;
   bool transformedFixed;
   return nsCSSRendering::ComputeImageLayerPositioningArea(
       mFrame->PresContext(), mFrame,
       mBackgroundRect,
       mBackgroundStyle->mImage.mLayers[mLayer],
       &attachedToFrame,
       &transformedFixed) + ToReferenceFrame();
 }
 
 bool
-nsDisplayBackgroundImage::RenderingMightDependOnPositioningAreaSizeChange()
+nsDisplayBackgroundImage::RenderingMightDependOnPositioningAreaSizeChange() const
 {
   if (!mBackgroundStyle)
     return false;
 
   nscoord radii[8];
   if (mFrame->GetBorderRadii(radii)) {
     // A change in the size of the positioning area might change the position
     // of the rounded corners.
@@ -3768,19 +3809,20 @@ nsDisplayBackgroundImage::PaintInternal(
 
   if (clip == StyleGeometryBox::Text) {
     ctx->PopGroupAndBlend();
   }
 
   nsDisplayBackgroundGeometry::UpdateDrawResult(this, result);
 }
 
-void nsDisplayBackgroundImage::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
-                                                         const nsDisplayItemGeometry* aGeometry,
-                                                         nsRegion* aInvalidRegion)
+void
+nsDisplayBackgroundImage::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+                                                    const nsDisplayItemGeometry* aGeometry,
+                                                    nsRegion* aInvalidRegion) const
 {
   if (!mBackgroundStyle) {
     return;
   }
 
   const nsDisplayBackgroundGeometry* geometry = static_cast<const nsDisplayBackgroundGeometry*>(aGeometry);
 
   bool snap;
@@ -3819,17 +3861,19 @@ void nsDisplayBackgroundImage::ComputeIn
     // painting area.
     aInvalidRegion->Xor(bounds, geometry->mBounds);
 
     NotifyRenderingChanged();
   }
 }
 
 nsRect
-nsDisplayBackgroundImage::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) {
+nsDisplayBackgroundImage::GetBounds(nsDisplayListBuilder* aBuilder,
+                                    bool* aSnap) const
+{
   *aSnap = true;
   return mBounds;
 }
 
 nsRect
 nsDisplayBackgroundImage::GetBoundsInternal(nsDisplayListBuilder* aBuilder) {
   nsPresContext* presContext = mFrame->PresContext();
 
@@ -3843,33 +3887,27 @@ nsDisplayBackgroundImage::GetBoundsInter
     clipRect = frame->CanvasArea() + ToReferenceFrame();
   }
   const nsStyleImageLayers::Layer& layer = mBackgroundStyle->mImage.mLayers[mLayer];
   return nsCSSRendering::GetBackgroundLayerRect(presContext, mFrame,
                                                 mBackgroundRect, clipRect, layer,
                                                 aBuilder->GetBackgroundPaintFlags());
 }
 
-uint32_t
-nsDisplayBackgroundImage::GetPerFrameKey()
-{
-  return (mLayer << TYPE_BITS) | nsDisplayItem::GetPerFrameKey();
-}
-
 nsDisplayTableBackgroundImage::nsDisplayTableBackgroundImage(const InitData& aData,
                                                              nsIFrame* aCellFrame)
   : nsDisplayBackgroundImage(aData)
   , mStyleFrame(aData.frame)
   , mTableType(GetTableTypeFromFrame(mStyleFrame))
 {
   mFrame = aCellFrame;
 }
 
 bool
-nsDisplayTableBackgroundImage::IsInvalid(nsRect& aRect)
+nsDisplayTableBackgroundImage::IsInvalid(nsRect& aRect) const
 {
   bool result = mStyleFrame ? mStyleFrame->IsInvalid(aRect) : false;
   aRect += ToReferenceFrame();
   return result;
 }
 
 nsDisplayThemedBackground::nsDisplayThemedBackground(nsDisplayListBuilder* aBuilder,
                                                      nsIFrame* aFrame,
@@ -3921,44 +3959,46 @@ nsDisplayThemedBackground::HitTest(nsDis
   // Assume that any point in our background rect is a hit.
   if (mBackgroundRect.Intersects(aRect)) {
     aOutFrames->AppendElement(mFrame);
   }
 }
 
 nsRegion
 nsDisplayThemedBackground::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
-                                           bool* aSnap) {
+                                           bool* aSnap) const
+{
   nsRegion result;
   *aSnap = false;
 
   if (mThemeTransparency == nsITheme::eOpaque) {
     result = mBackgroundRect;
   }
   return result;
 }
 
 Maybe<nscolor>
-nsDisplayThemedBackground::IsUniform(nsDisplayListBuilder* aBuilder) {
+nsDisplayThemedBackground::IsUniform(nsDisplayListBuilder* aBuilder) const
+{
   if (mAppearance == NS_THEME_WIN_BORDERLESS_GLASS ||
       mAppearance == NS_THEME_WIN_GLASS) {
     return Some(NS_RGBA(0,0,0,0));
   }
   return Nothing();
 }
 
 bool
-nsDisplayThemedBackground::ProvidesFontSmoothingBackgroundColor(nscolor* aColor)
+nsDisplayThemedBackground::ProvidesFontSmoothingBackgroundColor(nscolor* aColor) const
 {
   nsITheme* theme = mFrame->PresContext()->GetTheme();
   return theme->WidgetProvidesFontSmoothingBackgroundColor(mFrame, mAppearance, aColor);
 }
 
 nsRect
-nsDisplayThemedBackground::GetPositioningArea()
+nsDisplayThemedBackground::GetPositioningArea() const
 {
   return mBackgroundRect;
 }
 
 void
 nsDisplayThemedBackground::Paint(nsDisplayListBuilder* aBuilder,
                                  gfxContext* aCtx)
 {
@@ -3976,25 +4016,27 @@ nsDisplayThemedBackground::PaintInternal
   nsITheme *theme = presContext->GetTheme();
   nsRect drawing(mBackgroundRect);
   theme->GetWidgetOverflow(presContext->DeviceContext(), mFrame, mAppearance,
                            &drawing);
   drawing.IntersectRect(drawing, aBounds);
   theme->DrawWidgetBackground(aCtx, mFrame, mAppearance, mBackgroundRect, drawing);
 }
 
-bool nsDisplayThemedBackground::IsWindowActive()
+bool
+nsDisplayThemedBackground::IsWindowActive() const
 {
   EventStates docState = mFrame->GetContent()->OwnerDoc()->GetDocumentState();
   return !docState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE);
 }
 
-void nsDisplayThemedBackground::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
-                                                          const nsDisplayItemGeometry* aGeometry,
-                                                          nsRegion* aInvalidRegion)
+void
+nsDisplayThemedBackground::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+                                                     const nsDisplayItemGeometry* aGeometry,
+                                                     nsRegion* aInvalidRegion) const
 {
   const nsDisplayThemedBackgroundGeometry* geometry = static_cast<const nsDisplayThemedBackgroundGeometry*>(aGeometry);
 
   bool snap;
   nsRect bounds = GetBounds(aBuilder, &snap);
   nsRect positioningArea = GetPositioningArea();
   if (!positioningArea.IsEqualInterior(geometry->mPositioningArea)) {
     // Invalidate everything (both old and new painting areas).
@@ -4009,17 +4051,19 @@ void nsDisplayThemedBackground::ComputeI
   nsITheme* theme = mFrame->PresContext()->GetTheme();
   if (theme->WidgetAppearanceDependsOnWindowFocus(mAppearance) &&
       IsWindowActive() != geometry->mWindowIsActive) {
     aInvalidRegion->Or(*aInvalidRegion, bounds);
   }
 }
 
 nsRect
-nsDisplayThemedBackground::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) {
+nsDisplayThemedBackground::GetBounds(nsDisplayListBuilder* aBuilder,
+                                     bool* aSnap) const
+{
   *aSnap = true;
   return mBounds;
 }
 
 nsRect
 nsDisplayThemedBackground::GetBoundsInternal() {
   nsPresContext* presContext = mFrame->PresContext();
 
@@ -4283,17 +4327,17 @@ nsDisplayBackgroundColor::Paint(nsDispla
   ctx->NewPath();
   ctx->Rectangle(bounds, true);
   ctx->Fill();
 #endif
 }
 
 nsRegion
 nsDisplayBackgroundColor::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
-                                          bool* aSnap)
+                                          bool* aSnap) const
 {
   *aSnap = false;
 
   if (mColor.a != 1) {
     return nsRegion();
   }
 
   if (!mBackgroundStyle)
@@ -4306,17 +4350,17 @@ nsDisplayBackgroundColor::GetOpaqueRegio
   }
 
   *aSnap = true;
   return nsDisplayBackgroundImage::GetInsideClipRegion(this, bottomLayer.mClip,
                                                        mBackgroundRect, mBackgroundRect);
 }
 
 Maybe<nscolor>
-nsDisplayBackgroundColor::IsUniform(nsDisplayListBuilder* aBuilder)
+nsDisplayBackgroundColor::IsUniform(nsDisplayListBuilder* aBuilder) const
 {
   return Some(mColor.ToABGR());
 }
 
 void
 nsDisplayBackgroundColor::HitTest(nsDisplayListBuilder* aBuilder,
                                   const nsRect& aRect,
                                   HitTestState* aState,
@@ -4356,17 +4400,19 @@ nsDisplayClearBackground::BuildLayer(nsD
   nsRect bounds = GetBounds(aBuilder, &snap);
   int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
   layer->SetBounds(bounds.ToNearestPixels(appUnitsPerDevPixel)); // XXX Do we need to respect the parent layer's scale here?
 
   return layer.forget();
 }
 
 nsRect
-nsDisplayOutline::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) {
+nsDisplayOutline::GetBounds(nsDisplayListBuilder* aBuilder,
+                            bool* aSnap) const
+{
   *aSnap = false;
   return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
 }
 
 void
 nsDisplayOutline::Paint(nsDisplayListBuilder* aBuilder,
                         gfxContext* aCtx) {
   // TODO join outlines together
@@ -4437,17 +4483,17 @@ nsDisplayOutline::CreateWebRenderCommand
     }
   }
 
   mBorderRenderer->CreateWebRenderCommands(aBuilder, aSc);
   return true;
 }
 
 bool
-nsDisplayOutline::IsInvisibleInRect(const nsRect& aRect)
+nsDisplayOutline::IsInvisibleInRect(const nsRect& aRect) const
 {
   const nsStyleOutline* outline = mFrame->StyleOutline();
   nsRect borderBox(ToReferenceFrame(), mFrame->GetSize());
   if (borderBox.Contains(aRect) &&
       !nsLayoutUtils::HasNonZeroCorner(outline->mOutlineRadius)) {
     if (outline->mOutlineOffset >= 0) {
       // aRect is entirely inside the border-rect, and the outline isn't
       // rendered inside the border-rect, so the outline is not visible.
@@ -4685,17 +4731,18 @@ nsDisplayCaret::nsDisplayCaret(nsDisplay
 #ifdef NS_BUILD_REFCNT_LOGGING
 nsDisplayCaret::~nsDisplayCaret()
 {
   MOZ_COUNT_DTOR(nsDisplayCaret);
 }
 #endif
 
 nsRect
-nsDisplayCaret::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
+nsDisplayCaret::GetBounds(nsDisplayListBuilder* aBuilder,
+                          bool* aSnap) const
 {
   *aSnap = true;
   // The caret returns a rect in the coordinates of mFrame.
   return mBounds;
 }
 
 void
 nsDisplayCaret::Paint(nsDisplayListBuilder* aBuilder,
@@ -4779,17 +4826,17 @@ nsDisplayBorder::nsDisplayBorder(nsDispl
   : nsDisplayItem(aBuilder, aFrame)
 {
   MOZ_COUNT_CTOR(nsDisplayBorder);
 
   mBounds = CalculateBounds(*mFrame->StyleBorder()).GetBounds();
 }
 
 bool
-nsDisplayBorder::IsInvisibleInRect(const nsRect& aRect)
+nsDisplayBorder::IsInvisibleInRect(const nsRect& aRect) const
 {
   nsRect paddingRect = mFrame->GetPaddingRect() - mFrame->GetPosition() +
     ToReferenceFrame();
   const nsStyleBorder *styleBorder;
   if (paddingRect.Contains(aRect) &&
       !(styleBorder = mFrame->StyleBorder())->IsBorderImageLoaded() &&
       !nsLayoutUtils::HasNonZeroCorner(styleBorder->mBorderRadius)) {
     // aRect is entirely inside the content rect, and no part
@@ -4808,17 +4855,17 @@ nsDisplayItemGeometry*
 nsDisplayBorder::AllocateGeometry(nsDisplayListBuilder* aBuilder)
 {
   return new nsDisplayBorderGeometry(this, aBuilder);
 }
 
 void
 nsDisplayBorder::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                            const nsDisplayItemGeometry* aGeometry,
-                                           nsRegion* aInvalidRegion)
+                                           nsRegion* aInvalidRegion) const
 {
   const nsDisplayBorderGeometry* geometry = static_cast<const nsDisplayBorderGeometry*>(aGeometry);
   bool snap;
 
   if (!geometry->mBounds.IsEqualInterior(GetBounds(aBuilder, &snap)) ||
       !geometry->mContentRect.IsEqualInterior(GetContentRect())) {
     // We can probably get away with only invalidating the difference
     // between the border and padding rects, but the XUL ui at least
@@ -5120,24 +5167,25 @@ nsDisplayBorder::Paint(nsDisplayListBuil
                                 mFrame->StyleContext(),
                                 flags,
                                 mFrame->GetSkipSides());
 
   nsDisplayBorderGeometry::UpdateDrawResult(this, result);
 }
 
 nsRect
-nsDisplayBorder::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
+nsDisplayBorder::GetBounds(nsDisplayListBuilder* aBuilder,
+                           bool* aSnap) const
 {
   *aSnap = true;
   return mBounds;
 }
 
 nsRegion
-nsDisplayBorder::CalculateBounds(const nsStyleBorder& aStyleBorder)
+nsDisplayBorder::CalculateBounds(const nsStyleBorder& aStyleBorder) const
 {
   nsRect borderBounds(ToReferenceFrame(), mFrame->GetSize());
   if (aStyleBorder.IsBorderImageLoaded()) {
     borderBounds.Inflate(aStyleBorder.GetImageOutset());
     return borderBounds;
   } else {
     nsMargin border = aStyleBorder.GetComputedBorder();
     nsRegion result;
@@ -5223,29 +5271,31 @@ nsDisplayBoxShadowOuter::Paint(nsDisplay
 
   for (uint32_t i = 0; i < rects.Length(); ++i) {
     nsCSSRendering::PaintBoxShadowOuter(presContext, *aCtx, mFrame,
                                         borderRect, rects[i], mOpacity);
   }
 }
 
 nsRect
-nsDisplayBoxShadowOuter::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) {
+nsDisplayBoxShadowOuter::GetBounds(nsDisplayListBuilder* aBuilder,
+                                   bool* aSnap) const
+{
   *aSnap = false;
   return mBounds;
 }
 
 nsRect
 nsDisplayBoxShadowOuter::GetBoundsInternal() {
   return nsLayoutUtils::GetBoxShadowRectForFrame(mFrame, mFrame->GetSize()) +
          ToReferenceFrame();
 }
 
 bool
-nsDisplayBoxShadowOuter::IsInvisibleInRect(const nsRect& aRect)
+nsDisplayBoxShadowOuter::IsInvisibleInRect(const nsRect& aRect) const
 {
   nsPoint origin = ToReferenceFrame();
   nsRect frameRect(origin, mFrame->GetSize());
   if (!frameRect.Contains(aRect))
     return false;
 
   // the visible region is entirely inside the border-rect, and box shadows
   // never render within the border-rect (unless there's a border radius).
@@ -5420,17 +5470,17 @@ nsDisplayBoxShadowOuter::CreateWebRender
   }
 
   return true;
 }
 
 void
 nsDisplayBoxShadowOuter::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                                    const nsDisplayItemGeometry* aGeometry,
-                                                   nsRegion* aInvalidRegion)
+                                                   nsRegion* aInvalidRegion) const
 {
   const nsDisplayBoxShadowOuterGeometry* geometry =
     static_cast<const nsDisplayBoxShadowOuterGeometry*>(aGeometry);
   bool snap;
   if (!geometry->mBounds.IsEqualInterior(GetBounds(aBuilder, &snap)) ||
       !geometry->mBorderRect.IsEqualInterior(GetBorderRect()) ||
       mOpacity != geometry->mOpacity) {
     nsRegion oldShadow, newShadow;
@@ -5627,34 +5677,35 @@ nsDisplayWrapList::nsDisplayWrapList(nsD
   : nsDisplayItem(aBuilder, aFrame, aActiveScrolledRoot)
   , mOverrideZIndex(0)
   , mHasZIndexOverride(false)
 {
   MOZ_COUNT_CTOR(nsDisplayWrapList);
 
   mBaseVisibleRect = mVisibleRect;
 
-  mList.AppendToTop(aList);
+  mListPtr = &mList;
+  mListPtr->AppendToTop(aList);
   UpdateBounds(aBuilder);
 
   if (!aFrame || !aFrame->IsTransformed()) {
     return;
   }
 
   // If we're a transformed frame, then we need to find out if we're inside
   // the nsDisplayTransform or outside of it. Frames inside the transform
   // need mReferenceFrame == mFrame, outside needs the next ancestor
   // reference frame.
   // If we're inside the transform, then the nsDisplayItem constructor
   // will have done the right thing.
   // If we're outside the transform, then we should have only one child
   // (since nsDisplayTransform wraps all actual content), and that child
   // will have the correct reference frame set (since nsDisplayTransform
   // handles this explictly).
-  nsDisplayItem *i = mList.GetBottom();
+  nsDisplayItem *i = mListPtr->GetBottom();
   if (i && (!i->GetAbove() || i->GetType() == DisplayItemType::TYPE_TRANSFORM) &&
       i->Frame() == mFrame) {
     mReferenceFrame = i->ReferenceFrame();
     mToReferenceFrame = i->ToReferenceFrame();
   }
   mVisibleRect = aBuilder->GetDirtyRect() +
       aBuilder->GetCurrentFrameOffsetToReferenceFrame();
 }
@@ -5664,17 +5715,18 @@ nsDisplayWrapList::nsDisplayWrapList(nsD
   : nsDisplayItem(aBuilder, aFrame)
   , mOverrideZIndex(0)
   , mHasZIndexOverride(false)
 {
   MOZ_COUNT_CTOR(nsDisplayWrapList);
 
   mBaseVisibleRect = mVisibleRect;
 
-  mList.AppendToTop(aItem);
+  mListPtr = &mList;
+  mListPtr->AppendToTop(aItem);
   UpdateBounds(aBuilder);
 
   if (!aFrame || !aFrame->IsTransformed()) {
     return;
   }
 
   // See the previous nsDisplayWrapList constructor
   if (aItem->Frame() == aFrame) {
@@ -5685,63 +5737,86 @@ nsDisplayWrapList::nsDisplayWrapList(nsD
       aBuilder->GetCurrentFrameOffsetToReferenceFrame();
 }
 
 nsDisplayWrapList::~nsDisplayWrapList() {
   MOZ_COUNT_DTOR(nsDisplayWrapList);
 }
 
 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(*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);
+}
+
+void
 nsDisplayWrapList::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                            HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) {
-  mList.HitTest(aBuilder, aRect, aState, aOutFrames);
+  mListPtr->HitTest(aBuilder, aRect, aState, aOutFrames);
 }
 
 nsRect
-nsDisplayWrapList::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) {
+nsDisplayWrapList::GetBounds(nsDisplayListBuilder* aBuilder,
+                             bool* aSnap) const
+{
   *aSnap = false;
   return mBounds;
 }
 
 bool
 nsDisplayWrapList::ComputeVisibility(nsDisplayListBuilder* aBuilder,
                                      nsRegion* aVisibleRegion) {
   // Convert the passed in visible region to our appunits.
   nsRegion visibleRegion;
   // mVisibleRect has been clipped to GetClippedBounds
   visibleRegion.And(*aVisibleRegion, mVisibleRect);
   nsRegion originalVisibleRegion = visibleRegion;
 
   bool retval =
-    mList.ComputeVisibilityForSublist(aBuilder, &visibleRegion, mVisibleRect);
-
+    mListPtr->ComputeVisibilityForSublist(aBuilder,
+                                          &visibleRegion,
+                                          mVisibleRect);
   nsRegion removed;
   // removed = originalVisibleRegion - visibleRegion
   removed.Sub(originalVisibleRegion, visibleRegion);
   // aVisibleRegion = aVisibleRegion - removed (modulo any simplifications
   // SubtractFromVisibleRegion does)
   aBuilder->SubtractFromVisibleRegion(aVisibleRegion, removed);
 
   return retval;
 }
 
 nsRegion
 nsDisplayWrapList::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
-                                   bool* aSnap) {
+                                   bool* aSnap) const
+{
   *aSnap = false;
   nsRegion result;
-  if (mList.IsOpaque()) {
+  if (mListPtr->IsOpaque()) {
     // Everything within GetBounds that's visible is opaque.
     result = GetBounds(aBuilder, aSnap);
   }
   return result;
 }
 
 Maybe<nscolor>
-nsDisplayWrapList::IsUniform(nsDisplayListBuilder* aBuilder) {
+nsDisplayWrapList::IsUniform(nsDisplayListBuilder* aBuilder) const
+{
   // We could try to do something but let's conservatively just return Nothing.
   return Nothing();
 }
 
 void nsDisplayWrapList::Paint(nsDisplayListBuilder* aBuilder,
                               gfxContext* aCtx) {
   NS_ERROR("nsDisplayWrapList should have been flattened away for painting");
 }
@@ -5792,20 +5867,20 @@ RequiredLayerStateForChildren(nsDisplayL
           result = childState;
         }
       }
     }
   }
   return result;
 }
 
-nsRect nsDisplayWrapList::GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder)
+nsRect nsDisplayWrapList::GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const
 {
   nsRect bounds;
-  for (nsDisplayItem* i = mList.GetBottom(); i; i = i->GetAbove()) {
+  for (nsDisplayItem* i = mListPtr->GetBottom(); i; i = i->GetAbove()) {
     bounds.UnionRect(bounds, i->GetComponentAlphaBounds(aBuilder));
   }
   return bounds;
 }
 
 void
 nsDisplayWrapList::SetVisibleRect(const nsRect& aRect)
 {
@@ -5923,17 +5998,18 @@ nsDisplayOpacity::nsDisplayOpacity(nsDis
 
 #ifdef NS_BUILD_REFCNT_LOGGING
 nsDisplayOpacity::~nsDisplayOpacity() {
   MOZ_COUNT_DTOR(nsDisplayOpacity);
 }
 #endif
 
 nsRegion nsDisplayOpacity::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
-                                           bool* aSnap) {
+                                           bool* aSnap) const
+{
   *aSnap = false;
   // The only time where mOpacity == 1.0 should be when we have will-change.
   // We could report this as opaque then but when the will-change value starts
   // animating the element would become non opaque and could cause repaints.
   return nsRegion();
 }
 
 // nsDisplayOpacity uses layers for rendering
@@ -5996,74 +6072,121 @@ nsDisplayOpacity::ApplyOpacity(nsDisplay
 }
 
 bool
 nsDisplayOpacity::CanApplyOpacity() const
 {
   return true;
 }
 
+/**
+ * Recursively iterates through |aList| and collects at most |aMaxChildCount|
+ * display item pointers to items that return true for CanApplyOpacity().
+ * The item pointers are added to |aArray|.
+ *
+ * LayerEventRegions and WrapList items are ignored.
+ *
+ * We need to do this recursively, because the child display items might contain
+ * nested nsDisplayWrapLists.
+ *
+ * Returns false if there are more than |aMaxChildCount| items, or if an item
+ * that returns false for CanApplyOpacity() is encountered.
+ * Otherwise returns true.
+ */
+static bool
+CollectItemsWithOpacity(nsDisplayList* aList,
+                     nsTArray<nsDisplayItem*>& aArray,
+                     const size_t aMaxChildCount)
+{
+  for (nsDisplayItem* i = aList->GetBottom(); i; i = i->GetAbove()) {
+    DisplayItemType type = i->GetType();
+    nsDisplayList* children = i->GetChildren();
+
+    // Descend only into wraplists.
+    if (type == DisplayItemType::TYPE_WRAP_LIST && children) {
+      // The current display item has children, process them first.
+      if (!CollectItemsWithOpacity(children, aArray, aMaxChildCount)) {
+        return false;
+      }
+    }
+
+    if (type == DisplayItemType::TYPE_LAYER_EVENT_REGIONS ||
+        type == DisplayItemType::TYPE_WRAP_LIST) {
+      continue;
+    }
+
+    if (!i->CanApplyOpacity() || aArray.Length() == aMaxChildCount) {
+      return false;
+    }
+
+    aArray.AppendElement(i);
+  }
+
+  return true;
+}
+
 bool
 nsDisplayOpacity::ShouldFlattenAway(nsDisplayListBuilder* aBuilder)
 {
   if (NeedsActiveLayer(aBuilder, mFrame) || mOpacity == 0.0) {
     // If our opacity is zero then we'll discard all descendant display items
     // except for layer event regions, so there's no point in doing this
     // optimization (and if we do do it, then invalidations of those descendants
     // might trigger repainting).
     return false;
   }
 
-  nsDisplayItem* child = mList.GetBottom();
-  // Only try folding our opacity down if we have at most three children
-  // that don't overlap and can all apply the opacity to themselves.
-  if (!child) {
+  if (mList.IsEmpty()) {
     return false;
   }
+
+  // Only try folding our opacity down if we have at most kMaxChildCount
+  // children that don't overlap and can all apply the opacity to themselves.
+  static const size_t kMaxChildCount = 3;
+
+  // Iterate through the child display list and copy at most kMaxChildCount
+  // child display item pointers to a temporary list.
+  AutoTArray<nsDisplayItem*, kMaxChildCount> items;
+  if (!CollectItemsWithOpacity(&mList, items, kMaxChildCount)) {
+    return false;
+  }
+
   struct {
     nsDisplayItem* item;
     nsRect bounds;
-  } children[3];
+  } children[kMaxChildCount];
+
   bool snap;
-  uint32_t numChildren = 0;
-  for (; numChildren < ArrayLength(children) && child; numChildren++, child = child->GetAbove()) {
-    if (child->GetType() == DisplayItemType::TYPE_LAYER_EVENT_REGIONS) {
-      numChildren--;
-      continue;
-    }
-    if (!child->CanApplyOpacity()) {
-      return false;
-    }
-    children[numChildren].item = child;
-    children[numChildren].bounds = child->GetBounds(aBuilder, &snap);
-  }
-  if (child) {
-    // we have a fourth (or more) child
-    return false;
-  }
-
-  for (uint32_t i = 0; i < numChildren; i++) {
-    for (uint32_t j = i+1; j < numChildren; j++) {
+  size_t childCount = 0;
+  for (nsDisplayItem* item : items) {
+    children[childCount].item = item;
+    children[childCount].bounds = item->GetBounds(aBuilder, &snap);
+    childCount++;
+  }
+
+  for (size_t i = 0; i < childCount; i++) {
+    for (size_t j = i+1; j < childCount; j++) {
       if (children[i].bounds.Intersects(children[j].bounds)) {
         return false;
       }
     }
   }
 
   // When intersecting the children's clip, only intersect with the clip for
   // our ASR and not with the whole clip chain, because the rest of the clip
   // chain is usually already set on the children. In fact, opacity items
   // usually never have their own clip because during display item creation
   // time we propagated the clip to our contents, so maybe we should just
   // remove the clip parameter from ApplyOpacity completely.
-  DisplayItemClipChain clip = { GetClip(), mActiveScrolledRoot, nullptr };
-
-  for (uint32_t i = 0; i < numChildren; i++) {
+  DisplayItemClipChain clip { GetClip(), mActiveScrolledRoot, nullptr };
+
+  for (uint32_t i = 0; i < childCount; i++) {
     children[i].item->ApplyOpacity(aBuilder, mOpacity, mClip ? &clip : nullptr);
   }
+
   return true;
 }
 
 nsDisplayItem::LayerState
 nsDisplayOpacity::GetLayerState(nsDisplayListBuilder* aBuilder,
                                 LayerManager* aManager,
                                 const ContainerLayerParameters& aParameters) {
   // If we only created this item so that we'd get correct nsDisplayEventRegions for child
@@ -6093,30 +6216,16 @@ nsDisplayOpacity::ComputeVisibility(nsDi
   // the temporary compositing buffer.
   nsRect bounds = GetClippedBounds(aBuilder);
   nsRegion visibleUnderChildren;
   visibleUnderChildren.And(*aVisibleRegion, bounds);
   return
     nsDisplayWrapList::ComputeVisibility(aBuilder, &visibleUnderChildren);
 }
 
-bool nsDisplayOpacity::TryMerge(nsDisplayItem* aItem) {
-  if (aItem->GetType() != DisplayItemType::TYPE_OPACITY)
-    return false;
-  // items for the same content element should be merged into a single
-  // compositing group
-  // aItem->GetUnderlyingFrame() returns non-null because it's nsDisplayOpacity
-  if (aItem->Frame()->GetContent() != mFrame->GetContent())
-    return false;
-  if (aItem->GetClipChain() != GetClipChain())
-    return false;
-  MergeFromTrackingMergedFrames(static_cast<nsDisplayOpacity*>(aItem));
-  return true;
-}
-
 void
 nsDisplayOpacity::WriteDebugInfo(std::stringstream& aStream)
 {
   aStream << " (opacity " << mOpacity << ")";
 }
 
 bool
 nsDisplayOpacity::CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
@@ -6180,17 +6289,18 @@ nsDisplayBlendMode::nsDisplayBlendMode(n
 
 #ifdef NS_BUILD_REFCNT_LOGGING
 nsDisplayBlendMode::~nsDisplayBlendMode() {
   MOZ_COUNT_DTOR(nsDisplayBlendMode);
 }
 #endif
 
 nsRegion nsDisplayBlendMode::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
-                                                bool* aSnap) {
+                                                bool* aSnap) const
+{
   *aSnap = false;
   // We are never considered opaque
   return nsRegion();
 }
 
 LayerState
 nsDisplayBlendMode::GetLayerState(nsDisplayListBuilder* aBuilder,
                                      LayerManager* aManager,
@@ -6230,42 +6340,47 @@ nsDisplayBlendMode::BuildLayer(nsDisplay
     return nullptr;
   }
 
   container->SetMixBlendMode(nsCSSRendering::GetGFXBlendMode(mBlendMode));
 
   return container.forget();
 }
 
-bool nsDisplayBlendMode::ComputeVisibility(nsDisplayListBuilder* aBuilder,
+bool
+nsDisplayBlendMode::ComputeVisibility(nsDisplayListBuilder* aBuilder,
                                               nsRegion* aVisibleRegion) {
   // Our children are need their backdrop so we should not allow them to subtract
   // area from aVisibleRegion. We do need to find out what is visible under
   // our children in the temporary compositing buffer, because if our children
   // paint our entire bounds opaquely then we don't need an alpha channel in
   // the temporary compositing buffer.
   nsRect bounds = GetClippedBounds(aBuilder);
   nsRegion visibleUnderChildren;
   visibleUnderChildren.And(*aVisibleRegion, bounds);
   return nsDisplayWrapList::ComputeVisibility(aBuilder, &visibleUnderChildren);
 }
 
-bool nsDisplayBlendMode::TryMerge(nsDisplayItem* aItem) {
-  if (aItem->GetType() != DisplayItemType::TYPE_BLEND_MODE)
+bool
+nsDisplayBlendMode::CanMerge(const nsDisplayItem* aItem) const
+{
+  // Items for the same content element should be merged into a single
+  // compositing group.
+  if (!HasSameTypeAndClip(aItem) || !HasSameContent(aItem)) {
     return false;
-  nsDisplayBlendMode* item = static_cast<nsDisplayBlendMode*>(aItem);
-  // items for the same content element should be merged into a single
-  // compositing group
-  if (item->Frame()->GetContent() != mFrame->GetContent())
+  }
+
+  const nsDisplayBlendMode* item =
+    static_cast<const nsDisplayBlendMode*>(aItem);
+
+  if (item->mIndex != 0 || mIndex != 0) {
+    // Don't merge background-blend-mode items
     return false;
-  if (item->mIndex != 0 || mIndex != 0)
-    return false; // don't merge background-blend-mode items
-  if (item->GetClipChain() != GetClipChain())
-    return false;
-  MergeFromTrackingMergedFrames(item);
+  }
+
   return true;
 }
 
 /* static */ nsDisplayBlendContainer*
 nsDisplayBlendContainer::CreateForMixBlendMode(nsDisplayListBuilder* aBuilder,
                                                nsIFrame* aFrame, nsDisplayList* aList,
                                                const ActiveScrolledRoot* aActiveScrolledRoot)
 {
@@ -6334,30 +6449,16 @@ nsDisplayBlendContainer::CreateWebRender
 {
   StackingContextHelper sc(aSc, aBuilder, aDisplayListBuilder, this,
                            &mList, nullptr, 0, nullptr, nullptr);
 
   return nsDisplayWrapList::CreateWebRenderCommands(aBuilder, sc, aParentCommands,
                                                     aManager, aDisplayListBuilder);
 }
 
-bool nsDisplayBlendContainer::TryMerge(nsDisplayItem* aItem) {
-  if (aItem->GetType() != DisplayItemType::TYPE_BLEND_CONTAINER)
-    return false;
-  // items for the same content element should be merged into a single
-  // compositing group
-  // aItem->GetUnderlyingFrame() returns non-null because it's nsDisplayOpacity
-  if (aItem->Frame()->GetContent() != mFrame->GetContent())
-    return false;
-  if (aItem->GetClipChain() != GetClipChain())
-    return false;
-  MergeFromTrackingMergedFrames(static_cast<nsDisplayBlendContainer*>(aItem));
-  return true;
-}
-
 nsDisplayOwnLayer::nsDisplayOwnLayer(nsDisplayListBuilder* aBuilder,
                                      nsIFrame* aFrame, nsDisplayList* aList,
                                      const ActiveScrolledRoot* aActiveScrolledRoot,
                                      uint32_t aFlags, ViewID aScrollTarget,
                                      const ScrollThumbData& aThumbData,
                                      bool aForceActive)
     : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot)
     , mFlags(aFlags)
@@ -6397,17 +6498,17 @@ nsDisplayOwnLayer::GetLayerState(nsDispl
 
 bool
 nsDisplayOwnLayer::IsScrollThumbLayer() const
 {
   return (mFlags & VERTICAL_SCROLLBAR) || (mFlags & HORIZONTAL_SCROLLBAR);
 }
 
 bool
-nsDisplayOwnLayer::ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder)
+nsDisplayOwnLayer::ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder) const
 {
   // Render scroll thumb layers even if they are invisible, because async
   // scrolling might bring them into view.
   return IsScrollThumbLayer();
 }
 
 // nsDisplayOpacity uses layers for rendering
 already_AddRefed<Layer>
@@ -6551,17 +6652,18 @@ nsDisplaySubDocument::ComputeScrollMetad
 static bool
 UseDisplayPortForViewport(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
 {
   return aBuilder->IsPaintingToWindow() &&
       nsLayoutUtils::ViewportHasDisplayPort(aFrame->PresContext());
 }
 
 nsRect
-nsDisplaySubDocument::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
+nsDisplaySubDocument::GetBounds(nsDisplayListBuilder* aBuilder,
+                                bool* aSnap) const
 {
   bool usingDisplayPort = UseDisplayPortForViewport(aBuilder, mFrame);
 
   if ((mFlags & GENERATE_SCROLLABLE_LAYER) && usingDisplayPort) {
     *aSnap = false;
     return mFrame->GetRect() + aBuilder->ToReferenceFrame(mFrame);
   }
 
@@ -6607,29 +6709,30 @@ nsDisplaySubDocument::ComputeVisibility(
 
     aBuilder->SubtractFromVisibleRegion(aVisibleRegion, removed);
   }
 
   return visible;
 }
 
 bool
-nsDisplaySubDocument::ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder)
+nsDisplaySubDocument::ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder) const
 {
   bool usingDisplayPort = UseDisplayPortForViewport(aBuilder, mFrame);
 
   if ((mFlags & GENERATE_SCROLLABLE_LAYER) && usingDisplayPort) {
     return true;
   }
 
   return nsDisplayOwnLayer::ShouldBuildLayerEvenIfInvisible(aBuilder);
 }
 
 nsRegion
-nsDisplaySubDocument::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, bool* aSnap)
+nsDisplaySubDocument::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+                                      bool* aSnap) const
 {
   bool usingDisplayPort = UseDisplayPortForViewport(aBuilder, mFrame);
 
   if ((mFlags & GENERATE_SCROLLABLE_LAYER) && usingDisplayPort) {
     *aSnap = false;
     return nsRegion();
   }
 
@@ -6764,29 +6867,16 @@ nsDisplayFixedPosition::BuildLayer(nsDis
   anchorRect.MoveTo(viewportFrame->GetOffsetToCrossDoc(ReferenceFrame()));
 
   nsLayoutUtils::SetFixedPositionLayerData(layer,
       viewportFrame, anchorRect, fixedFrame, presContext, aContainerParameters);
 
   return layer.forget();
 }
 
-bool nsDisplayFixedPosition::TryMerge(nsDisplayItem* aItem) {
-  if (aItem->GetType() != DisplayItemType::TYPE_FIXED_POSITION)
-    return false;
-  // Items with the same fixed position frame can be merged.
-  nsDisplayFixedPosition* other = static_cast<nsDisplayFixedPosition*>(aItem);
-  if (other->mFrame != mFrame)
-    return false;
-  if (aItem->GetClipChain() != GetClipChain())
-    return false;
-  MergeFromTrackingMergedFrames(other);
-  return true;
-}
-
 bool
 nsDisplayFixedPosition::UpdateScrollData(mozilla::layers::WebRenderScrollData* aData,
                                          mozilla::layers::WebRenderLayerScrollData* aLayerData)
 {
   if (aLayerData) {
     FrameMetrics::ViewID id = nsLayoutUtils::ScrollIdForRootScrollFrame(
         Frame()->PresContext());
     aLayerData->SetFixedPositionScrollContainerId(id);
@@ -6923,29 +7013,16 @@ nsDisplayStickyPosition::BuildLayer(nsDi
                           aContainerParameters.mXScale,
                         NSAppUnitsToFloatPixels(inner.height, factor) *
                           aContainerParameters.mYScale);
   layer->SetStickyPositionData(scrollId, stickyOuter, stickyInner);
 
   return layer.forget();
 }
 
-bool nsDisplayStickyPosition::TryMerge(nsDisplayItem* aItem) {
-  if (aItem->GetType() != DisplayItemType::TYPE_STICKY_POSITION)
-    return false;
-  // Items with the same fixed position frame can be merged.
-  nsDisplayStickyPosition* other = static_cast<nsDisplayStickyPosition*>(aItem);
-  if (other->mFrame != mFrame)
-    return false;
-  if (aItem->GetClipChain() != GetClipChain())
-    return false;
-  MergeFromTrackingMergedFrames(other);
-  return true;
-}
-
 nsDisplayScrollInfoLayer::nsDisplayScrollInfoLayer(
   nsDisplayListBuilder* aBuilder,
   nsIFrame* aScrolledFrame,
   nsIFrame* aScrollFrame)
   : nsDisplayWrapList(aBuilder, aScrollFrame)
   , mScrollFrame(aScrollFrame)
   , mScrolledFrame(aScrolledFrame)
   , mScrollParentId(aBuilder->GetCurrentScrollParentId())
@@ -7034,17 +7111,18 @@ nsDisplayZoom::nsDisplayZoom(nsDisplayLi
 }
 
 #ifdef NS_BUILD_REFCNT_LOGGING
 nsDisplayZoom::~nsDisplayZoom() {
   MOZ_COUNT_DTOR(nsDisplayZoom);
 }
 #endif
 
-nsRect nsDisplayZoom::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
+nsRect nsDisplayZoom::GetBounds(nsDisplayListBuilder* aBuilder,
+                                bool* aSnap) const
 {
   nsRect bounds = nsDisplaySubDocument::GetBounds(aBuilder, aSnap);
   *aSnap = false;
   return bounds.ScaleToOtherAppUnitsRoundOut(mAPD, mParentAPD);
 }
 
 void nsDisplayZoom::HitTest(nsDisplayListBuilder *aBuilder,
                             const nsRect& aRect,
@@ -7714,17 +7792,17 @@ static bool IsFrameVisible(nsIFrame* aFr
   }
   if (aFrame->BackfaceIsHidden() && aMatrix.IsBackfaceVisible()) {
     return false;
   }
   return true;
 }
 
 const Matrix4x4&
-nsDisplayTransform::GetTransform()
+nsDisplayTransform::GetTransform() const
 {
   if (mTransform.IsIdentity()) {
     float scale = mFrame->PresContext()->AppUnitsPerDevPixel();
     Point3D newOrigin =
       Point3D(NSAppUnitsToFloatPixels(mToReferenceFrame.x, scale),
               NSAppUnitsToFloatPixels(mToReferenceFrame.y, scale),
               0.0f);
     if (mTransformGetter) {
@@ -7782,17 +7860,17 @@ nsDisplayTransform::GetAccumulatedPreser
     uint32_t flags = INCLUDE_PRESERVE3D_ANCESTORS|INCLUDE_PERSPECTIVE|OFFSET_BY_ORIGIN;
     mTransformPreserves3D =
       GetResultingTransformMatrix(mFrame, offset, scale, flags);
   }
   return mTransformPreserves3D;
 }
 
 bool
-nsDisplayTransform::ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder)
+nsDisplayTransform::ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder) const
 {
   // The visible rect of a Preserves-3D frame is just an intermediate
   // result.  It should always build a layer to make sure it is
   // rendering correctly.
   return MayBeAnimated(aBuilder) || mFrame->Combines3DTransformWithAncestors();
 }
 
 bool
@@ -7929,17 +8007,17 @@ already_AddRefed<Layer> nsDisplayTransfo
   } else {
     container->RemoveUserData(nsIFrame::LayerIsPrerenderedDataKey());
     container->SetContentFlags(container->GetContentFlags() & ~Layer::CONTENT_MAY_CHANGE_TRANSFORM);
   }
   return container.forget();
 }
 
 bool
-nsDisplayTransform::MayBeAnimated(nsDisplayListBuilder* aBuilder)
+nsDisplayTransform::MayBeAnimated(nsDisplayListBuilder* aBuilder) const
 {
   // If EffectCompositor::HasAnimationsForCompositor() is true then we can
   // completely bypass the main thread for this animation, so it is always
   // worthwhile.
   // For ActiveLayerTracker::IsStyleAnimated() cases the main thread is
   // already involved so there is less to be gained.
   // Therefore we check that the *post-transform* bounds of this item are
   // big enough to justify an active layer.
@@ -8118,17 +8196,18 @@ nsDisplayTransform::GetHitDepthAtPoint(n
   Point3D transformed = matrix.TransformPoint(Point3D(point2d.x, point2d.y, 0));
   return transformed.z;
 }
 
 /* The bounding rectangle for the object is the overflow rectangle translated
  * by the reference point.
  */
 nsRect
-nsDisplayTransform::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
+nsDisplayTransform::GetBounds(nsDisplayListBuilder* aBuilder,
+                              bool* aSnap) const
 {
   *aSnap = false;
 
   if (mHasBounds) {
     return mBounds;
   }
 
   if (mFrame->Extend3DContext() && !mIsTransformSeparator) {
@@ -8194,18 +8273,19 @@ nsDisplayTransform::ComputeBounds(nsDisp
  * We need b and c to be zero.
  *
  * We also need to check whether the underlying opaque content completely fills
  * our visible rect. We use UntransformRect which expands to the axis-aligned
  * bounding rect, but that's OK since if
  * mStoredList.GetVisibleRect().Contains(untransformedVisible), then it
  * certainly contains the actual (non-axis-aligned) untransformed rect.
  */
-nsRegion nsDisplayTransform::GetOpaqueRegion(nsDisplayListBuilder *aBuilder,
-                                             bool* aSnap)
+nsRegion
+nsDisplayTransform::GetOpaqueRegion(nsDisplayListBuilder *aBuilder,
+                                             bool* aSnap) const
 {
   *aSnap = false;
   nsRect untransformedVisible;
   if (!UntransformVisibleRect(aBuilder, &untransformedVisible)) {
       return nsRegion();
   }
 
   const Matrix4x4& matrix = GetTransform();
@@ -8221,17 +8301,17 @@ nsRegion nsDisplayTransform::GetOpaqueRe
   return result;
 }
 
 /* The transform is uniform if it fills the entire bounding rect and the
  * wrapped list is uniform.  See GetOpaqueRegion for discussion of why this
  * works.
  */
 Maybe<nscolor>
-nsDisplayTransform::IsUniform(nsDisplayListBuilder *aBuilder)
+nsDisplayTransform::IsUniform(nsDisplayListBuilder *aBuilder) const
 {
   nsRect untransformedVisible;
   if (!UntransformVisibleRect(aBuilder, &untransformedVisible)) {
     return Nothing();
   }
   const Matrix4x4& matrix = GetTransform();
 
   Matrix matrix2d;
@@ -8239,55 +8319,16 @@ nsDisplayTransform::IsUniform(nsDisplayL
       matrix2d.PreservesAxisAlignedRectangles() &&
       mStoredList.GetVisibleRect().Contains(untransformedVisible)) {
     return mStoredList.IsUniform(aBuilder);
   }
 
   return Nothing();
 }
 
-/* If UNIFIED_CONTINUATIONS is defined, we can merge two display lists that
- * share the same underlying content.  Otherwise, doing so results in graphical
- * glitches.
- */
-#ifndef UNIFIED_CONTINUATIONS
-
-bool
-nsDisplayTransform::TryMerge(nsDisplayItem *aItem)
-{
-  return false;
-}
-
-#else
-
-bool
-nsDisplayTransform::TryMerge(nsDisplayItem *aItem)
-{
-  NS_PRECONDITION(aItem, "Why did you try merging with a null item?");
-
-  /* Make sure that we're dealing with two transforms. */
-  if (aItem->GetType() != TYPE_TRANSFORM)
-    return false;
-
-  /* Check to see that both frames are part of the same content. */
-  if (aItem->Frame()->GetContent() != mFrame->GetContent())
-    return false;
-
-  if (aItem->GetClipChain() != GetClipChain())
-    return false;
-
-  /* Now, move everything over to this frame and signal that
-   * we merged things!
-   */
-  mStoredList.MergeFromTrackingMergedFrames(&static_cast<nsDisplayTransform*>(aItem)->mStoredList);
-  return true;
-}
-
-#endif
-
 /* TransformRect takes in as parameters a rectangle (in app space) and returns
  * the smallest rectangle (in app space) containing the transformed image of
  * that rectangle.  That is, it takes the four corners of the rectangle,
  * transforms them according to the matrix associated with the specified frame,
  * then returns the smallest rectangle containing the four transformed points.
  *
  * @param aUntransformedBounds The rectangle (in app units) to transform.
  * @param aFrame The frame whose transformation should be applied.
@@ -8339,17 +8380,17 @@ bool nsDisplayTransform::UntransformRect
                             NSAppUnitsToFloatPixels(aChildBounds.height, factor));
 
   result = transform.Inverse().ProjectRectBounds(result, childGfxBounds);
   *aOutRect = nsLayoutUtils::RoundGfxRectToAppRect(ThebesRect(result), factor);
   return true;
 }
 
 bool nsDisplayTransform::UntransformVisibleRect(nsDisplayListBuilder* aBuilder,
-                                                nsRect *aOutRect)
+                                                nsRect *aOutRect) const
 {
   const Matrix4x4& matrix = GetTransform();
   if (matrix.IsSingular())
     return false;
 
   // GetTransform always operates in dev pixels.
   float factor = mFrame->PresContext()->AppUnitsPerDevPixel();
   RectDouble result(NSAppUnitsToFloatPixels(mVisibleRect.x, factor),
@@ -8530,17 +8571,17 @@ nsDisplayItemGeometry*
 nsCharClipDisplayItem::AllocateGeometry(nsDisplayListBuilder* aBuilder)
 {
   return new nsCharClipGeometry(this, aBuilder);
 }
 
 void
 nsCharClipDisplayItem::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion* aInvalidRegion)
+                                         nsRegion* aInvalidRegion) const
 {
   const nsCharClipGeometry* geometry = static_cast<const nsCharClipGeometry*>(aGeometry);
 
   bool snap;
   nsRect newRect = geometry->mBounds;
   nsRect oldRect = GetBounds(aBuilder, &snap);
   if (mVisIStartEdge != geometry->mVisIStartEdge ||
       mVisIEndEdge != geometry->mVisIEndEdge ||
@@ -8572,17 +8613,17 @@ nsDisplaySVGEffects::nsDisplaySVGEffects
 #ifdef NS_BUILD_REFCNT_LOGGING
 nsDisplaySVGEffects::~nsDisplaySVGEffects()
 {
   MOZ_COUNT_DTOR(nsDisplaySVGEffects);
 }
 #endif
 
 nsRegion nsDisplaySVGEffects::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
-                                              bool* aSnap)
+                                              bool* aSnap) const
 {
   *aSnap = false;
   return nsRegion();
 }
 
 void
 nsDisplaySVGEffects::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                              HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames)
@@ -8604,17 +8645,17 @@ gfxPoint
 nsDisplaySVGEffects::UserSpaceOffset() const
 {
   return nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(mFrame);
 }
 
 void
 nsDisplaySVGEffects::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                                const nsDisplayItemGeometry* aGeometry,
-                                               nsRegion* aInvalidRegion)
+                                               nsRegion* aInvalidRegion) const
 {
   const nsDisplaySVGEffectGeometry* geometry =
     static_cast<const nsDisplaySVGEffectGeometry*>(aGeometry);
   bool snap;
   nsRect bounds = GetBounds(aBuilder, &snap);
   if (geometry->mFrameOffsetToReferenceFrame != ToReferenceFrame() ||
       geometry->mUserSpaceOffset != UserSpaceOffset() ||
       !geometry->mBBox.IsEqualInterior(BBoxInUserSpace())) {
@@ -8793,46 +8834,27 @@ nsDisplayMask::nsDisplayMask(nsDisplayLi
 
 #ifdef NS_BUILD_REFCNT_LOGGING
 nsDisplayMask::~nsDisplayMask()
 {
   MOZ_COUNT_DTOR(nsDisplayMask);
 }
 #endif
 
-bool nsDisplayMask::TryMerge(nsDisplayItem* aItem)
-{
-  if (aItem->GetType() != DisplayItemType::TYPE_MASK)
-    return false;
-
-  // Do not merge items for box-decoration-break:clone elements,
-  // since each box should have its own mask in that case.
-  if (mFrame->StyleBorder()->mBoxDecorationBreak == StyleBoxDecorationBreak::Clone) {
-    return false;
-  }
-
-  // items for the same content element should be merged into a single
-  // compositing group
-  // aItem->GetUnderlyingFrame() returns non-null because it's nsDisplaySVGEffects
-  if (aItem->Frame()->GetContent() != mFrame->GetContent()) {
-    return false;
-  }
-  if (aItem->GetClipChain() != GetClipChain()) {
-    return false;
-  }
-
+bool
+nsDisplayMask::CanMerge(const nsDisplayItem* aItem) const
+{
+  // Items for the same content element should be merged into a single
+  // compositing group.
   // Do not merge if mFrame has mask. Continuation frames should apply mask
-  // independently(just like nsDisplayBackgroundImage).
-  if (mFrame->StyleSVGReset()->HasMask()) {
-    return false;
-  }
-
-  MergeFromTrackingMergedFrames(static_cast<nsDisplayMask*>(aItem));
-
-  return true;
+  // independently (just like nsDisplayBackgroundImage).
+  return HasSameTypeAndClip(aItem) && HasSameContent(aItem) &&
+         !mFrame->StyleSVGReset()->HasMask() &&
+         (mFrame->StyleBorder()->mBoxDecorationBreak !=
+            mozilla::StyleBoxDecorationBreak::Clone);
 }
 
 already_AddRefed<Layer>
 nsDisplayMask::BuildLayer(nsDisplayListBuilder* aBuilder,
                           LayerManager* aManager,
                           const ContainerLayerParameters& aContainerParameters)
 {
   if (!ValidateSVGFrame()) {
@@ -8923,17 +8945,17 @@ bool nsDisplayMask::ComputeVisibility(ns
     mList.GetClippedBoundsWithRespectToASR(aBuilder, mActiveScrolledRoot));
   mList.ComputeVisibilityForSublist(aBuilder, &childrenVisible, r);
   return true;
 }
 
 void
 nsDisplayMask::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion* aInvalidRegion)
+                                         nsRegion* aInvalidRegion) const
 {
   nsDisplaySVGEffects::ComputeInvalidationRegion(aBuilder, aGeometry,
                                                  aInvalidRegion);
 
   const nsDisplayMaskGeometry* geometry =
     static_cast<const nsDisplayMaskGeometry*>(aGeometry);
   bool snap;
   nsRect bounds = GetBounds(aBuilder, &snap);
@@ -9128,40 +9150,16 @@ nsDisplayFilter::BuildLayer(nsDisplayLis
     }
 
     container->SetFilterChain(Move(cssFilters));
   }
 
   return container.forget();
 }
 
-bool nsDisplayFilter::TryMerge(nsDisplayItem* aItem)
-{
-  if (aItem->GetType() != DisplayItemType::TYPE_FILTER) {
-    return false;
-  }
-
-  // items for the same content element should be merged into a single
-  // compositing group.
-  // aItem->Frame() returns non-null because it's nsDisplayFilter
-  if (aItem->Frame()->GetContent() != mFrame->GetContent()) {
-    return false;
-  }
-  if (aItem->GetClipChain() != GetClipChain()) {
-    return false;
-  }
-
-  nsDisplayFilter* other = static_cast<nsDisplayFilter*>(aItem);
-  MergeFromTrackingMergedFrames(other);
-  mEffectsBounds.UnionRect(mEffectsBounds,
-    other->mEffectsBounds + other->mFrame->GetOffsetTo(mFrame));
-
-  return true;
-}
-
 LayerState
 nsDisplayFilter::GetLayerState(nsDisplayListBuilder* aBuilder,
                                LayerManager* aManager,
                                const ContainerLayerParameters& aParameters)
 {
   if (mFrame->IsFrameOfType(nsIFrame::eSVG)) {
     return LAYER_SVG_EFFECTS;
   }
@@ -9185,18 +9183,19 @@ nsDisplayFilter::GetLayerState(nsDisplay
         filter.GetType() != NS_STYLE_FILTER_SEPIA) {
       return LAYER_SVG_EFFECTS;
     }
   }
 
   return LAYER_ACTIVE;
 }
 
-bool nsDisplayFilter::ComputeVisibility(nsDisplayListBuilder* aBuilder,
-                                        nsRegion* aVisibleRegion)
+bool
+nsDisplayFilter::ComputeVisibility(nsDisplayListBuilder* aBuilder,
+                                   nsRegion* aVisibleRegion)
 {
   nsPoint offset = ToReferenceFrame();
   nsRect dirtyRect =
     nsSVGIntegrationUtils::GetRequiredSourceForInvalidArea(mFrame,
                                                            mVisibleRect - offset) +
     offset;
 
   // Our children may be made translucent or arbitrarily deformed so we should
@@ -9206,17 +9205,17 @@ bool nsDisplayFilter::ComputeVisibility(
     mList.GetClippedBoundsWithRespectToASR(aBuilder, mActiveScrolledRoot));
   mList.ComputeVisibilityForSublist(aBuilder, &childrenVisible, r);
   return true;
 }
 
 void
 nsDisplayFilter::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                            const nsDisplayItemGeometry* aGeometry,
-                                           nsRegion* aInvalidRegion)
+                                           nsRegion* aInvalidRegion) const
 {
   nsDisplaySVGEffects::ComputeInvalidationRegion(aBuilder, aGeometry,
                                                  aInvalidRegion);
 
   const nsDisplayFilterGeometry* geometry =
     static_cast<const nsDisplayFilterGeometry*>(aGeometry);
 
   if (aBuilder->ShouldSyncDecodeImages() &&
--- a/layout/painting/nsDisplayList.h
+++ b/layout/painting/nsDisplayList.h
@@ -323,16 +323,21 @@ public:
   nsDisplayListBuilder(nsIFrame* aReferenceFrame,
                        nsDisplayListBuilderMode aMode,
                        bool aBuildCaret);
   ~nsDisplayListBuilder();
 
   void BeginFrame();
   void EndFrame();
 
+  void AddTemporaryItem(nsDisplayItem* aItem)
+  {
+    mTemporaryItems.AppendElement(aItem);
+  }
+
   void SetWillComputePluginGeometry(bool aWillComputePluginGeometry)
   {
     mWillComputePluginGeometry = aWillComputePluginGeometry;
   }
   void SetForPluginGeometry(bool aForPlugin)
   {
     if (aForPlugin) {
       NS_ASSERTION(mMode == nsDisplayListBuilderMode::PAINTING, "Can only switch from PAINTING to PLUGIN_GEOMETRY");
@@ -810,16 +815,23 @@ public:
    */
   static void AddAnimationsAndTransitionsToLayer(Layer* aLayer,
                                                  nsDisplayListBuilder* aBuilder,
                                                  nsDisplayItem* aItem,
                                                  nsIFrame* aFrame,
                                                  nsCSSPropertyID aProperty);
 
   /**
+   * Merges the display items in |aMergedItems| and returns a new temporary
+   * display item.
+   * The display items in |aMergedItems| have to be mergeable with each other.
+   */
+  nsDisplayItem* MergeItems(nsTArray<nsDisplayItem*>& aMergedItems);
+
+  /**
    * A helper class to temporarily set the value of
    * mIsAtRootOfPseudoStackingContext, and temporarily
    * set mCurrentFrame and related state. Also temporarily sets mDirtyRect.
    * aDirtyRect is relative to aForChild.
    */
   class AutoBuildingDisplayList;
   friend class AutoBuildingDisplayList;
   class AutoBuildingDisplayList {
@@ -1539,16 +1551,17 @@ private:
   // A temporary list that we append scroll info items to while building
   // display items for the contents of frames with SVG effects.
   // Only non-null when ShouldBuildScrollInfoItemsForHoisting() is true.
   // This is a pointer and not a real nsDisplayList value because the
   // nsDisplayList class is defined below this class, so we can't use it here.
   nsDisplayList*                 mScrollInfoItemsForHoisting;
   nsTArray<ActiveScrolledRoot*>  mActiveScrolledRoots;
   nsTArray<DisplayItemClipChain*> mClipChainsToDestroy;
+  nsTArray<nsDisplayItem*> mTemporaryItems;
   const ActiveScrolledRoot*      mActiveScrolledRootForRootScrollframe;
   nsDisplayListBuilderMode       mMode;
   ViewID                         mCurrentScrollParentId;
   ViewID                         mCurrentScrollbarTarget;
   uint32_t                       mCurrentScrollbarFlags;
   Preserves3DContext             mPreserves3DCtx;
   uint32_t                       mPerspectiveItemIndex;
   int32_t                        mSVGEffectsBuildingDepth;
@@ -1592,21 +1605,24 @@ class nsDisplayList;
  * nsDisplayItemLink holds the link. The lists are linked from lowest to
  * highest in z-order.
  */
 class nsDisplayItemLink {
   // This is never instantiated directly, so no need to count constructors and
   // destructors.
 protected:
   nsDisplayItemLink() : mAbove(nullptr) {}
+  nsDisplayItemLink(const nsDisplayItemLink&) : mAbove(nullptr) {}
   nsDisplayItem* mAbove;
 
   friend class nsDisplayList;
 };
 
+class nsDisplayWrapList;
+
 /**
  * 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.
  *
@@ -1648,18 +1664,18 @@ public:
    * temporary items.
    */
   explicit nsDisplayItem(nsIFrame* aFrame)
     : mFrame(aFrame)
     , mClipChain(nullptr)
     , mClip(nullptr)
     , mActiveScrolledRoot(nullptr)
     , mReferenceFrame(nullptr)
-    , mAnimatedGeometryRoot(nullptr)
     , mForceNotVisible(false)
+    , mDisableSubpixelAA(false)
 #ifdef MOZ_DUMP_PAINTING
     , mPainted(false)
 #endif
   {
   }
 protected:
   virtual ~nsDisplayItem() {
   }
@@ -1667,16 +1683,48 @@ public:
 
   virtual void Destroy(nsDisplayListBuilder* aBuilder)
   {
     DisplayItemType type = GetType();
     this->~nsDisplayItem();
     aBuilder->Destroy(type, this);
   }
 
+  /**
+   * Downcasts this item to nsDisplayWrapList, if possible.
+   */
+  virtual const nsDisplayWrapList* AsDisplayWrapList() const { return nullptr; }
+
+  /**
+   * Create a clone of this item.
+   */
+  virtual nsDisplayItem* Clone(nsDisplayListBuilder* aBuilder) const
+  {
+    return nullptr;
+  }
+
+  /**
+   * The custom copy-constructor is implemented to prevent copying the saved
+   * state of the item.
+   * This is currently only used when creating temporary items for merging.
+   */
+  nsDisplayItem(const nsDisplayItem& aOther)
+    : mFrame(aOther.mFrame)
+    , mClipChain(aOther.mClipChain)
+    , mClip(aOther.mClip)
+    , mActiveScrolledRoot(aOther.mActiveScrolledRoot)
+    , mReferenceFrame(aOther.mReferenceFrame)
+    , mAnimatedGeometryRoot(aOther.mAnimatedGeometryRoot)
+    , mToReferenceFrame(aOther.mToReferenceFrame)
+    , mVisibleRect(aOther.mVisibleRect)
+    , mForceNotVisible(aOther.mForceNotVisible)
+    , mDisableSubpixelAA(aOther.mDisableSubpixelAA)
+  {
+  }
+
   struct HitTestState {
     explicit HitTestState() : mInPreserves3D(false) {}
 
     ~HitTestState() {
       NS_ASSERTION(mItemBuffer.Length() == 0,
                    "mItemBuffer should have been cleared");
     }
 
@@ -1691,17 +1739,17 @@ public:
    * identify their type. We use the type for other purposes too.
    */
   virtual DisplayItemType GetType() const = 0;
   /**
    * Pairing this with the GetUnderlyingFrame() pointer gives a key that
    * uniquely identifies this display item in the display item tree.
    * XXX check nsOptionEventGrabberWrapper/nsXULEventRedirectorWrapper
    */
-  virtual uint32_t GetPerFrameKey() { return uint32_t(GetType()); }
+  virtual uint32_t GetPerFrameKey() const { return uint32_t(GetType()); }
 
   /**
    * This is called after we've constructed a display list for event handling.
    * When this is called, we've already ensured that aRect intersects the
    * item's bounds and that clipping has been taking into account.
    *
    * @param aRect the point or rect being tested, relative to the reference
    * frame. If the width and height are both 1 app unit, it indicates we're
@@ -1731,57 +1779,64 @@ public:
    * snapped to nearest device pixel edges during actual drawing.
    * It might be set to false and snap anyway, so code computing the set of
    * pixels affected by this display item needs to round outwards to pixel
    * boundaries when *aSnap is set to false.
    * This does not take the item's clipping into account.
    * @return a rectangle relative to aBuilder->ReferenceFrame() that
    * contains the area drawn by this display item
    */
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+                           bool* aSnap) const
   {
     *aSnap = false;
     return nsRect(ToReferenceFrame(), Frame()->GetSize());
   }
 
-  virtual nsRegion GetTightBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
+  virtual nsRegion GetTightBounds(nsDisplayListBuilder* aBuilder,
+                                  bool* aSnap) const
   {
     *aSnap = false;
     return nsRegion();
   }
 
   /**
    * Returns true if nothing will be rendered inside aRect, false if uncertain.
    * aRect is assumed to be contained in this item's bounds.
    */
-  virtual bool IsInvisibleInRect(const nsRect& aRect)
-  {
-    return false;
-  }
+  virtual bool IsInvisibleInRect(const nsRect& aRect) const { return false; }
+
   /**
    * Returns the result of GetBounds intersected with the item's clip.
    * The intersection is approximate since rounded corners are not taking into
    * account.
    */
-  nsRect GetClippedBounds(nsDisplayListBuilder* aBuilder);
-  nsRect GetBorderRect() {
+  nsRect GetClippedBounds(nsDisplayListBuilder* aBuilder) const;
+
+  nsRect GetBorderRect() const
+  {
     return nsRect(ToReferenceFrame(), Frame()->GetSize());
   }
-  nsRect GetPaddingRect() {
+
+  nsRect GetPaddingRect() const
+  {
     return Frame()->GetPaddingRectRelativeToSelf() + ToReferenceFrame();
   }
-  nsRect GetContentRect() {
+
+  nsRect GetContentRect() const
+  {
     return Frame()->GetContentRectRelativeToSelf() + ToReferenceFrame();
   }
 
   /**
    * Checks if the frame(s) owning this display item have been marked as invalid,
    * and needing repainting.
    */
-  virtual bool IsInvalid(nsRect& aRect) {
+  virtual bool IsInvalid(nsRect& aRect) const
+  {
     bool result = mFrame ? mFrame->IsInvalid(aRect) : false;
     aRect += ToReferenceFrame();
     return result;
   }
 
   /**
    * Creates and initializes an nsDisplayItemGeometry object that retains the current
    * areas covered by this display item. These need to retain enough information
@@ -1813,33 +1868,33 @@ public:
    *
    * @param aGeometry The geometry of the matching display item from the
    * previous paint.
    * @param aInvalidRegion Output param, the region to invalidate, or
    * unchanged if none.
    */
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion* aInvalidRegion)
+                                         nsRegion* aInvalidRegion) const
   {
     const nsDisplayItemGenericGeometry* geometry = static_cast<const nsDisplayItemGenericGeometry*>(aGeometry);
     bool snap;
     if (!geometry->mBounds.IsEqualInterior(GetBounds(aBuilder, &snap)) ||
         !geometry->mBorderRect.IsEqualInterior(GetBorderRect())) {
       aInvalidRegion->Or(GetBounds(aBuilder, &snap), geometry->mBounds);
     }
   }
 
   /**
    * An alternative default implementation of ComputeInvalidationRegion,
    * that instead invalidates only the changed area between the two items.
    */
   void ComputeInvalidationRegionDifference(nsDisplayListBuilder* aBuilder,
                                            const nsDisplayItemBoundsGeometry* aGeometry,
-                                           nsRegion* aInvalidRegion)
+                                           nsRegion* aInvalidRegion) const
   {
     bool snap;
     nsRect bounds = GetBounds(aBuilder, &snap);
 
     if (!aGeometry->mBounds.IsEqualInterior(bounds)) {
       nscoord radii[8];
       if (aGeometry->mHasRoundedCorners ||
           Frame()->GetBorderRadii(radii)) {
@@ -1853,52 +1908,61 @@ public:
   /**
    * Called when the area rendered by this display item has changed (been
    * invalidated or changed geometry) since the last paint. This includes
    * when the display item was not rendered at all in the last paint.
    * It does NOT get called when a display item was being rendered and no
    * longer is, because generally that means there is no display item to
    * call this method on.
    */
-  virtual void NotifyRenderingChanged() {}
+  virtual void NotifyRenderingChanged() const {}
 
   /**
    * @param aSnap set to true if the edges of the rectangles of the opaque
    * region would be snapped to device pixels when drawing
    * @return a region of the item that is opaque --- that is, every pixel
    * that is visible is painted with an opaque
    * color. This is useful for determining when one piece
    * of content completely obscures another so that we can do occlusion
    * culling.
    * This does not take clipping into account.
    */
   virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
-                                   bool* aSnap)
+                                   bool* aSnap) const
   {
     *aSnap = false;
     return nsRegion();
   }
   /**
    * @return Some(nscolor) if the item is guaranteed to paint every pixel in its
    * bounds with the same (possibly translucent) color
    */
-  virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder)
-  { return mozilla::Nothing(); }
+  virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) const
+  {
+    return mozilla::Nothing();
+  }
+
   /**
    * @return true if the contents of this item are rendered fixed relative
    * to the nearest viewport.
    */
-  virtual bool ShouldFixToViewport(nsDisplayListBuilder* aBuilder)
-  { return false; }
-
-  virtual bool ClearsBackground()
-  { return false; }
-
-  virtual bool ProvidesFontSmoothingBackgroundColor(nscolor* aColor)
-  { return false; }
+  virtual bool ShouldFixToViewport(nsDisplayListBuilder* aBuilder) const
+  {
+    return false;
+  }
+
+  virtual bool ClearsBackground() const
+  {
+    return false;
+  }
+
+  virtual bool ProvidesFontSmoothingBackgroundColor(nscolor* aColor) const
+  {
+    return false;
+  }
 
   /**
    * Returns true if all layers that can be active should be forced to be
    * active. Requires setting the pref layers.force-active=true.
    */
   static bool ForceActiveLayers();
 
   /**
@@ -1922,35 +1986,41 @@ public:
    * every time we paint.
    *
    * Users of GetLayerState should check ForceActiveLayers() and if it returns
    * true, change a returned value of LAYER_INACTIVE to LAYER_ACTIVE.
    */
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters)
-  { return mozilla::LAYER_NONE; }
+  {
+    return mozilla::LAYER_NONE;
+  }
+
   /**
    * Return true to indicate the layer should be constructed even if it's
    * completely invisible.
    */
-  virtual bool ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder)
-  { return false; }
+  virtual bool ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder) const
+  {
+    return false;
+  }
+
   /**
    * Actually paint this item to some rendering context.
    * Content outside mVisibleRect need not be painted.
    * aCtx must be set up as for nsDisplayList::Paint.
    */
   virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {}
 
 #ifdef MOZ_DUMP_PAINTING
   /**
    * Mark this display item as being painted via FrameLayerBuilder::DrawPaintedLayer.
    */
-  bool Painted() { return mPainted; }
+  bool Painted() const { return mPainted; }
 
   /**
    * Check if this display item has been painted.
    */
   void SetPainted() { mPainted = true; }
 #endif
 
   /**
@@ -1965,17 +2035,19 @@ public:
    *
    * @param aContainerParameters should be passed to
    * FrameLayerBuilder::BuildContainerLayerFor if a ContainerLayer is
    * constructed.
    */
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager,
                                              const ContainerLayerParameters& aContainerParameters)
-  { return nullptr; }
+  {
+    return nullptr;
+  }
 
   /**
    * Function to create the WebRenderCommands without
    * Layer. For layers mode, aManager->IsLayersFreeTransaction()
    * should be false to prevent doing GetLayerState again. For
    * layers-free mode, we should check if the layer state is
    * active first and have an early return if the layer state is
    * not active.
@@ -2034,34 +2106,43 @@ public:
    *
    * @return true if the item is visible, false if no part of the item
    * is visible.
    */
   virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
                                  nsRegion* aVisibleRegion);
 
   /**
+   * Checks if the given display item can be merged with this item.
+   * @return true if the merging is possible, otherwise false.
+   */
+  virtual bool CanMerge(const nsDisplayItem* aItem) const { return false; }
+
+  /**
    * Try to merge with the other item (which is below us in the display
    * list). This gets used by nsDisplayClip to coalesce clipping operations
    * (optimization), by nsDisplayOpacity to merge rendering for the same
    * content element into a single opacity group (correctness), and will be
    * used by nsDisplayOutline to merge multiple outlines for the same element
    * (also for correctness).
-   * @return true if the merge was successful and the other item should be deleted
-   */
-  virtual bool TryMerge(nsDisplayItem* aItem) {
-    return false;
-  }
+   */
+  virtual void Merge(const nsDisplayItem* aItem) {}
+
+  /**
+   * Merges the given display list to this item.
+   */
+  virtual void MergeDisplayListFromItem(nsDisplayListBuilder* aBuilder,
+                                        const nsDisplayItem* aItem) {}
 
   /**
    * 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) {}
+  virtual void GetMergedFrames(nsTArray<nsIFrame*>* aFrames) const {}
 
   /**
    * 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) {
@@ -2075,17 +2156,21 @@ public:
    */
   virtual bool MustPaintOnContentSide() const { return false; }
 
   /**
    * If this has a child list where the children are in the same coordinate
    * system as this item (i.e., they have the same reference frame),
    * return the list.
    */
-  virtual nsDisplayList* GetSameCoordinateSystemChildren() { return nullptr; }
+  virtual nsDisplayList* GetSameCoordinateSystemChildren() const
+  {
+    return nullptr;
+  }
+
   virtual void UpdateBounds(nsDisplayListBuilder* aBuilder) {}
   /**
    * Do UpdateBounds() for items with frames establishing or extending
    * 3D rendering context.
    *
    * This function is called by UpdateBoundsFor3D() of
    * nsDisplayTransform(), and it is called by
    * BuildDisplayListForStackingContext() on transform items
@@ -2097,17 +2182,17 @@ public:
    * the same 3d rendering context.
    */
   virtual void DoUpdateBoundsPreserves3D(nsDisplayListBuilder* aBuilder) {}
 
   /**
    * If this has a child list, return it, even if the children are in
    * a different coordinate system to this item.
    */
-  virtual nsDisplayList* GetChildren() { return nullptr; }
+  virtual nsDisplayList* GetChildren() const { return nullptr; }
 
   /**
    * Returns the visible rect.
    */
   const nsRect& GetVisibleRect() const { return mVisibleRect; }
 
   /**
    * Returns the visible rect for the children, relative to their
@@ -2125,19 +2210,19 @@ public:
                             float aOpacity,
                             const DisplayItemClipChain* aClip) {
     NS_ASSERTION(CanApplyOpacity(), "ApplyOpacity not supported on this type");
   }
   /**
    * Returns true if this display item would return true from ApplyOpacity without
    * actually applying the opacity. Otherwise returns false.
    */
-  virtual bool CanApplyOpacity() const {
-    return false;
-  }
+  virtual bool CanApplyOpacity() const { return false; }
+
+  bool ForceNotVisible() const { return mForceNotVisible; }
 
   /**
    * For debugging and stuff
    */
   virtual const char* Name() const = 0;
 
   virtual void WriteDebugInfo(std::stringstream& aStream) {}
 
@@ -2181,33 +2266,39 @@ public:
   }
 
   /**
    * Checks if this display item (or any children) contains content that might
    * be rendered with component alpha (e.g. subpixel antialiasing). Returns the
    * bounds of the area that needs component alpha, or an empty rect if nothing
    * in the item does.
    */
-  virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) { return nsRect(); }
+  virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const
+  {
+    return nsRect();
+  }
 
   /**
    * Disable usage of component alpha. Currently only relevant for items that have text.
    */
-  virtual void DisableComponentAlpha() {}
+  void DisableComponentAlpha()
+  {
+    mDisableSubpixelAA = true;
+  }
 
   /**
    * Check if we can add async animations to the layer for this display item.
    */
   virtual bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) {
     return false;
   }
 
-  virtual bool SupportsOptimizingToImage() { return false; }
-
-  const DisplayItemClip& GetClip()
+  virtual bool SupportsOptimizingToImage() const { return false; }
+
+  const DisplayItemClip& GetClip() const
   {
     return mClip ? *mClip : DisplayItemClip::NoClip();
   }
   void IntersectClip(nsDisplayListBuilder* aBuilder, const DisplayItemClipChain* aOther);
 
   void SetActiveScrolledRoot(const ActiveScrolledRoot* aActiveScrolledRoot) { mActiveScrolledRoot = aActiveScrolledRoot; }
   const ActiveScrolledRoot* GetActiveScrolledRoot() const { return mActiveScrolledRoot; }
 
@@ -2216,24 +2307,37 @@ public:
 
   /**
    * Intersect all clips in our clip chain up to (and including) aASR and set
    * set the intersection as this item's clip.
    */
   void FuseClipChainUpTo(nsDisplayListBuilder* aBuilder,
                          const ActiveScrolledRoot* aASR);
 
-  bool BackfaceIsHidden() {
-    return mFrame->BackfaceIsHidden();
+  bool BackfaceIsHidden() const { return mFrame->BackfaceIsHidden(); }
+
+  bool In3DContextAndBackfaceIsHidden() const
+  {
+    return Frame()->In3DContextAndBackfaceIsHidden();
+  }
+
+  bool HasSameTypeAndClip(const nsDisplayItem* aOther) const
+  {
+    return GetType() == aOther->GetType() &&
+           GetClipChain() == aOther->GetClipChain();
+  }
+
+  bool HasSameContent(const nsDisplayItem* aOther) const
+  {
+    return mFrame->GetContent() == aOther->Frame()->GetContent();
   }
 
 protected:
   friend class nsDisplayList;
-
-  nsDisplayItem() { mAbove = nullptr; }
+  nsDisplayItem() = delete;
 
   typedef bool (*PrefFunc)(void);
   bool ShouldUseAdvancedLayer(LayerManager* aManager, PrefFunc aFunc) const;
   bool CanUseAdvancedLayer(LayerManager* aManager) const;
 
   nsIFrame* mFrame;
   const DisplayItemClipChain* mClipChain;
   const DisplayItemClip* mClip;
@@ -2246,16 +2350,17 @@ protected:
   // 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;
+  bool      mDisableSubpixelAA;
 #ifdef MOZ_DUMP_PAINTING
   // True if this frame has been painted.
   bool      mPainted;
 #endif
 };
 
 /**
  * Manages a singly-linked list of display list items.
@@ -2732,19 +2837,19 @@ public:
 
   already_AddRefed<ImageContainer> GetContainer(LayerManager* aManager,
                                                 nsDisplayListBuilder* aBuilder);
   void ConfigureLayer(ImageLayer* aLayer,
                       const ContainerLayerParameters& aParameters);
 
   virtual already_AddRefed<imgIContainer> GetImage() = 0;
 
-  virtual nsRect GetDestRect() = 0;
-
-  virtual bool SupportsOptimizingToImage() override { return true; }
+  virtual nsRect GetDestRect() const = 0;
+
+  virtual bool SupportsOptimizingToImage() const override { return true; }
 };
 
 /**
  * Use this class to implement not-very-frequently-used display items
  * that are not opaque, do not receive events, and are bounded by a frame's
  * border-rect.
  *
  * This should not be used for display items which are created frequently,
@@ -2901,17 +3006,18 @@ protected:
 
 class nsDisplayCaret : public nsDisplayItem {
 public:
   nsDisplayCaret(nsDisplayListBuilder* aBuilder, nsIFrame* aCaretFrame);
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayCaret();
 #endif
 
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+                           bool* aSnap) const override;
   virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
   NS_DISPLAY_DECL_NAME("Caret", TYPE_CARET)
 
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters) override;
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager,
@@ -2935,18 +3041,19 @@ public:
   nsDisplayBorder(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame);
 
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayBorder() {
     MOZ_COUNT_DTOR(nsDisplayBorder);
   }
 #endif
 
-  virtual bool IsInvisibleInRect(const nsRect& aRect) override;
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+  virtual bool IsInvisibleInRect(const nsRect& aRect) const override;
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+                           bool* aSnap) const override;
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters) override;
 
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager,
                                              const ContainerLayerParameters& aContainerParameters) override;
   virtual bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
@@ -2958,31 +3065,32 @@ public:
   virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
 
   NS_DISPLAY_DECL_NAME("Border", TYPE_BORDER)
 
   virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override;
 
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion* aInvalidRegion) override;
-
-  virtual nsRegion GetTightBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override
+                                         nsRegion* aInvalidRegion) const override;
+
+  virtual nsRegion GetTightBounds(nsDisplayListBuilder* aBuilder,
+                                  bool* aSnap) const override
   {
     *aSnap = true;
     return CalculateBounds(*mFrame->StyleBorder());
   }
 
 protected:
   void CreateBorderImageWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
                                           const StackingContextHelper& aSc,
                                           nsTArray<WebRenderParentCommand>& aParentCommands,
                                           mozilla::layers::WebRenderLayerManager* aManager,
                                           nsDisplayListBuilder* aDisplayListBuilder);
-  nsRegion CalculateBounds(const nsStyleBorder& aStyleBorder);
+  nsRegion CalculateBounds(const nsStyleBorder& aStyleBorder) const;
 
   mozilla::Array<mozilla::gfx::Color, 4> mColors;
   mozilla::Array<mozilla::LayerCoord, 4> mWidths;
   mozilla::Array<mozilla::LayerSize, 4> mCorners;
   mozilla::Array<uint8_t, 4> mBorderStyles;
   mozilla::LayerRect mRect;
 
   mozilla::Maybe<nsCSSBorderRenderer> mBorderRenderer;
@@ -3010,39 +3118,40 @@ public:
 
   virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
   {
     return new nsDisplaySolidColorGeometry(this, aBuilder, mColor);
   }
 
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion* aInvalidRegion) override
+                                         nsRegion* aInvalidRegion) const override
   {
     const nsDisplaySolidColorGeometry* geometry =
       static_cast<const nsDisplaySolidColorGeometry*>(aGeometry);
     if (mColor != geometry->mColor) {
       bool dummy;
       aInvalidRegion->Or(geometry->mBounds, GetBounds(aBuilder, &dummy));
       return;
     }
     ComputeInvalidationRegionDifference(aBuilder, geometry, aInvalidRegion);
   }
 
   virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
-                                   bool* aSnap) override {
+                                   bool* aSnap) const override
+  {
     *aSnap = false;
     nsRegion result;
     if (NS_GET_A(mColor) == 255) {
       result = GetBounds(aBuilder, aSnap);
     }
     return result;
   }
 
-  virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) override
+  virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) const override
   {
     return mozilla::Some(mColor);
   }
 
 protected:
   nscolor mColor;
 };
 
@@ -3056,17 +3165,18 @@ public:
     MOZ_COUNT_CTOR(nsDisplaySolidColor);
   }
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplaySolidColor() {
     MOZ_COUNT_DTOR(nsDisplaySolidColor);
   }
 #endif
 
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+                           bool* aSnap) const override;
 
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters) override;
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager,
                                              const ContainerLayerParameters& aContainerParameters) override;
 
@@ -3110,17 +3220,17 @@ public:
 
   virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
   {
     return new nsDisplaySolidColorRegionGeometry(this, aBuilder, mRegion, mColor);
   }
 
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion* aInvalidRegion) override
+                                         nsRegion* aInvalidRegion) const override
   {
     const nsDisplaySolidColorRegionGeometry* geometry =
       static_cast<const nsDisplaySolidColorRegionGeometry*>(aGeometry);
     if (mColor == geometry->mColor) {
       aInvalidRegion->Xor(geometry->mRegion, mRegion);
     } else {
       aInvalidRegion->Or(geometry->mRegion.GetBounds(), mRegion.GetBounds());
     }
@@ -3131,17 +3241,18 @@ public:
                                        nsTArray<WebRenderParentCommand>& aParentCommands,
                                        mozilla::layers::WebRenderLayerManager* aManager,
                                        nsDisplayListBuilder* aDisplayListBuilder) override;
 
   NS_DISPLAY_DECL_NAME("SolidColorRegion", TYPE_SOLID_COLOR_REGION)
 
 protected:
 
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+                           bool* aSnap) const override;
   virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
   virtual void WriteDebugInfo(std::stringstream& aStream) override;
 
 private:
   nsRegion mRegion;
   Color mColor;
 };
 
@@ -3214,62 +3325,70 @@ public:
                                        nsTArray<WebRenderParentCommand>& aParentCommands,
                                        mozilla::layers::WebRenderLayerManager* aManager,
                                        nsDisplayListBuilder* aDisplayListBuilder) override;
   virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                        HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override;
   virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
                                  nsRegion* aVisibleRegion) override;
   virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
-                                   bool* aSnap) override;
-  virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) override;
+                                   bool* aSnap) const override;
+  virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) const override;
   /**
    * GetBounds() returns the background painting area.
    */
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+                           bool* aSnap) const override;
   virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
-  virtual uint32_t GetPerFrameKey() override;
+  virtual uint32_t GetPerFrameKey() const override
+  {
+    return (mLayer << TYPE_BITS) | nsDisplayItem::GetPerFrameKey();
+  }
+
   NS_DISPLAY_DECL_NAME("Background", TYPE_BACKGROUND)
 
   /**
    * Return the background positioning area.
    * (GetBounds() returns the background painting area.)
    * Can be called only when mBackgroundStyle is non-null.
    */
-  nsRect GetPositioningArea();
+  nsRect GetPositioningArea() const;
 
   /**
    * Returns true if existing rendered pixels of this display item may need
    * to be redrawn if the positioning area size changes but its position does
    * not.
    * If false, only the changed painting area needs to be redrawn when the
    * positioning area size changes but its position does not.
    */
-  bool RenderingMightDependOnPositioningAreaSizeChange();
+  bool RenderingMightDependOnPositioningAreaSizeChange() const;
 
   virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
   {
     return new nsDisplayBackgroundGeometry(this, aBuilder);
   }
 
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion* aInvalidRegion) override;
+                                         nsRegion* aInvalidRegion) const override;
 
   virtual bool CanOptimizeToImageLayer(LayerManager* aManager,
                                        nsDisplayListBuilder* aBuilder) override;
   virtual already_AddRefed<imgIContainer> GetImage() override;
-  virtual nsRect GetDestRect() override;
-
-  static nsRegion GetInsideClipRegion(nsDisplayItem* aItem,
+  virtual nsRect GetDestRect() const override;
+
+  static nsRegion GetInsideClipRegion(const nsDisplayItem* aItem,
                                       StyleGeometryBox aClip,
                                       const nsRect& aRect,
                                       const nsRect& aBackgroundRect);
 
-  virtual bool ShouldFixToViewport(nsDisplayListBuilder* aBuilder) override { return mShouldFixToViewport; }
+  virtual bool ShouldFixToViewport(nsDisplayListBuilder* aBuilder) const override
+  {
+    return mShouldFixToViewport;
+  }
 
 protected:
   typedef class mozilla::layers::ImageContainer ImageContainer;
   typedef class mozilla::layers::ImageLayer ImageLayer;
 
   bool CanBuildWebRenderDisplayItems(LayerManager* aManager);
   bool TryOptimizeToImageLayer(LayerManager* aManager, nsDisplayListBuilder* aBuilder);
   nsRect GetBoundsInternal(nsDisplayListBuilder* aBuilder);
@@ -3337,22 +3456,22 @@ TableType GetTableTypeFromFrame(nsIFrame
  * avoid sharing DisplayItemData.
  *
  * Also store ancestor frame as mStyleFrame for all rendering informations.
  */
 class nsDisplayTableBackgroundImage : public nsDisplayBackgroundImage {
 public:
   nsDisplayTableBackgroundImage(const InitData& aInitData, nsIFrame* aCellFrame);
 
-  virtual uint32_t GetPerFrameKey() override {
+  virtual uint32_t GetPerFrameKey() const override {
     return (static_cast<uint8_t>(mTableType) << TYPE_BITS) |
            nsDisplayItem::GetPerFrameKey();
   }
 
-  virtual bool IsInvalid(nsRect& aRect) override;
+  virtual bool IsInvalid(nsRect& aRect) const override;
 protected:
   virtual nsIFrame* StyleFrame() override { return mStyleFrame; }
 
   nsIFrame* mStyleFrame;
   TableType mTableType;
 };
 
 /**
@@ -3362,49 +3481,50 @@ class nsDisplayThemedBackground : public
 public:
   nsDisplayThemedBackground(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                             const nsRect& aBackgroundRect);
   virtual ~nsDisplayThemedBackground();
 
   virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                        HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override;
   virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
-                                   bool* aSnap) override;
-  virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) override;
-  virtual bool ProvidesFontSmoothingBackgroundColor(nscolor* aColor) override;
+                                   bool* aSnap) const override;
+  virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) const override;
+  virtual bool ProvidesFontSmoothingBackgroundColor(nscolor* aColor) const override;
   virtual bool MustPaintOnContentSide() const override { return true; }
 
   /**
    * GetBounds() returns the background painting area.
    */
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+                           bool* aSnap) const override;
   virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
   NS_DISPLAY_DECL_NAME("ThemedBackground", TYPE_THEMED_BACKGROUND)
 
   /**
    * Return the background positioning area.
    * (GetBounds() returns the background painting area.)
    * Can be called only when mBackgroundStyle is non-null.
    */
-  nsRect GetPositioningArea();
+  nsRect GetPositioningArea() const;
 
   /**
    * Return whether our frame's document does not have the state
    * NS_DOCUMENT_STATE_WINDOW_INACTIVE.
    */
-  bool IsWindowActive();
+  bool IsWindowActive() const;
 
   virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
   {
     return new nsDisplayThemedBackgroundGeometry(this, aBuilder);
   }
 
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion* aInvalidRegion) override;
+                                         nsRegion* aInvalidRegion) const override;
 
   virtual void WriteDebugInfo(std::stringstream& aStream) override;
 protected:
   nsRect GetBoundsInternal();
 
   void PaintInternal(nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
                      const nsRect& aBounds, nsRect* aClipRect);
 
@@ -3437,40 +3557,41 @@ public:
                                              LayerManager* aManager,
                                              const ContainerLayerParameters& aContainerParameters) override;
   virtual bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
                                        const StackingContextHelper& aSc,
                                        nsTArray<WebRenderParentCommand>& aParentCommands,
                                        mozilla::layers::WebRenderLayerManager* aManager,
                                        nsDisplayListBuilder* aDisplayListBuilder) override;
   virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
-                                   bool* aSnap) override;
-  virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) override;
+                                   bool* aSnap) const override;
+  virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) const override;
   virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                        HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override;
 
   virtual void ApplyOpacity(nsDisplayListBuilder* aBuilder,
                             float aOpacity,
                             const DisplayItemClipChain* aClip) override;
   virtual bool CanApplyOpacity() const override;
 
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+                           bool* aSnap) const override
   {
     *aSnap = true;
     return mBackgroundRect;
   }
 
   virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
   {
     return new nsDisplaySolidColorGeometry(this, aBuilder, mColor.ToABGR());
   }
 
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion* aInvalidRegion) override
+                                         nsRegion* aInvalidRegion) const override
   {
     const nsDisplaySolidColorGeometry* geometry = static_cast<const nsDisplaySolidColorGeometry*>(aGeometry);
     if (mColor.ToABGR() != geometry->mColor) {
       bool dummy;
       aInvalidRegion->Or(geometry->mBounds, GetBounds(aBuilder, &dummy));
       return;
     }
     ComputeInvalidationRegionDifference(aBuilder, geometry, aInvalidRegion);
@@ -3492,49 +3613,50 @@ public:
                                 const nsRect& aBackgroundRect,
                                 const nsStyleBackground* aBackgroundStyle,
                                 nscolor aColor,
                                 nsIFrame* aAncestorFrame)
     : nsDisplayBackgroundColor(aBuilder, aFrame, aBackgroundRect, aBackgroundStyle, aColor)
     , mTableType(GetTableTypeFromFrame(aAncestorFrame))
   { }
 
-  virtual uint32_t GetPerFrameKey() override {
+  virtual uint32_t GetPerFrameKey() const override {
     return (static_cast<uint8_t>(mTableType) << TYPE_BITS) |
            nsDisplayItem::GetPerFrameKey();
   }
 protected:
   TableType mTableType;
 };
 
 class nsDisplayClearBackground : public nsDisplayItem
 {
 public:
   nsDisplayClearBackground(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
     : nsDisplayItem(aBuilder, aFrame)
   { }
 
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+                           bool* aSnap) const override
   {
     *aSnap = true;
     return nsRect(ToReferenceFrame(), Frame()->GetSize());
   }
 
   virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
-                                   bool* aSnap) override {
+                                   bool* aSnap) const override {
     *aSnap = false;
     return GetBounds(aBuilder, aSnap);
   }
 
-  virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) override
+  virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) const override
   {
     return mozilla::Some(NS_RGBA(0, 0, 0, 0));
   }
 
-  virtual bool ClearsBackground() override
+  virtual bool ClearsBackground() const override
   {
     return true;
   }
 
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters) override
   {
@@ -3550,36 +3672,38 @@ public:
 
 /**
  * The standard display item to paint the outer CSS box-shadows of a frame.
  */
 class nsDisplayBoxShadowOuter final : public nsDisplayItem {
 public:
   nsDisplayBoxShadowOuter(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
     : nsDisplayItem(aBuilder, aFrame)
-    , mOpacity(1.0) {
+    , mOpacity(1.0f)
+  {
     MOZ_COUNT_CTOR(nsDisplayBoxShadowOuter);
     mBounds = GetBoundsInternal();
   }
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayBoxShadowOuter() {
     MOZ_COUNT_DTOR(nsDisplayBoxShadowOuter);
   }
 #endif
 
   virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
-  virtual bool IsInvisibleInRect(const nsRect& aRect) override;
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+                           bool* aSnap) const override;
+  virtual bool IsInvisibleInRect(const nsRect& aRect) const override;
   virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
                                  nsRegion* aVisibleRegion) override;
   NS_DISPLAY_DECL_NAME("BoxShadowOuter", TYPE_BOX_SHADOW_OUTER)
 
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion* aInvalidRegion) override;
+                                         nsRegion* aInvalidRegion) const override;
 
   virtual void ApplyOpacity(nsDisplayListBuilder* aBuilder,
                             float aOpacity,
                             const DisplayItemClipChain* aClip) override
   {
     NS_ASSERTION(CanApplyOpacity(), "ApplyOpacity should be allowed");
     mOpacity = aOpacity;
     IntersectClip(aBuilder, aClip);
@@ -3637,17 +3761,17 @@ public:
 
   virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
   {
     return new nsDisplayBoxShadowInnerGeometry(this, aBuilder);
   }
 
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion* aInvalidRegion) override
+                                         nsRegion* aInvalidRegion) const override
   {
     const nsDisplayBoxShadowInnerGeometry* geometry = static_cast<const nsDisplayBoxShadowInnerGeometry*>(aGeometry);
     if (!geometry->mPaddingRect.IsEqualInterior(GetPaddingRect())) {
       // nsDisplayBoxShadowInner is based around the padding rect, but it can
       // touch pixels outside of this. We should invalidate the entire bounds.
       bool snap;
       aInvalidRegion->Or(geometry->mBounds, GetBounds(aBuilder, &snap));
     }
@@ -3698,18 +3822,18 @@ public:
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager,
                                              const ContainerLayerParameters& aContainerParameters) override;
   virtual bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
                                        const StackingContextHelper& aSc,
                                        nsTArray<WebRenderParentCommand>& aParentCommands,
                                        mozilla::layers::WebRenderLayerManager* aManager,
                                        nsDisplayListBuilder* aDisplayListBuilder) override;
-  virtual bool IsInvisibleInRect(const nsRect& aRect) override;
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+  virtual bool IsInvisibleInRect(const nsRect& aRect) const override;
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override;
   virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
   NS_DISPLAY_DECL_NAME("Outline", TYPE_OUTLINE)
 
   mozilla::Maybe<nsCSSBorderRenderer> mBorderRenderer;
 };
 
 /**
  * A class that lets you receive events within the frame bounds but never paints.
@@ -3757,17 +3881,18 @@ public:
   {
     MOZ_COUNT_CTOR(nsDisplayLayerEventRegions);
   }
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayLayerEventRegions() {
     MOZ_COUNT_DTOR(nsDisplayLayerEventRegions);
   }
 #endif
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+                           bool* aSnap) const override
   {
     *aSnap = false;
     return nsRect();
   }
   nsRect GetHitRegionBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
   {
     *aSnap = false;
     return mHitRegion.GetBounds().Union(mMaybeHitRegion.GetBounds());
@@ -3862,60 +3987,105 @@ public:
                     const ActiveScrolledRoot* aActiveScrolledRoot);
   nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                     nsDisplayItem* aItem);
   nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
     : nsDisplayItem(aBuilder, aFrame), mOverrideZIndex(0), mHasZIndexOverride(false)
   {
     MOZ_COUNT_CTOR(nsDisplayWrapList);
     mBaseVisibleRect = mVisibleRect;
-  }
+    mListPtr = &mList;
+  }
+
+  /**
+   * A custom copy-constructor that does not copy mList, as this would mutate
+   * the other item.
+   */
+  nsDisplayWrapList(const nsDisplayWrapList& aOther)
+    : nsDisplayItem(aOther)
+    , mListPtr(&mList)
+    , mMergedFrames(aOther.mMergedFrames)
+    , mBounds(aOther.mBounds)
+    , mBaseVisibleRect(aOther.mBaseVisibleRect)
+    , mOverrideZIndex(aOther.mOverrideZIndex)
+    , mHasZIndexOverride(aOther.mHasZIndexOverride)
+  {
+    MOZ_COUNT_CTOR(nsDisplayWrapList);
+  }
+
   virtual ~nsDisplayWrapList();
 
+  virtual const nsDisplayWrapList* AsDisplayWrapList() const override
+  {
+    return this;
+  }
+
   virtual void Destroy(nsDisplayListBuilder* aBuilder) override {
     mList.DeleteAll(aBuilder);
     nsDisplayItem::Destroy(aBuilder);
   }
+
+  /**
+   * Creates a new nsDisplayWrapList that holds a pointer to the display list
+   * owned by the given nsDisplayItem. The new nsDisplayWrapList will be added
+   * to the bottom of this item's contents.
+   */
+  virtual void MergeDisplayListFromItem(nsDisplayListBuilder* aBuilder,
+                                        const nsDisplayItem* aItem) override;
+
   /**
    * Call this if the wrapped list is changed.
    */
   virtual void UpdateBounds(nsDisplayListBuilder* aBuilder) override
   {
     nsRect visibleRect;
-    mBounds = mList.GetClippedBoundsWithRespectToASR(aBuilder, mActiveScrolledRoot,
-                                                     &visibleRect);
+    mBounds =
+      mListPtr->GetClippedBoundsWithRespectToASR(aBuilder, mActiveScrolledRoot, &visibleRect);
     // The display list may contain content that's visible outside the visible
     // rect (i.e. the current dirty rect) passed in when the item was created.
     // This happens when the dirty rect has been restricted to the visual
     // overflow rect of a frame for some reason (e.g. when setting up dirty
     // rects in nsDisplayListBuilder::MarkOutOfFlowFrameForDisplay), but that
     // frame contains placeholders for out-of-flows that aren't descendants of
     // the frame.
     mVisibleRect.UnionRect(mBaseVisibleRect, visibleRect);
   }
   virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                        HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override;
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+                           bool* aSnap) const override;
   virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
-                                   bool* aSnap) override;
-  virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) override;
+                                   bool* aSnap) const override;
+  virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) const override;
   virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
   virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
                                  nsRegion* aVisibleRegion) override;
-  virtual bool TryMerge(nsDisplayItem* aItem) override {
+
+  virtual bool CanMerge(const nsDisplayItem* aItem) const override
+  {
     return false;
   }
-  virtual void GetMergedFrames(nsTArray<nsIFrame*>* aFrames) override
+
+  virtual void Merge(const nsDisplayItem* aItem) override
+  {
+    MOZ_ASSERT(CanMerge(aItem));
+    MergeFromTrackingMergedFrames(static_cast<const nsDisplayWrapList*>(aItem));
+  }
+
+  virtual void GetMergedFrames(nsTArray<nsIFrame*>* aFrames) const override
   {
     aFrames->AppendElements(mMergedFrames);
   }
-  virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+
+  virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override
+  {
     return true;
   }
-  virtual bool IsInvalid(nsRect& aRect) override
+
+  virtual bool IsInvalid(nsRect& aRect) const override
   {
     if (mFrame->IsInvalid(aRect) && aRect.IsEmpty()) {
       return true;
     }
     nsRect temp;
     for (uint32_t i = 0; i < mMergedFrames.Length(); i++) {
       if (mMergedFrames[i]->IsInvalid(temp) && temp.IsEmpty()) {
         aRect.SetEmpty();
@@ -3923,27 +4093,28 @@ public:
       }
       aRect = aRect.Union(temp);
     }
     aRect += ToReferenceFrame();
     return !aRect.IsEmpty();
   }
   NS_DISPLAY_DECL_NAME("WrapList", TYPE_WRAP_LIST)
 
-  virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override;
-
-  virtual nsDisplayList* GetSameCoordinateSystemChildren() override
+  virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const override;
+
+  virtual nsDisplayList* GetSameCoordinateSystemChildren() const override
   {
-    NS_ASSERTION(mList.IsEmpty() || !ReferenceFrame() ||
-                 !mList.GetBottom()->ReferenceFrame() ||
-                 mList.GetBottom()->ReferenceFrame() == ReferenceFrame(),
+    NS_ASSERTION(mListPtr->IsEmpty() || !ReferenceFrame() ||
+                 !mListPtr->GetBottom()->ReferenceFrame() ||
+                 mListPtr->GetBottom()->ReferenceFrame() == ReferenceFrame(),
                  "Children must have same reference frame");
-    return &mList;
-  }
-  virtual nsDisplayList* GetChildren() override { return &mList; }
+    return mListPtr;
+  }
+
+  virtual nsDisplayList* GetChildren() const override { return mListPtr; }
 
   virtual int32_t ZIndex() const override
   {
     return (mHasZIndexOverride) ? mOverrideZIndex : nsDisplayItem::ZIndex();
   }
 
   void SetOverrideZIndex(int32_t aZIndex)
   {
@@ -3969,28 +4140,28 @@ public:
 
   virtual bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
                                        const StackingContextHelper& aSc,
                                        nsTArray<WebRenderParentCommand>& aParentCommands,
                                        mozilla::layers::WebRenderLayerManager* aManager,
                                        nsDisplayListBuilder* aDisplayListBuilder) override;
 
 protected:
-  nsDisplayWrapList() {}
-
-  void MergeFromTrackingMergedFrames(nsDisplayWrapList* aOther)
+  nsDisplayWrapList() = delete;
+
+  void MergeFromTrackingMergedFrames(const nsDisplayWrapList* aOther)
   {
-    mList.AppendToBottom(&aOther->mList);
     mBounds.UnionRect(mBounds, aOther->mBounds);
     mVisibleRect.UnionRect(mVisibleRect, aOther->mVisibleRect);
     mMergedFrames.AppendElement(aOther->mFrame);
-    mMergedFrames.AppendElements(mozilla::Move(aOther->mMergedFrames));
+    mMergedFrames.AppendElements(aOther->mMergedFrames);
   }
 
   nsDisplayList mList;
+  nsDisplayList* mListPtr;
   // The frames from items that have been merged into this item, excluding
   // this item's own frame.
   nsTArray<nsIFrame*> mMergedFrames;
   nsRect mBounds;
   // Visible rect contributed by this display item itself.
   // Our mVisibleRect may include the visible areas of children.
   nsRect mBaseVisibleRect;
   int32_t mOverrideZIndex;
@@ -4015,51 +4186,65 @@ public:
   virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder,
                                   nsDisplayItem* aItem) = 0;
 
   nsresult WrapLists(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                      const nsDisplayListSet& aIn, const nsDisplayListSet& aOut);
   nsresult WrapListsInPlace(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                             const nsDisplayListSet& aLists);
 protected:
-  nsDisplayWrapper() {}
+  nsDisplayWrapper() = default;
 };
 
 /**
  * The standard display item to paint a stacking context with translucency
  * set by the stacking context root frame's 'opacity' style.
  */
 class nsDisplayOpacity : public nsDisplayWrapList {
 public:
   nsDisplayOpacity(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                    nsDisplayList* aList,
                    const ActiveScrolledRoot* aActiveScrolledRoot,
                    bool aForEventsAndPluginsOnly);
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayOpacity();
 #endif
 
+  virtual nsDisplayWrapList* Clone(nsDisplayListBuilder* aBuilder) const override
+  {
+    MOZ_COUNT_CTOR(nsDisplayOpacity);
+    return new (aBuilder) nsDisplayOpacity(*this);
+  }
+
   virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
-                                   bool* aSnap) override;
+                                   bool* aSnap) const override;
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager,
                                              const ContainerLayerParameters& aContainerParameters) override;
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters) override;
   virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
                                  nsRegion* aVisibleRegion) override;
-  virtual bool TryMerge(nsDisplayItem* aItem) override;
+
+  virtual bool CanMerge(const nsDisplayItem* aItem) const override
+  {
+    // items for the same content element should be merged into a single
+    // compositing group
+    // aItem->GetUnderlyingFrame() returns non-null because it's nsDisplayOpacity
+    return HasSameTypeAndClip(aItem) && HasSameContent(aItem);
+  }
+
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion* aInvalidRegion) override
+                                         nsRegion* aInvalidRegion) const override
   {
     // We don't need to compute an invalidation region since we have LayerTreeInvalidation
   }
-  virtual bool IsInvalid(nsRect& aRect) override
+  virtual bool IsInvalid(nsRect& aRect) const override
   {
     if (mForEventsAndPluginsOnly) {
       return false;
     }
     return nsDisplayWrapList::IsInvalid(aRect);
   }
   virtual void ApplyOpacity(nsDisplayListBuilder* aBuilder,
                             float aOpacity,
@@ -4088,45 +4273,55 @@ public:
   nsDisplayBlendMode(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                         nsDisplayList* aList, uint8_t aBlendMode,
                         const ActiveScrolledRoot* aActiveScrolledRoot,
                         uint32_t aIndex = 0);
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayBlendMode();
 #endif
 
+  virtual nsDisplayWrapList* Clone(nsDisplayListBuilder* aBuilder) const override
+  {
+    MOZ_COUNT_CTOR(nsDisplayBlendMode);
+    return new (aBuilder) nsDisplayBlendMode(*this);
+  }
+
   nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
-                           bool* aSnap) override;
+                           bool* aSnap) const override;
 
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager,
                                              const ContainerLayerParameters& aContainerParameters) override;
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion* aInvalidRegion) override
+                                         nsRegion* aInvalidRegion) const override
   {
     // We don't need to compute an invalidation region since we have LayerTreeInvalidation
   }
-  virtual uint32_t GetPerFrameKey() override {
+  virtual uint32_t GetPerFrameKey() const override {
     return (mIndex << TYPE_BITS) | nsDisplayItem::GetPerFrameKey();
   }
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters) override;
   bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
                                const StackingContextHelper& aSc,
                                nsTArray<WebRenderParentCommand>& aParentCommands,
                                mozilla::layers::WebRenderLayerManager* aManager,
                                nsDisplayListBuilder* aDisplayListBuilder) override;
   virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
                                  nsRegion* aVisibleRegion) override;
-  virtual bool TryMerge(nsDisplayItem* aItem) override;
-  virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+
+  virtual bool CanMerge(const nsDisplayItem* aItem) const override;
+
+  virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override
+  {
     return false;
   }
+
   NS_DISPLAY_DECL_NAME("BlendMode", TYPE_BLEND_MODE)
 
 private:
   uint8_t mBlendMode;
   uint32_t mIndex;
 };
 
 class nsDisplayBlendContainer : public nsDisplayWrapList {
@@ -4140,32 +4335,46 @@ public:
     CreateForBackgroundBlendMode(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                                  nsDisplayList* aList,
                                  const ActiveScrolledRoot* aActiveScrolledRoot);
 
 #ifdef NS_BUILD_REFCNT_LOGGING
     virtual ~nsDisplayBlendContainer();
 #endif
 
+    virtual nsDisplayWrapList* Clone(nsDisplayListBuilder* aBuilder) const override
+    {
+      MOZ_COUNT_CTOR(nsDisplayBlendContainer);
+      return new (aBuilder) nsDisplayBlendContainer(*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;
     bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
                                  const StackingContextHelper& aSc,
                                  nsTArray<WebRenderParentCommand>& aParentCommands,
                                  mozilla::layers::WebRenderLayerManager* aManager,
                                  nsDisplayListBuilder* aDisplayListBuilder) override;
-    virtual bool TryMerge(nsDisplayItem* aItem) override;
-    virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+
+    virtual bool CanMerge(const nsDisplayItem* aItem) const override
+    {
+      // Items for the same content element should be merged into a single
+      // compositing group.
+      return HasSameTypeAndClip(aItem) && HasSameContent(aItem);
+    }
+
+    virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override
+    {
       return false;
     }
-    virtual uint32_t GetPerFrameKey() override {
+    virtual uint32_t GetPerFrameKey() const override {
       return (mIsForBackground ? 1 << TYPE_BITS : 0) | nsDisplayItem::GetPerFrameKey();
     }
     NS_DISPLAY_DECL_NAME("BlendContainer", TYPE_BLEND_CONTAINER)
 
 private:
     nsDisplayBlendContainer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                             nsDisplayList* aList,
                             const ActiveScrolledRoot* aActiveScrolledRoot,
@@ -4211,38 +4420,52 @@ public:
                     const ActiveScrolledRoot* aActiveScrolledRoot,
                     uint32_t aFlags = 0,
                     ViewID aScrollTarget = mozilla::layers::FrameMetrics::NULL_SCROLL_ID,
                     const ScrollThumbData& aThumbData = ScrollThumbData{},
                     bool aForceActive = true);
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayOwnLayer();
 #endif
-  virtual bool ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder) override;
+  nsDisplayOwnLayer(const nsDisplayOwnLayer& aOther)
+    : nsDisplayWrapList(aOther)
+    , mFlags(aOther.mFlags)
+    , mScrollTarget(aOther.mFlags)
+    , mThumbData(aOther.mThumbData)
+    , mForceActive(aOther.mForceActive)
+  {
+    MOZ_COUNT_CTOR(nsDisplayOwnLayer);
+  }
+
+  virtual bool ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder) const override;
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager,
                                              const ContainerLayerParameters& aContainerParameters) override;
   virtual bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
                                        const StackingContextHelper& aSc,
                                        nsTArray<WebRenderParentCommand>& aParentCommands,
                                        mozilla::layers::WebRenderLayerManager* aManager,
                                        nsDisplayListBuilder* aDisplayListBuilder) override;
   virtual bool UpdateScrollData(mozilla::layers::WebRenderScrollData* aData,
                                 mozilla::layers::WebRenderLayerScrollData* aLayerData) override;
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters) override;
-  virtual bool TryMerge(nsDisplayItem* aItem) override
+
+  virtual bool CanMerge(const nsDisplayItem* aItem) const override
   {
     // Don't allow merging, each sublist must have its own layer
     return false;
   }
-  virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+
+  virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override
+  {
     return false;
   }
+
   uint32_t GetFlags() { return mFlags; }
   bool IsScrollThumbLayer() const;
   NS_DISPLAY_DECL_NAME("OwnLayer", TYPE_OWN_LAYER)
 protected:
   uint32_t mFlags;
   ViewID mScrollTarget;
   // If this nsDisplayOwnLayer represents a scroll thumb layer, mThumbData
   // stores information about the scroll thumb. Otherwise, mThumbData will be
@@ -4265,24 +4488,26 @@ public:
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplaySubDocument();
 #endif
 
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager,
                                              const ContainerLayerParameters& aContainerParameters) override;
 
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+                           bool* aSnap) const override;
 
   virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
                                  nsRegion* aVisibleRegion) override;
 
-  virtual bool ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder) override;
-
-  virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+  virtual bool ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder) const override;
+
+  virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+                                   bool* aSnap) const override;
 
   NS_DISPLAY_DECL_NAME("SubDocument", TYPE_SUBDOCUMENT)
 
   mozilla::UniquePtr<ScrollMetadata> ComputeScrollMetadata(Layer* aLayer,
                                                            const ContainerLayerParameters& aContainerParameters);
 
 protected:
   ViewID mScrollParentId;
@@ -4321,27 +4546,38 @@ public:
   nsDisplayStickyPosition(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                           nsDisplayList* aList,
                           const ActiveScrolledRoot* aActiveScrolledRoot);
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayStickyPosition();
 #endif
 
   void SetClipChain(const DisplayItemClipChain* aClipChain) override;
+  virtual nsDisplayWrapList* Clone(nsDisplayListBuilder* aBuilder) const override
+  {
+    MOZ_COUNT_CTOR(nsDisplayStickyPosition);
+    return new (aBuilder) nsDisplayStickyPosition(*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,
                                    const ContainerLayerParameters& aParameters) override
   {
     return mozilla::LAYER_ACTIVE;
   }
-  virtual bool TryMerge(nsDisplayItem* aItem) override;
+
+  virtual bool CanMerge(const nsDisplayItem* aItem) const override
+  {
+    // Items with the same fixed position frame can be merged.
+    return HasSameTypeAndClip(aItem) && mFrame == aItem->Frame();
+  }
 };
 
 class nsDisplayFixedPosition : public nsDisplayOwnLayer {
 public:
   nsDisplayFixedPosition(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                          nsDisplayList* aList,
                          const ActiveScrolledRoot* aActiveScrolledRoot);
 
@@ -4350,31 +4586,47 @@ public:
                                                           nsDisplayBackgroundImage* aImage,
                                                           uint32_t aIndex);
 
 
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayFixedPosition();
 #endif
 
+  virtual nsDisplayWrapList* Clone(nsDisplayListBuilder* aBuilder) const override
+  {
+    MOZ_COUNT_CTOR(nsDisplayFixedPosition);
+    return new (aBuilder) nsDisplayFixedPosition(*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,
                                    const ContainerLayerParameters& aParameters) override
   {
     return mozilla::LAYER_ACTIVE_FORCE;
   }
-  virtual bool TryMerge(nsDisplayItem* aItem) override;
-
-  virtual bool ShouldFixToViewport(nsDisplayListBuilder* aBuilder) override { return mIsFixedBackground; }
-
-  virtual uint32_t GetPerFrameKey() override { return (mIndex << TYPE_BITS) | nsDisplayItem::GetPerFrameKey(); }
+
+  virtual bool CanMerge(const nsDisplayItem* aItem) const override
+  {
+    // Items with the same fixed position frame can be merged.
+    return HasSameTypeAndClip(aItem) && mFrame == aItem->Frame();
+  }
+
+  virtual bool ShouldFixToViewport(nsDisplayListBuilder* aBuilder) const override
+  {
+    return mIsFixedBackground;
+  }
+
+  virtual uint32_t GetPerFrameKey() const override {
+    return (mIndex << TYPE_BITS) | nsDisplayItem::GetPerFrameKey();
+  }
 
   AnimatedGeometryRoot* AnimatedGeometryRootForScrollMetadata() const override {
     return mAnimatedGeometryRootForScrollMetadata;
   }
 
   virtual bool UpdateScrollData(mozilla::layers::WebRenderScrollData* aData,
                                 mozilla::layers::WebRenderLayerScrollData* aLayerData) override;
 
@@ -4393,17 +4645,17 @@ class nsDisplayTableFixedPosition : publ
 {
 public:
   static nsDisplayTableFixedPosition* CreateForFixedBackground(nsDisplayListBuilder* aBuilder,
                                                                nsIFrame* aFrame,
                                                                nsDisplayBackgroundImage* aImage,
                                                                uint32_t aIndex,
                                                                nsIFrame* aAncestorFrame);
 
-  virtual uint32_t GetPerFrameKey() override {
+  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();
   }
 protected:
   nsDisplayTableFixedPosition(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                               nsDisplayList* aList, uint32_t aIndex, nsIFrame* aAncestorFrame);
 
@@ -4426,31 +4678,36 @@ public:
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayScrollInfoLayer();
 #endif
 
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager,
                                              const ContainerLayerParameters& aContainerParameters) override;
 
-  virtual bool ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder) override
-  { return true; }
+  virtual bool ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder) const override
+  {
+    return true;
+  }
 
   virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
-                                   bool* aSnap) override {
+                                   bool* aSnap) const override
+  {
     *aSnap = false;
     return nsRegion();
   }
 
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters) override;
 
   virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override
-  { return false; }
+  {
+    return false;
+  }
 
   virtual void WriteDebugInfo(std::stringstream& aStream) override;
 
   mozilla::UniquePtr<ScrollMetadata> ComputeScrollMetadata(Layer* aLayer,
                                                            const ContainerLayerParameters& aContainerParameters);
 
   virtual bool UpdateScrollData(mozilla::layers::WebRenderScrollData* aData,
                                 mozilla::layers::WebRenderLayerScrollData* aLayerData) override;
@@ -4480,17 +4737,18 @@ public:
   nsDisplayZoom(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                 nsDisplayList* aList,
                 int32_t aAPD, int32_t aParentAPD,
                 uint32_t aFlags = 0);
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayZoom();
 #endif
 
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+                           bool* aSnap) const override;
   virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                        HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override;
   virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
                                  nsRegion* aVisibleRegion) override;
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters) override
   {
@@ -4513,32 +4771,40 @@ public:
                       nsDisplayList* aList, bool aHandleOpacity,
                       const ActiveScrolledRoot* aActiveScrolledRoot);
   nsDisplaySVGEffects(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                       nsDisplayList* aList, bool aHandleOpacity);
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplaySVGEffects();
 #endif
 
+  nsDisplaySVGEffects(const nsDisplaySVGEffects& aOther)
+    : nsDisplayWrapList(aOther)
+    , mEffectsBounds(aOther.mEffectsBounds)
+    , mHandleOpacity(aOther.mHandleOpacity)
+  {
+    MOZ_COUNT_CTOR(nsDisplaySVGEffects);
+  }
+
   virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
-                                   bool* aSnap) override;
+                                   bool* aSnap) const override;
   virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                        HitTestState* aState,
                        nsTArray<nsIFrame*> *aOutFrames) override;
 
   virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
     return false;
   }
 
   gfxRect BBoxInUserSpace() const;
   gfxPoint UserSpaceOffset() const;
 
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion* aInvalidRegion) override;
+                                         nsRegion* aInvalidRegion) const override;
 protected:
   bool ValidateSVGFrame();
 
   // relative to mFrame
   nsRect mEffectsBounds;
   // True if we need to handle css opacity in this display item.
   bool mHandleOpacity;
 };
@@ -4553,36 +4819,52 @@ public:
 
   nsDisplayMask(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                 nsDisplayList* aList, bool aHandleOpacity,
                 const ActiveScrolledRoot* aActiveScrolledRoot);
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayMask();
 #endif
 
+  virtual nsDisplayWrapList* Clone(nsDisplayListBuilder* aBuilder) const override
+  {
+    MOZ_COUNT_CTOR(nsDisplayMask);
+    return new (aBuilder) nsDisplayMask(*this);
+  }
+
   NS_DISPLAY_DECL_NAME("Mask", TYPE_MASK)
 
-  virtual bool TryMerge(nsDisplayItem* aItem) override;
+  virtual bool CanMerge(const nsDisplayItem* aItem) const override;
+
+  virtual void Merge(const nsDisplayItem* aItem) override
+  {
+    nsDisplayWrapList::Merge(aItem);
+
+    const nsDisplayMask* other = static_cast<const nsDisplayMask*>(aItem);
+    mEffectsBounds.UnionRect(mEffectsBounds,
+      other->mEffectsBounds + other->mFrame->GetOffsetTo(mFrame));
+  }
+
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager,
                                              const ContainerLayerParameters& aContainerParameters) override;
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters) override;
 
   virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
                                  nsRegion* aVisibleRegion) override;
 
   virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
   {
     return new nsDisplayMaskGeometry(this, aBuilder);
   }
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion* aInvalidRegion) override;
+                                         nsRegion* aInvalidRegion) const override;
 #ifdef MOZ_DUMP_PAINTING
   void PrintEffects(nsACString& aTo);
 #endif
 
   void PaintAsLayer(nsDisplayListBuilder* aBuilder,
                     gfxContext* aCtx,
                     LayerManager* aManager);
 
@@ -4617,39 +4899,61 @@ private:
 class nsDisplayFilter : public nsDisplaySVGEffects {
 public:
   nsDisplayFilter(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                   nsDisplayList* aList, bool aHandleOpacity);
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayFilter();
 #endif
 
+  virtual nsDisplayWrapList* Clone(nsDisplayListBuilder* aBuilder) const override
+  {
+    MOZ_COUNT_CTOR(nsDisplayFilter);
+    return new (aBuilder) nsDisplayFilter(*this);
+  }
+
   NS_DISPLAY_DECL_NAME("Filter", TYPE_FILTER)
 
-  virtual bool TryMerge(nsDisplayItem* aItem) override;
+  virtual bool CanMerge(const nsDisplayItem* aItem) const override
+  {
+    // Items for the same content element should be merged into a single
+    // compositing group.
+    return HasSameTypeAndClip(aItem) && HasSameContent(aItem);
+  }
+
+  virtual void Merge(const nsDisplayItem* aItem) override
+  {
+    nsDisplayWrapList::Merge(aItem);
+
+    const nsDisplayFilter* other = static_cast<const nsDisplayFilter*>(aItem);
+    mEffectsBounds.UnionRect(mEffectsBounds,
+      other->mEffectsBounds + other->mFrame->GetOffsetTo(mFrame));
+  }
+
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager,
                                              const ContainerLayerParameters& aContainerParameters) override;
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters) override;
   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
-                           bool* aSnap) override {
+                           bool* aSnap) const override
+  {
     *aSnap = false;
     return mEffectsBounds + ToReferenceFrame();
   }
   virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
                                  nsRegion* aVisibleRegion) override;
   virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
   {
     return new nsDisplayFilterGeometry(this, aBuilder);
   }
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion* aInvalidRegion) override;
+                                         nsRegion* aInvalidRegion) const override;
 #ifdef MOZ_DUMP_PAINTING
   void PrintEffects(nsACString& aTo);
 #endif
 
   void PaintAsLayer(nsDisplayListBuilder* aBuilder,
                     gfxContext* aCtx,
                     LayerManager* aManager);
 
@@ -4758,55 +5062,65 @@ public:
   virtual void Destroy(nsDisplayListBuilder* aBuilder) override
   {
     mStoredList.Destroy(aBuilder);
     nsDisplayItem::Destroy(aBuilder);
   }
 
   NS_DISPLAY_DECL_NAME("nsDisplayTransform", TYPE_TRANSFORM)
 
-  virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override
+  virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const override
   {
     if (mStoredList.GetComponentAlphaBounds(aBuilder).IsEmpty())
       return nsRect();
     bool snap;
     return GetBounds(aBuilder, &snap);
   }
 
-  virtual nsDisplayList* GetChildren() override { return mStoredList.GetChildren(); }
+  virtual nsDisplayList* GetChildren() const override
+  {
+    return mStoredList.GetChildren();
+  }
 
   virtual void HitTest(nsDisplayListBuilder *aBuilder, const nsRect& aRect,
                        HitTestState *aState, nsTArray<nsIFrame*> *aOutFrames) override;
-  virtual nsRect GetBounds(nsDisplayListBuilder *aBuilder, bool* aSnap) override;
+  virtual nsRect GetBounds(nsDisplayListBuilder *aBuilder,
+                           bool* aSnap) const override;
   virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder *aBuilder,
-                                   bool* aSnap) override;
-  virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder *aBuilder) override;
+                                   bool* aSnap) const override;
+  virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder *aBuilder) const override;
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters) override;
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager,
                                              const ContainerLayerParameters& aContainerParameters) override;
   virtual bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
                                        const StackingContextHelper& aSc,
                                        nsTArray<WebRenderParentCommand>& aParentCommands,
                                        mozilla::layers::WebRenderLayerManager* aManager,
                                        nsDisplayListBuilder* aDisplayListBuilder) override;
   virtual bool UpdateScrollData(mozilla::layers::WebRenderScrollData* aData,
                                 mozilla::layers::WebRenderLayerScrollData* aLayerData) override;
-  virtual bool ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder) override;
+  virtual bool ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder) const override;
   virtual bool ComputeVisibility(nsDisplayListBuilder *aBuilder,
                                  nsRegion *aVisibleRegion) override;
-  virtual bool TryMerge(nsDisplayItem *aItem) override;
-
-  virtual uint32_t GetPerFrameKey() override { return (mIndex << TYPE_BITS) | nsDisplayItem::GetPerFrameKey(); }
+
+  virtual bool CanMerge(const nsDisplayItem* aItem) const override
+  {
+    return false;
+  }
+
+  virtual uint32_t GetPerFrameKey() const override {
+    return (mIndex << TYPE_BITS) | nsDisplayItem::GetPerFrameKey();
+  }
 
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion* aInvalidRegion) override
+                                         nsRegion* aInvalidRegion) const override
   {
     // We don't need to compute an invalidation region since we have LayerTreeInvalidation
   }
 
   virtual const nsIFrame* ReferenceFrameForChildren() const override {
     // If we were created using a transform-getter, then we don't
     // belong to a transformed frame, and aren't a reference frame
     // for our children.
@@ -4830,17 +5144,17 @@ public:
   };
 
   /**
    * We include the perspective matrix from our containing block for the
    * purposes of visibility calculations, but we exclude it from the transform
    * we set on the layer (for rendering), since there will be an
    * nsDisplayPerspective created for that.
    */
-  const Matrix4x4& GetTransform();
+  const Matrix4x4& GetTransform() const;
   Matrix4x4 GetTransformForRendering();
 
   /**
    * Return the transform that is aggregation of all transform on the
    * preserves3d chain.
    */
   const Matrix4x4& GetAccumulatedPreserved3DTransform(nsDisplayListBuilder* aBuilder);
 
@@ -4872,17 +5186,17 @@ public:
    * transform.
    */
   static bool UntransformRect(const nsRect &aTransformedBounds,
                               const nsRect &aChildBounds,
                               const nsIFrame* aFrame,
                               nsRect *aOutRect);
 
   bool UntransformVisibleRect(nsDisplayListBuilder* aBuilder,
-                              nsRect* aOutRect);
+                              nsRect* aOutRect) const;
 
   static Point3D GetDeltaToTransformOrigin(const nsIFrame* aFrame,
                                            float aAppUnitsPerPixel,
                                            const nsRect* aBoundsOverride);
 
   /*
    * Returns true if aFrame has perspective applied from its containing
    * block.
@@ -4956,17 +5270,17 @@ public:
    * or NoPrerender if only the visible area should be rendered.
    * |aDirtyRect| is updated to the area that should be prerendered.
    */
   static PrerenderDecision ShouldPrerenderTransformedContent(nsDisplayListBuilder* aBuilder,
                                                              nsIFrame* aFrame,
                                                              nsRect* aDirtyRect);
   bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override;
 
-  bool MayBeAnimated(nsDisplayListBuilder* aBuilder);
+  bool MayBeAnimated(nsDisplayListBuilder* aBuilder) const;
 
   virtual void WriteDebugInfo(std::stringstream& aStream) override;
 
   // Force the layer created for this item not to extend 3D context.
   // See nsIFrame::BuildDisplayListForStackingContext()
   void SetNoExtendContext() { mNoExtendContext = true; }
 
   virtual void DoUpdateBoundsPreserves3D(nsDisplayListBuilder* aBuilder) override {
@@ -5030,27 +5344,27 @@ private:
 
   static Matrix4x4 GetResultingTransformMatrixInternal(const FrameTransformProperties& aProperties,
                                                        const nsPoint& aOrigin,
                                                        float aAppUnitsPerPixel,
                                                        uint32_t aFlags,
                                                        const nsRect* aBoundsOverride);
 
   StoreList mStoredList;
-  Matrix4x4 mTransform;
+  mutable Matrix4x4 mTransform;
   // Accumulated transform of ancestors on the preserves-3d chain.
   Matrix4x4 mTransformPreserves3D;
   ComputeTransformFunction mTransformGetter;
   AnimatedGeometryRoot* mAnimatedGeometryRootForChildren;
   AnimatedGeometryRoot* mAnimatedGeometryRootForScrollMetadata;
   nsRect mChildrenVisibleRect;
   uint32_t mIndex;
-  nsRect mBounds;
+  mutable nsRect mBounds;
   // True for mBounds is valid.
-  bool mHasBounds;
+  mutable bool mHasBounds;
   // Be forced not to extend 3D context.  Since we don't create a
   // transform item, a container layer, for every frames in a
   // preserves3d context, the transform items of a child preserves3d
   // context may extend the parent context not intented if the root of
   // the child preserves3d context doesn't create a transform item.
   // With this flags, we force the item not extending 3D context.
   bool mNoExtendContext;
   // This item is a separator between 3D rendering contexts, and
@@ -5073,55 +5387,58 @@ class nsDisplayPerspective : public nsDi
 
 public:
   NS_DISPLAY_DECL_NAME("nsDisplayPerspective", TYPE_PERSPECTIVE)
 
   nsDisplayPerspective(nsDisplayListBuilder* aBuilder, nsIFrame* aTransformFrame,
                        nsIFrame* aPerspectiveFrame,
                        nsDisplayList* aList);
 
-  virtual uint32_t GetPerFrameKey() override { return (mIndex << TYPE_BITS) | nsDisplayItem::GetPerFrameKey(); }
+  virtual uint32_t GetPerFrameKey() const override {
+    return (mIndex << TYPE_BITS) | nsDisplayItem::GetPerFrameKey();
+  }
 
   virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                        HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override
   {
     return mList.HitTest(aBuilder, aRect, aState, aOutFrames);
   }
 
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+                           bool* aSnap) const override
   {
     return mList.GetBounds(aBuilder, aSnap);
   }
 
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion* aInvalidRegion) override
+                                         nsRegion* aInvalidRegion) const override
   {}
 
   virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
-                                   bool* aSnap) override
+                                   bool* aSnap) const override
   {
     return mList.GetOpaqueRegion(aBuilder, aSnap);
   }
 
-  virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) override
+  virtual mozilla::Maybe<nscolor> IsUniform(nsDisplayListBuilder* aBuilder) const override
   {
     return mList.IsUniform(aBuilder);
   }
 
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters) override;
   bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
                                const StackingContextHelper& aSc,
                                nsTArray<WebRenderParentCommand>& aParentCommands,
                                mozilla::layers::WebRenderLayerManager* aManager,
                                nsDisplayListBuilder* aDisplayListBuilder) override;
 
-  virtual bool ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder) override
+  virtual bool ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder) const override
   {
     if (!mList.GetChildren()->GetTop()) {
       return false;
     }
     return mList.GetChildren()->GetTop()->ShouldBuildLayerEvenIfInvisible(aBuilder);
   }
 
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
@@ -5129,19 +5446,28 @@ public:
                                              const ContainerLayerParameters& aContainerParameters) override;
 
   virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
                                  nsRegion* aVisibleRegion) override
   {
     mList.RecomputeVisibility(aBuilder, aVisibleRegion);
     return true;
   }
-  virtual nsDisplayList* GetSameCoordinateSystemChildren() override { return mList.GetChildren(); }
-  virtual nsDisplayList* GetChildren() override { return mList.GetChildren(); }
-  virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override
+
+  virtual nsDisplayList* GetSameCoordinateSystemChildren() const override
+  {
+    return mList.GetChildren();
+  }
+
+  virtual nsDisplayList* GetChildren() const override
+  {
+    return mList.GetChildren();
+  }
+
+  virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const override
   {
     return mList.GetComponentAlphaBounds(aBuilder);
   }
 
   nsIFrame* TransformFrame() { return mTransformFrame; }
 
   virtual int32_t ZIndex() const override;
 
@@ -5183,17 +5509,17 @@ public:
 
   explicit nsCharClipDisplayItem(nsIFrame* aFrame)
     : nsDisplayItem(aFrame) {}
 
   virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override;
 
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion* aInvalidRegion) override;
+                                         nsRegion* aInvalidRegion) const override;
 
   struct ClipEdges {
     ClipEdges(const nsDisplayItem& aItem,
               nscoord aVisIStartEdge, nscoord aVisIEndEdge) {
       nsRect r = aItem.Frame()->GetScrollableOverflowRect() +
                  aItem.ToReferenceFrame();
       if (aItem.Frame()->GetWritingMode().IsVertical()) {
         mVisIStart = aVisIStartEdge > 0 ? r.y + aVisIStartEdge : nscoord_MIN;
--- a/layout/svg/SVGGeometryFrame.cpp
+++ b/layout/svg/SVGGeometryFrame.cpp
@@ -82,17 +82,17 @@ public:
 
   nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
   {
     return new nsDisplayItemGenericImageGeometry(this, aBuilder);
   }
 
   void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                  const nsDisplayItemGeometry* aGeometry,
-                                 nsRegion *aInvalidRegion) override;
+                                 nsRegion *aInvalidRegion) const override;
 };
 
 void
 nsDisplaySVGGeometry::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                               HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames)
 {
   SVGGeometryFrame *frame = static_cast<SVGGeometryFrame*>(mFrame);
   nsPoint pointRelativeToReferenceFrame = aRect.Center();
@@ -129,20 +129,19 @@ nsDisplaySVGGeometry::Paint(nsDisplayLis
 
   static_cast<SVGGeometryFrame*>(mFrame)->PaintSVG(*aCtx,
                                                    tm, imgParams);
 
   nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, imgParams.result);
 }
 
 void
-nsDisplaySVGGeometry::ComputeInvalidationRegion(
-  nsDisplayListBuilder* aBuilder,
-  const nsDisplayItemGeometry* aGeometry,
-  nsRegion* aInvalidRegion)
+nsDisplaySVGGeometry::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+                                                const nsDisplayItemGeometry* aGeometry,
+                                                nsRegion* aInvalidRegion) const
 {
   auto geometry =
     static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
 
   if (aBuilder->ShouldSyncDecodeImages() &&
       geometry->ShouldInvalidateToSyncDecodeImages()) {
     bool snap;
     aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
--- a/layout/svg/SVGTextFrame.cpp
+++ b/layout/svg/SVGTextFrame.cpp
@@ -3062,50 +3062,45 @@ SVGTextDrawPathCallbacks::StrokeGeometry
 // ============================================================================
 // SVGTextFrame
 
 // ----------------------------------------------------------------------------
 // Display list item
 
 class nsDisplaySVGText : public nsDisplayItem {
 public:
-  nsDisplaySVGText(nsDisplayListBuilder* aBuilder,
-                   SVGTextFrame* aFrame)
-    : nsDisplayItem(aBuilder, aFrame),
-      mDisableSubpixelAA(false)
+  nsDisplaySVGText(nsDisplayListBuilder* aBuilder, SVGTextFrame* aFrame)
+    : nsDisplayItem(aBuilder, aFrame)
   {
     MOZ_COUNT_CTOR(nsDisplaySVGText);
     MOZ_ASSERT(aFrame, "Must have a frame!");
   }
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplaySVGText() {
     MOZ_COUNT_DTOR(nsDisplaySVGText);
   }
 #endif
 
   NS_DISPLAY_DECL_NAME("nsDisplaySVGText", TYPE_SVG_TEXT)
 
-  virtual void DisableComponentAlpha() override {
-    mDisableSubpixelAA = true;
-  }
   virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                        HitTestState* aState,
                        nsTArray<nsIFrame*> *aOutFrames) override;
   virtual void Paint(nsDisplayListBuilder* aBuilder,
                      gfxContext* aCtx) override;
   nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
   {
     return new nsDisplayItemGenericImageGeometry(this, aBuilder);
   }
-  virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override {
+
+  virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const override
+  {
     bool snap;
     return GetBounds(aBuilder, &snap);
   }
-private:
-  bool mDisableSubpixelAA;
 };
 
 void
 nsDisplaySVGText::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                           HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames)
 {
   SVGTextFrame *frame = static_cast<SVGTextFrame*>(mFrame);
   nsPoint pointRelativeToReferenceFrame = aRect.Center();
--- a/layout/svg/nsSVGOuterSVGFrame.cpp
+++ b/layout/svg/nsSVGOuterSVGFrame.cpp
@@ -556,17 +556,17 @@ public:
   virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                        HitTestState* aState,
                        nsTArray<nsIFrame*> *aOutFrames) override;
   virtual void Paint(nsDisplayListBuilder* aBuilder,
                      gfxContext* aCtx) override;
 
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion* aInvalidRegion) override;
+                                         nsRegion* aInvalidRegion) const override;
 
   nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
   {
     return new nsDisplayItemGenericImageGeometry(this, aBuilder);
   }
 
   NS_DISPLAY_DECL_NAME("SVGOuterSVG", TYPE_SVG_OUTER_SVG)
 };
@@ -657,17 +657,17 @@ nsSVGOuterSVGFrame::FindInvalidatedForei
     }
   }
   return result;
 }
 
 void
 nsDisplayOuterSVG::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                              const nsDisplayItemGeometry* aGeometry,
-                                             nsRegion* aInvalidRegion)
+                                             nsRegion* aInvalidRegion) const
 {
   nsSVGOuterSVGFrame *frame = static_cast<nsSVGOuterSVGFrame*>(mFrame);
   frame->InvalidateSVG(frame->FindInvalidatedForeignObjectFrameChildren(frame));
 
   nsRegion result = frame->GetInvalidRegion();
   result.MoveBy(ToReferenceFrame());
   frame->ClearInvalidRegion();
 
--- a/layout/tables/nsTableCellFrame.cpp
+++ b/layout/tables/nsTableCellFrame.cpp
@@ -423,33 +423,33 @@ public:
   virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                        HitTestState* aState,
                        nsTArray<nsIFrame*> *aOutFrames) override {
     aOutFrames->AppendElement(mFrame);
   }
   virtual void Paint(nsDisplayListBuilder* aBuilder,
                      gfxContext* aCtx) override;
   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
-                           bool* aSnap) override;
+                           bool* aSnap) const override;
   NS_DISPLAY_DECL_NAME("TableCellBackground", TYPE_TABLE_CELL_BACKGROUND)
 };
 
 void nsDisplayTableCellBackground::Paint(nsDisplayListBuilder* aBuilder,
                                          gfxContext* aCtx)
 {
   DrawResult result = static_cast<nsTableCellFrame*>(mFrame)->
     PaintBackground(*aCtx, mVisibleRect, ToReferenceFrame(),
                     aBuilder->GetBackgroundPaintFlags());
 
   nsDisplayTableItemGeometry::UpdateDrawResult(this, result);
 }
 
 nsRect
 nsDisplayTableCellBackground::GetBounds(nsDisplayListBuilder* aBuilder,
-                                        bool* aSnap)
+                                        bool* aSnap) const
 {
   // revert from nsDisplayTableItem's implementation ... cell backgrounds
   // don't overflow the cell
   return nsDisplayItem::GetBounds(aBuilder, aSnap);
 }
 
 void nsTableCellFrame::InvalidateFrame(uint32_t aDisplayItemKey)
 {
--- a/layout/tables/nsTableFrame.cpp
+++ b/layout/tables/nsTableFrame.cpp
@@ -1196,17 +1196,19 @@ nsTableFrame::GetChildList(ChildListID a
 void
 nsTableFrame::GetChildLists(nsTArray<ChildList>* aLists) const
 {
   nsContainerFrame::GetChildLists(aLists);
   mColGroups.AppendIfNonempty(aLists, kColGroupList);
 }
 
 nsRect
-nsDisplayTableItem::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) {
+nsDisplayTableItem::GetBounds(nsDisplayListBuilder* aBuilder,
+                              bool* aSnap) const
+{
   *aSnap = false;
   return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
 }
 
 void
 nsDisplayTableItem::UpdateForFrameBackground(nsIFrame* aFrame)
 {
   nsStyleContext *bgSC;
@@ -1223,17 +1225,17 @@ nsDisplayTableItem::AllocateGeometry(nsD
 {
   return new nsDisplayTableItemGeometry(this, aBuilder,
       mFrame->GetOffsetTo(mFrame->PresContext()->PresShell()->GetRootFrame()));
 }
 
 void
 nsDisplayTableItem::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                               const nsDisplayItemGeometry* aGeometry,
-                                              nsRegion *aInvalidRegion)
+                                              nsRegion *aInvalidRegion) const
 {
   auto geometry =
     static_cast<const nsDisplayTableItemGeometry*>(aGeometry);
 
   bool invalidateForAttachmentFixed = false;
   if (mDrawsBackground && mPartHasFixedBackground) {
     nsPoint frameOffsetToViewport = mFrame->GetOffsetTo(
         mFrame->PresContext()->PresShell()->GetRootFrame());
--- a/layout/tables/nsTableFrame.h
+++ b/layout/tables/nsTableFrame.h
@@ -52,22 +52,23 @@ public:
       nsDisplayItem(aBuilder, aFrame),
       mPartHasFixedBackground(false),
       mDrawsBackground(aDrawsBackground) {}
 
   // With collapsed borders, parts of the collapsed border can extend outside
   // the table part frames, so allow this display element to blow out to our
   // overflow rect. This is also useful for row frames that have spanning
   // cells extending outside them.
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+                           bool* aSnap) const override;
 
   virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override;
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion *aInvalidRegion) override;
+                                         nsRegion *aInvalidRegion) const override;
 
   void UpdateForFrameBackground(nsIFrame* aFrame);
 
 private:
   bool mPartHasFixedBackground;
   bool mDrawsBackground;
 };
 
--- a/layout/xul/nsGroupBoxFrame.cpp
+++ b/layout/xul/nsGroupBoxFrame.cpp
@@ -95,37 +95,36 @@ public:
   virtual ~nsDisplayXULGroupBorder() {
     MOZ_COUNT_DTOR(nsDisplayXULGroupBorder);
   }
 #endif
 
   nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override;
   void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                  const nsDisplayItemGeometry* aGeometry,
-                                 nsRegion *aInvalidRegion) override;
+                                 nsRegion *aInvalidRegion) const override;
   virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                        HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override {
     aOutFrames->AppendElement(mFrame);
   }
   virtual void Paint(nsDisplayListBuilder* aBuilder,
                      gfxContext* aCtx) override;
   NS_DISPLAY_DECL_NAME("XULGroupBackground", TYPE_XUL_GROUP_BACKGROUND)
 };
 
 nsDisplayItemGeometry*
 nsDisplayXULGroupBorder::AllocateGeometry(nsDisplayListBuilder* aBuilder)
 {
   return new nsDisplayItemGenericImageGeometry(this, aBuilder);
 }
 
 void
-nsDisplayXULGroupBorder::ComputeInvalidationRegion(
-  nsDisplayListBuilder* aBuilder,
-  const nsDisplayItemGeometry* aGeometry,
-  nsRegion* aInvalidRegion)
+nsDisplayXULGroupBorder::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+                                                   const nsDisplayItemGeometry* aGeometry,
+                                                   nsRegion* aInvalidRegion) const
 {
   auto geometry =
     static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
 
   if (aBuilder->ShouldSyncDecodeImages() &&
       geometry->ShouldInvalidateToSyncDecodeImages()) {
     bool snap;
     aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
--- a/layout/xul/nsImageBoxFrame.cpp
+++ b/layout/xul/nsImageBoxFrame.cpp
@@ -581,17 +581,17 @@ nsDisplayItemGeometry*
 nsDisplayXULImage::AllocateGeometry(nsDisplayListBuilder* aBuilder)
 {
   return new nsDisplayItemGenericImageGeometry(this, aBuilder);
 }
 
 void
 nsDisplayXULImage::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                              const nsDisplayItemGeometry* aGeometry,
-                                             nsRegion* aInvalidRegion)
+                                             nsRegion* aInvalidRegion) const
 {
   auto boxFrame = static_cast<nsImageBoxFrame*>(mFrame);
   auto geometry =
     static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
 
   if (aBuilder->ShouldSyncDecodeImages() &&
       boxFrame->mImageRequest &&
       geometry->ShouldInvalidateToSyncDecodeImages()) {
@@ -624,17 +624,17 @@ nsDisplayXULImage::GetImage()
 
   nsCOMPtr<imgIContainer> imgCon;
   imageFrame->mImageRequest->GetImage(getter_AddRefs(imgCon));
 
   return imgCon.forget();
 }
 
 nsRect
-nsDisplayXULImage::GetDestRect()
+nsDisplayXULImage::GetDestRect() const
 {
   Maybe<nsPoint> anchorPoint;
   return static_cast<nsImageBoxFrame*>(mFrame)->GetDestRect(ToReferenceFrame(), anchorPoint);
 }
 
 bool
 nsImageBoxFrame::CanOptimizeToImageLayer()
 {
--- a/layout/xul/nsImageBoxFrame.h
+++ b/layout/xul/nsImageBoxFrame.h
@@ -151,26 +151,27 @@ public:
   virtual ~nsDisplayXULImage() {
     MOZ_COUNT_DTOR(nsDisplayXULImage);
   }
 #endif
 
   virtual bool CanOptimizeToImageLayer(LayerManager* aManager,
                                        nsDisplayListBuilder* aBuilder) override;
   virtual already_AddRefed<imgIContainer> GetImage() override;
-  virtual nsRect GetDestRect() override;
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override
+  virtual nsRect GetDestRect() const override;
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+                           bool* aSnap) const override
   {
     *aSnap = true;
     return nsRect(ToReferenceFrame(), Frame()->GetSize());
   }
   virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override;
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion* aInvalidRegion) override;
+                                         nsRegion* aInvalidRegion) const override;
   // Doesn't handle HitTest because nsLeafBoxFrame already creates an
   // event receiver for us
   virtual void Paint(nsDisplayListBuilder* aBuilder,
                      gfxContext* aCtx) override;
 
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters) override;
--- a/layout/xul/nsTextBoxFrame.cpp
+++ b/layout/xul/nsTextBoxFrame.cpp
@@ -280,46 +280,38 @@ nsTextBoxFrame::UpdateAttributes(nsIAtom
         aResize = true;
     }
 
 }
 
 class nsDisplayXULTextBox final : public nsDisplayItem
 {
 public:
-  nsDisplayXULTextBox(nsDisplayListBuilder* aBuilder,
-                      nsTextBoxFrame* aFrame) :
-    nsDisplayItem(aBuilder, aFrame),
-    mDisableSubpixelAA(false)
+  nsDisplayXULTextBox(nsDisplayListBuilder* aBuilder, nsTextBoxFrame* aFrame)
+    : nsDisplayItem(aBuilder, aFrame)
   {
     MOZ_COUNT_CTOR(nsDisplayXULTextBox);
   }
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayXULTextBox() {
     MOZ_COUNT_DTOR(nsDisplayXULTextBox);
   }
 #endif
 
   virtual void Paint(nsDisplayListBuilder* aBuilder,
                      gfxContext* aCtx) override;
   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
-                           bool* aSnap) override;
+                           bool* aSnap) const override;
   NS_DISPLAY_DECL_NAME("XULTextBox", TYPE_XUL_TEXT_BOX)
 
-  virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override;
-
-  virtual void DisableComponentAlpha() override {
-    mDisableSubpixelAA = true;
-  }
+  virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const override;
 
   void PaintTextToContext(gfxContext* aCtx,
                           nsPoint aOffset,
                           const nscolor* aColor);
-
-  bool mDisableSubpixelAA;
 };
 
 static void
 PaintTextShadowCallback(gfxContext* aCtx,
                         nsPoint aShadowOffset,
                         const nscolor& aShadowColor,
                         void* aData)
 {
@@ -351,23 +343,25 @@ nsDisplayXULTextBox::PaintTextToContext(
                                         nsPoint aOffset,
                                         const nscolor* aColor)
 {
   static_cast<nsTextBoxFrame*>(mFrame)->
     PaintTitle(*aCtx, mVisibleRect, ToReferenceFrame() + aOffset, aColor);
 }
 
 nsRect
-nsDisplayXULTextBox::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) {
+nsDisplayXULTextBox::GetBounds(nsDisplayListBuilder* aBuilder,
+                               bool* aSnap) const
+{
   *aSnap = false;
   return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
 }
 
 nsRect
-nsDisplayXULTextBox::GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder)
+nsDisplayXULTextBox::GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const
 {
   return static_cast<nsTextBoxFrame*>(mFrame)->GetComponentAlphaBounds() +
       ToReferenceFrame();
 }
 
 void
 nsTextBoxFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                  const nsDisplayListSet& aLists)
@@ -1028,17 +1022,17 @@ nsTextBoxFrame::DoXULLayout(nsBoxLayoutS
       vis.UnionRect(vis, nsLayoutUtils::GetTextShadowRectsUnion(mTextDrawRect, this));
     }
     FinishAndStoreOverflow(overflow, GetSize());
 
     return rv;
 }
 
 nsRect
-nsTextBoxFrame::GetComponentAlphaBounds()
+nsTextBoxFrame::GetComponentAlphaBounds() const
 {
   if (StyleText()->mTextShadow) {
     return GetVisualOverflowRectRelativeToSelf();
   }
   return mTextDrawRect;
 }
 
 bool
--- a/layout/xul/nsTextBoxFrame.h
+++ b/layout/xul/nsTextBoxFrame.h
@@ -51,17 +51,17 @@ public:
 
   virtual ~nsTextBoxFrame();
 
   void PaintTitle(gfxContext&          aRenderingContext,
                   const nsRect&        aDirtyRect,
                   nsPoint              aPt,
                   const nscolor*       aOverrideColor);
 
-  nsRect GetComponentAlphaBounds();
+  nsRect GetComponentAlphaBounds() const;
 
   virtual bool ComputesOwnOverflowArea() override;
 
   void GetCroppedTitle(nsString& aTitle) const { aTitle = mCroppedTitle; }
 
   virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override;
 
 protected:
--- a/layout/xul/tree/nsTreeBodyFrame.cpp
+++ b/layout/xul/tree/nsTreeBodyFrame.cpp
@@ -2769,35 +2769,35 @@ nsTreeBodyFrame::HandleEvent(nsPresConte
     }
   }
 
   return NS_OK;
 }
 
 class nsDisplayTreeBody final : public nsDisplayItem {
 public:
-  nsDisplayTreeBody(nsDisplayListBuilder* aBuilder, nsFrame* aFrame) :
-    nsDisplayItem(aBuilder, aFrame),
-    mDisableSubpixelAA(false) {
+  nsDisplayTreeBody(nsDisplayListBuilder* aBuilder, nsFrame* aFrame)
+    : nsDisplayItem(aBuilder, aFrame)
+  {
     MOZ_COUNT_CTOR(nsDisplayTreeBody);
   }
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayTreeBody() {
     MOZ_COUNT_DTOR(nsDisplayTreeBody);
   }
 #endif
 
   nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
   {
     return new nsDisplayItemGenericImageGeometry(this, aBuilder);
   }
 
   void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                  const nsDisplayItemGeometry* aGeometry,
-                                 nsRegion *aInvalidRegion) override
+                                 nsRegion *aInvalidRegion) const override
   {
     auto geometry =
       static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
 
     if (aBuilder->ShouldSyncDecodeImages() &&
         geometry->ShouldInvalidateToSyncDecodeImages()) {
       bool snap;
       aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
@@ -2816,26 +2816,21 @@ public:
     DrawResult result = static_cast<nsTreeBodyFrame*>(mFrame)
       ->PaintTreeBody(*aCtx, mVisibleRect, ToReferenceFrame(), aBuilder);
 
     nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
   }
 
   NS_DISPLAY_DECL_NAME("XULTreeBody", TYPE_XUL_TREE_BODY)
 
-  virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override
+  virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const override
   {
     bool snap;
     return GetBounds(aBuilder, &snap);
   }
-  virtual void DisableComponentAlpha() override {
-    mDisableSubpixelAA = true;
-  }
-
-  bool mDisableSubpixelAA;
 };
 
 // Painting routines
 void
 nsTreeBodyFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                   const nsDisplayListSet& aLists)
 {
   // REVIEW: why did we paint if we were collapsed? that makes no sense!
--- a/memory/build/mozjemalloc.cpp
+++ b/memory/build/mozjemalloc.cpp
@@ -159,20 +159,16 @@
 #define _CRT_SPINCOUNT 5000
 #include <io.h>
 #include <windows.h>
 #include <intrin.h>
 
 #define	SIZE_T_MAX SIZE_MAX
 #define	STDERR_FILENO 2
 
-#ifndef NO_TLS
-static unsigned long tlsIndex = 0xffffffff;
-#endif
-
 /* use MSVC intrinsics */
 #pragma intrinsic(_BitScanForward)
 static __forceinline int
 ffs(int x)
 {
 	unsigned long i;
 
 	if (_BitScanForward(&i, x) != 0)
@@ -247,16 +243,17 @@ typedef long ssize_t;
 #include <mach/mach_error.h>
 #include <mach/mach_init.h>
 #include <mach/vm_map.h>
 #include <malloc/malloc.h>
 #endif
 
 #endif
 
+#include "mozilla/ThreadLocal.h"
 #include "mozjemalloc_types.h"
 
 /* Some tools, such as /dev/dsp wrappers, LD_PRELOAD libraries that
  * happen to override mmap() and call dlsym() from their overridden
  * mmap(). The problem is that dlsym() calls malloc(), and this ends
  * up in a dead lock in jemalloc.
  * On these systems, we prefer to directly use the system call.
  * We do that for Linux systems and kfreebsd with GNU userland.
@@ -300,20 +297,16 @@ void *_mmap(void *addr, size_t length, i
 #endif
 #endif
 }
 #define mmap _mmap
 #define munmap(a, l) syscall(SYS_munmap, a, l)
 #endif
 #endif
 
-#ifdef XP_DARWIN
-static pthread_key_t tlsIndex;
-#endif
-
 #ifdef XP_WIN
    /* MSVC++ does not support C99 variable-length arrays. */
 #  define RB_NO_C99_VARARRAYS
 #endif
 #include "rb.h"
 
 #ifdef MOZ_DEBUG
    /* Disable inlining to make debugging easier. */
@@ -329,30 +322,24 @@ static pthread_key_t tlsIndex;
 
 /* Minimum alignment of non-tiny allocations is 2^QUANTUM_2POW_MIN bytes. */
 #  define QUANTUM_2POW_MIN      4
 #if defined(_WIN64) || defined(__LP64__)
 #  define SIZEOF_PTR_2POW       3
 #else
 #  define SIZEOF_PTR_2POW       2
 #endif
-#define PIC
 
 #define	SIZEOF_PTR		(1U << SIZEOF_PTR_2POW)
 
 /* sizeof(int) == (1U << SIZEOF_INT_2POW). */
 #ifndef SIZEOF_INT_2POW
 #  define SIZEOF_INT_2POW	2
 #endif
 
-/* We can't use TLS in non-PIC programs, since TLS relies on loader magic. */
-#if (!defined(PIC) && !defined(NO_TLS))
-#  define NO_TLS
-#endif
-
 /*
  * Size and alignment of memory chunks that are allocated by the OS's virtual
  * memory system.
  */
 #define	CHUNK_2POW_DEFAULT	20
 /* Maximum number of dirty pages per arena. */
 #define	DIRTY_MAX_DEFAULT	(1U << 8)
 
@@ -741,16 +728,20 @@ struct arena_t {
 
 	/*
 	 * Current count of pages within unused runs that are potentially
 	 * dirty, and for which madvise(... MADV_FREE) has not been called.  By
 	 * tracking this, we can institute a limit on how much dirty unused
 	 * memory is mapped for each arena.
 	 */
 	size_t			ndirty;
+	/*
+	 * Maximum value allowed for ndirty.
+	 */
+	size_t			dirty_max;
 
 	/*
 	 * Size/address-ordered tree of this arena's available runs.  This tree
 	 * is used for first-best-fit run allocation.
 	 */
 	arena_avail_tree_t	runs_avail;
 
 	/*
@@ -964,21 +955,27 @@ static size_t		base_committed;
  * arenas array are necessarily used; arenas are created lazily as needed.
  */
 static arena_t		**arenas;
 static unsigned		narenas;
 static malloc_spinlock_t arenas_lock; /* Protects arenas initialization. */
 
 #ifndef NO_TLS
 /*
- * Map of pthread_self() --> arenas[???], used for selecting an arena to use
- * for allocations.
+ * The arena associated with the current thread (per jemalloc_thread_local_arena)
+ * On OSX, __thread/thread_local circles back calling malloc to allocate storage
+ * on first access on each thread, which leads to an infinite loop, but
+ * pthread-based TLS somehow doesn't have this problem.
+ * On Windows, we use Tls{Get,Set}Value-based TLS for historical reasons.
+ * TODO: we may want to use native TLS instead.
  */
 #if !defined(XP_WIN) && !defined(XP_DARWIN)
-static __thread arena_t	*arenas_map;
+static MOZ_THREAD_LOCAL(arena_t*) thread_arena;
+#else
+static mozilla::detail::ThreadLocal<arena_t*, mozilla::detail::ThreadLocalKeyStorage> thread_arena;
 #endif
 #endif
 
 /*******************************/
 /*
  * Runtime configuration options.
  */
 const uint8_t kAllocJunk = 0xe4;
@@ -2252,80 +2249,69 @@ chunk_dealloc(void *chunk, size_t size, 
 /*
  * Begin arena.
  */
 
 static inline arena_t *
 thread_local_arena(bool enabled)
 {
 #ifndef NO_TLS
-	arena_t *arena;
-
-	if (enabled) {
-		/* The arena will essentially be leaked if this function is
-		 * called with `false`, but it doesn't matter at the moment.
-		 * because in practice nothing actually calls this function
-		 * with `false`, except maybe at shutdown. */
-		arena = arenas_extend();
-	} else {
-		malloc_spin_lock(&arenas_lock);
-		arena = arenas[0];
-		malloc_spin_unlock(&arenas_lock);
-	}
-#ifdef XP_WIN
-	TlsSetValue(tlsIndex, arena);
-#elif defined(XP_DARWIN)
-	pthread_setspecific(tlsIndex, arena);
+  arena_t *arena;
+
+  if (enabled) {
+    /* The arena will essentially be leaked if this function is
+     * called with `false`, but it doesn't matter at the moment.
+     * because in practice nothing actually calls this function
+     * with `false`, except maybe at shutdown. */
+    arena = arenas_extend();
+  } else {
+    malloc_spin_lock(&arenas_lock);
+    arena = arenas[0];
+    malloc_spin_unlock(&arenas_lock);
+  }
+  thread_arena.set(arena);
+  return arena;
 #else
-	arenas_map = arena;
-#endif
-
-	return arena;
-#else
-	return arenas[0];
+  return arenas[0];
 #endif
 }
 
 template<> inline void
 MozJemalloc::jemalloc_thread_local_arena(bool aEnabled)
 {
   thread_local_arena(aEnabled);
 }
 
 /*
  * Choose an arena based on a per-thread value.
  */
 static inline arena_t *
-choose_arena(void)
+choose_arena(size_t size)
 {
-	arena_t *ret;
-
-	/*
-	 * We can only use TLS if this is a PIC library, since for the static
-	 * library version, libc's malloc is used by TLS allocation, which
-	 * introduces a bootstrapping issue.
-	 */
+  arena_t *ret = nullptr;
+
+  /*
+   * We can only use TLS if this is a PIC library, since for the static
+   * library version, libc's malloc is used by TLS allocation, which
+   * introduces a bootstrapping issue.
+   */
 #ifndef NO_TLS
-
-#  ifdef XP_WIN
-	ret = (arena_t*)TlsGetValue(tlsIndex);
-#  elif defined(XP_DARWIN)
-	ret = (arena_t*)pthread_getspecific(tlsIndex);
-#  else
-	ret = arenas_map;
-#  endif
-
-	if (!ret) {
-                ret = thread_local_arena(false);
-	}
+  // Only use a thread local arena for small sizes.
+  if (size <= small_max) {
+    ret = thread_arena.get();
+  }
+
+  if (!ret) {
+    ret = thread_local_arena(false);
+  }
 #else
-	ret = arenas[0];
+  ret = arenas[0];
 #endif
-	MOZ_DIAGNOSTIC_ASSERT(ret);
-	return (ret);
+  MOZ_DIAGNOSTIC_ASSERT(ret);
+  return (ret);
 }
 
 static inline int
 arena_chunk_comp(arena_chunk_t *a, arena_chunk_t *b)
 {
 	uintptr_t a_chunk = (uintptr_t)a;
 	uintptr_t b_chunk = (uintptr_t)b;
 
@@ -2812,26 +2798,26 @@ arena_run_alloc(arena_t *arena, arena_bi
 }
 
 static void
 arena_purge(arena_t *arena, bool all)
 {
 	arena_chunk_t *chunk;
 	size_t i, npages;
 	/* If all is set purge all dirty pages. */
-	size_t dirty_max = all ? 1 : opt_dirty_max;
+	size_t dirty_max = all ? 1 : arena->dirty_max;
 #ifdef MOZ_DEBUG
 	size_t ndirty = 0;
 	rb_foreach_begin(arena_chunk_t, link_dirty, &arena->chunks_dirty,
 	    chunk) {
 		ndirty += chunk->ndirty;
 	} rb_foreach_end(arena_chunk_t, link_dirty, &arena->chunks_dirty, chunk)
 	MOZ_ASSERT(ndirty == arena->ndirty);
 #endif
-	MOZ_DIAGNOSTIC_ASSERT(all || (arena->ndirty > opt_dirty_max));
+	MOZ_DIAGNOSTIC_ASSERT(all || (arena->ndirty > arena->dirty_max));
 
 	/*
 	 * Iterate downward through chunks until enough dirty memory has been
 	 * purged.  Terminate as soon as possible in order to minimize the
 	 * number of system calls, even if a chunk has only been partially
 	 * purged.
 	 */
 	while (arena->ndirty > (dirty_max >> 1)) {
@@ -3001,18 +2987,18 @@ arena_run_dalloc(arena_t *arena, arena_r
 	/* Insert into runs_avail, now that coalescing is complete. */
 	arena_avail_tree_insert(&arena->runs_avail, &chunk->map[run_ind]);
 
 	/* Deallocate chunk if it is now completely unused. */
 	if ((chunk->map[arena_chunk_header_npages].bits & (~pagesize_mask |
 	    CHUNK_MAP_ALLOCATED)) == arena_maxclass)
 		arena_chunk_dealloc(arena, chunk);
 
-	/* Enforce opt_dirty_max. */
-	if (arena->ndirty > opt_dirty_max)
+	/* Enforce dirty_max. */
+	if (arena->ndirty > arena->dirty_max)
 		arena_purge(arena, false);
 }
 
 static void
 arena_run_trim_head(arena_t *arena, arena_chunk_t *chunk, arena_run_t *run,