Bug 1340987 - (Part 5) Apply changes for subdialogs.js to in-content and fix tests. r=MattN
authorScott Wu <scottcwwu@gmail.com>
Mon, 05 Jun 2017 17:00:47 +0800
changeset 412965 73f9f9a12e3510cd13f835640c99be921b545574
parent 412964 fb8ec9403cd5b0b957937aea130afe1019bf6025
child 412966 cc0069335c6d0eccda9501ff9a5cf94eb1b5d587
push id1490
push usermtabara@mozilla.com
push dateMon, 31 Jul 2017 14:08:16 +0000
treeherdermozilla-release@70e32e6bf15e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN
bugs1340987
milestone55.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
Bug 1340987 - (Part 5) Apply changes for subdialogs.js to in-content and fix tests. r=MattN MozReview-Commit-ID: D729DVbZw9I
browser/components/preferences/in-content/preferences.xul
browser/components/preferences/in-content/subdialogs.js
browser/components/preferences/in-content/tests/browser_advanced_siteData.js
browser/components/preferences/in-content/tests/browser_advanced_update.js
browser/components/preferences/in-content/tests/browser_basic_rebuild_fonts_test.js
browser/components/preferences/in-content/tests/browser_cookies_exceptions.js
browser/components/preferences/in-content/tests/browser_subdialogs.js
browser/components/preferences/in-content/tests/head.js
browser/themes/shared/incontentprefs-old/preferences.inc.css
--- a/browser/components/preferences/in-content/preferences.xul
+++ b/browser/components/preferences/in-content/preferences.xul
@@ -197,28 +197,27 @@
 #include content.xul
 #include security.xul
 #include sync.xul
       </prefpane>
     </vbox>
 
   </hbox>
 
-    <vbox id="dialogOverlay" align="center" pack="center">
-      <groupbox id="dialogBox"
-                orient="vertical"
-                pack="end"
-                role="dialog"
-                aria-labelledby="dialogTitle">
-        <caption flex="1" align="center">
-          <label id="dialogTitle" flex="1"></label>
-          <button id="dialogClose"
-                  class="close-icon"
-                  aria-label="&preferencesCloseButton.label;"/>
-        </caption>
-        <browser id="dialogFrame"
-                 name="dialogFrame"
-                 autoscroll="false"
-                 disablehistory="true"/>
-      </groupbox>
-    </vbox>
+  <stack id="dialogStack" hidden="true"/>
+  <vbox id="dialogTemplate" class="dialogOverlay" align="center" pack="center" topmost="true" hidden="true">
+    <groupbox class="dialogBox"
+              orient="vertical"
+              pack="end"
+              role="dialog"
+              aria-labelledby="dialogTitle">
+      <caption flex="1" align="center">
+        <label class="dialogTitle" flex="1"></label>
+        <button class="dialogClose close-icon"
+                aria-label="&preferencesCloseButton.label;"/>
+      </caption>
+      <browser class="dialogFrame"
+               autoscroll="false"
+               disablehistory="true"/>
+    </groupbox>
+  </vbox>
   </stack>
 </page>
--- a/browser/components/preferences/in-content/subdialogs.js
+++ b/browser/components/preferences/in-content/subdialogs.js
@@ -2,74 +2,96 @@
    - 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 ../../../base/content/utilityOverlay.js */
 /* import-globals-from preferences.js */
 
 "use strict";
 
-var gSubDialog = {
+/**
+ * SubDialog constructor creates a new subdialog from a template and appends
+ * it to the parentElement.
+ * @param {DOMNode} template: The template is copied to create a new dialog.
+ * @param {DOMNode} parentElement: New dialog is appended onto parentElement.
+ * @param {String}  id: A unique identifier for the dialog.
+ */
+function SubDialog({template, parentElement, id}) {
+  this._id = id;
+
+  this._overlay = template.cloneNode(true);
+  this._frame = this._overlay.querySelector(".dialogFrame");
+  this._box = this._overlay.querySelector(".dialogBox");
+  this._closeButton = this._overlay.querySelector(".dialogClose");
+  this._titleElement = this._overlay.querySelector(".dialogTitle");
+
+  this._overlay.id = `dialogOverlay-${id}`;
+  this._frame.setAttribute("name", `dialogFrame-${id}`);
+  this._frameCreated = new Promise(resolve => {
+    this._frame.addEventListener("load", resolve, {once: true});
+  });
+
+  parentElement.appendChild(this._overlay);
+  this._overlay.hidden = false;
+}
+
+SubDialog.prototype = {
   _closingCallback: null,
   _closingEvent: null,
   _isClosing: false,
   _frame: null,
+  _frameCreated: null,
   _overlay: null,
   _box: null,
   _openedURL: null,
   _injectedStyleSheets: [
     "chrome://browser/skin/preferences/preferences.css",
     "chrome://global/skin/in-content/common.css",
     "chrome://browser/skin/preferences/in-content/preferences.css",
     "chrome://browser/skin/preferences/in-content/dialog.css",
   ],
   _resizeObserver: null,
-
-  init() {
-    this._frame = document.getElementById("dialogFrame");
-    this._overlay = document.getElementById("dialogOverlay");
-    this._box = document.getElementById("dialogBox");
-    this._closeButton = document.getElementById("dialogClose");
-  },
+  _template: null,
+  _id: null,
+  _titleElement: null,
+  _closeButton: null,
 
   updateTitle(aEvent) {
-    if (aEvent.target != gSubDialog._frame.contentDocument)
+    if (aEvent.target != this._frame.contentDocument)
       return;
-    document.getElementById("dialogTitle").textContent = gSubDialog._frame.contentDocument.title;
+    this._titleElement.textContent = this._frame.contentDocument.title;
   },
 
   injectXMLStylesheet(aStylesheetURL) {
     let contentStylesheet = this._frame.contentDocument.createProcessingInstruction(
       "xml-stylesheet",
       'href="' + aStylesheetURL + '" type="text/css"'
     );
     this._frame.contentDocument.insertBefore(contentStylesheet,
                                              this._frame.contentDocument.documentElement);
   },
 
-  open(aURL, aFeatures = null, aParams = null, aClosingCallback = null) {
-    // If we're already open/opening on this URL, do nothing.
-    if (this._openedURL == aURL && !this._isClosing) {
-      return;
-    }
+  async open(aURL, aFeatures = null, aParams = null, aClosingCallback = null) {
+    // Wait until frame is ready to prevent browser crash in tests
+    await this._frameCreated;
     // If we're open on some (other) URL or we're closing, open when closing has finished.
     if (this._openedURL || this._isClosing) {
       if (!this._isClosing) {
         this.close();
       }
       let args = Array.from(arguments);
       this._closingPromise.then(() => {
         this.open.apply(this, args);
       });
       return;
     }
     this._addDialogEventListeners();
 
     let features = (aFeatures ? aFeatures + "," : "") + "resizable,dialog=no,centerscreen";
-    let dialog = window.openDialog(aURL, "dialogFrame", features, aParams);
+    let dialog = window.openDialog(aURL, `dialogFrame-${this._id}`, features, aParams);
     if (aClosingCallback) {
       this._closingCallback = aClosingCallback.bind(dialog);
     }
 
     this._closingEvent = null;
     this._isClosing = false;
     this._openedURL = aURL;
 
@@ -104,16 +126,21 @@ var gSubDialog = {
     // Clear the sizing inline styles.
     this._frame.removeAttribute("style");
     // Clear the sizing attributes
     this._box.removeAttribute("width");
     this._box.removeAttribute("height");
     this._box.style.removeProperty("min-height");
     this._box.style.removeProperty("min-width");
 
+    this._overlay.dispatchEvent(new CustomEvent("dialogclose", {
+      bubbles: true,
+      detail: { dialog: this },
+    }));
+
     setTimeout(() => {
       // Unload the dialog after the event listeners run so that the load of about:blank isn't
       // cancelled by the ESC <key>.
       let onBlankLoad = e => {
         if (this._frame.contentWindow.location.href == "about:blank") {
           this._frame.removeEventListener("load", onBlankLoad);
           // We're now officially done closing, so update the state to reflect that.
           this._openedURL = null;
@@ -180,42 +207,42 @@ var gSubDialog = {
     }
 
     // Provide the ability for the dialog to know that it is being loaded "in-content".
     this._frame.contentDocument.documentElement.setAttribute("subdialog", "true");
 
     this._frame.contentWindow.addEventListener("dialogclosing", this);
 
     let oldResizeBy = this._frame.contentWindow.resizeBy;
-    this._frame.contentWindow.resizeBy = function(resizeByWidth, resizeByHeight) {
+    this._frame.contentWindow.resizeBy = (resizeByWidth, resizeByHeight) => {
       // Only handle resizeByHeight currently.
-      let frameHeight = gSubDialog._frame.clientHeight;
-      let boxMinHeight = parseFloat(getComputedStyle(gSubDialog._box).minHeight, 10);
+      let frameHeight = this._frame.clientHeight;
+      let boxMinHeight = parseFloat(getComputedStyle(this._box).minHeight, 10);
 
-      gSubDialog._frame.style.height = (frameHeight + resizeByHeight) + "px";
-      gSubDialog._box.style.minHeight = (boxMinHeight + resizeByHeight) + "px";
+      this._frame.style.height = (frameHeight + resizeByHeight) + "px";
+      this._box.style.minHeight = (boxMinHeight + resizeByHeight) + "px";
 
-      oldResizeBy.call(gSubDialog._frame.contentWindow, resizeByWidth, resizeByHeight);
+      oldResizeBy.call(this._frame.contentWindow, resizeByWidth, resizeByHeight);
     };
 
     // Make window.close calls work like dialog closing.
     let oldClose = this._frame.contentWindow.close;
-    this._frame.contentWindow.close = function() {
-      var closingEvent = gSubDialog._closingEvent;
+    this._frame.contentWindow.close = () => {
+      var closingEvent = this._closingEvent;
       if (!closingEvent) {
         closingEvent = new CustomEvent("dialogclosing", {
           bubbles: true,
           detail: { button: null },
         });
 
-        gSubDialog._frame.contentWindow.dispatchEvent(closingEvent);
+        this._frame.contentWindow.dispatchEvent(closingEvent);
       }
 
-      gSubDialog.close(closingEvent);
-      oldClose.call(gSubDialog._frame.contentWindow);
+      this.close(closingEvent);
+      oldClose.call(this._frame.contentWindow);
     };
 
     // XXX: Hack to make focus during the dialog's load functions work. Make the element visible
     // sooner in DOMContentLoaded but mostly invisible instead of changing visibility just before
     // the dialog's load event.
     this._overlay.style.visibility = "visible";
     this._overlay.style.opacity = "0.01";
   },
@@ -288,29 +315,33 @@ var gSubDialog = {
       }
     }
 
     this._frame.style.height = frameHeight;
     this._box.style.minHeight = "calc(" +
                                 (boxVerticalBorder + groupBoxTitleHeight + boxVerticalPadding) +
                                 "px + " + frameMinHeight + ")";
 
+    this._overlay.dispatchEvent(new CustomEvent("dialogopen", {
+      bubbles: true,
+      detail: { dialog: this },
+    }));
     this._overlay.style.visibility = "visible";
     this._overlay.style.opacity = ""; // XXX: focus hack continued from _onContentLoaded
 
     if (this._box.getAttribute("resizable") == "true") {
       this._resizeObserver = new MutationObserver(this._onResize);
       this._resizeObserver.observe(this._box, {attributes: true});
     }
 
     this._trapFocus();
   },
 
   _onResize(mutations) {
-    let frame = gSubDialog._frame;
+    let frame = this._frame;
     // The width and height styles are needed for the initial
     // layout of the frame, but afterward they need to be removed
     // or their presence will restrict the contents of the <browser>
     // from resizing to a smaller size.
     frame.style.removeProperty("width");
     frame.style.removeProperty("height");
 
     let docEl = frame.contentDocument.documentElement;
@@ -343,23 +374,23 @@ var gSubDialog = {
     }
     if (aEvent.keyCode != aEvent.DOM_VK_TAB ||
         aEvent.ctrlKey || aEvent.altKey || aEvent.metaKey) {
       return;
     }
 
     let fm = Services.focus;
 
-    function isLastFocusableElement(el) {
+    let isLastFocusableElement = el => {
       // XXXgijs unfortunately there is no way to get the last focusable element without asking
       // the focus manager to move focus to it.
-      let rv = el == fm.moveFocus(gSubDialog._frame.contentWindow, null, fm.MOVEFOCUS_LAST, 0);
+      let rv = el == fm.moveFocus(this._frame.contentWindow, null, fm.MOVEFOCUS_LAST, 0);
       fm.setFocus(el, 0);
       return rv;
-    }
+    };
 
     let forward = !aEvent.shiftKey;
     // check if focus is leaving the frame (incl. the close button):
     if ((aEvent.target == this._closeButton && !forward) ||
         (isLastFocusableElement(aEvent.originalTarget) && forward)) {
       aEvent.preventDefault();
       aEvent.stopImmediatePropagation();
       let parentWin = this._getBrowser().ownerGlobal;
@@ -444,8 +475,109 @@ var gSubDialog = {
 
   _getBrowser() {
     return window.QueryInterface(Ci.nsIInterfaceRequestor)
                  .getInterface(Ci.nsIWebNavigation)
                  .QueryInterface(Ci.nsIDocShell)
                  .chromeEventHandler;
   },
 };
+
+var gSubDialog = {
+  /**
+   * New dialogs are stacked on top of the existing ones, and they are pushed
+   * to the end of the _dialogs array.
+   * @type {Array}
+   */
+  _dialogs: [],
+  _dialogStack: null,
+  _dialogTemplate: null,
+  _nextDialogID: 0,
+  _preloadDialog: null,
+  get _topDialog() {
+    return this._dialogs.length > 0 ? this._dialogs[this._dialogs.length - 1] : undefined;
+  },
+
+  init() {
+    this._dialogStack = document.getElementById("dialogStack");
+    this._dialogTemplate = document.getElementById("dialogTemplate");
+    this._preloadDialog = new SubDialog({template: this._dialogTemplate,
+                                         parentElement: this._dialogStack,
+                                         id: this._nextDialogID++});
+  },
+
+  open(aURL, aFeatures = null, aParams = null, aClosingCallback = null) {
+    // If we're already open/opening on this URL, do nothing.
+    if (this._topDialog && this._topDialog._openedURL == aURL) {
+      return;
+    }
+
+    this._preloadDialog.open(aURL, aFeatures, aParams, aClosingCallback);
+    this._dialogs.push(this._preloadDialog);
+    this._preloadDialog = new SubDialog({template: this._dialogTemplate,
+                                         parentElement: this._dialogStack,
+                                         id: this._nextDialogID++});
+
+    if (this._dialogs.length == 1) {
+      this._dialogStack.hidden = false;
+      this._ensureStackEventListeners();
+    }
+  },
+
+  close() {
+    this._topDialog.close();
+  },
+
+  handleEvent(aEvent) {
+    switch (aEvent.type) {
+      case "dialogopen": {
+        this._onDialogOpen();
+        break;
+      }
+      case "dialogclose": {
+        this._onDialogClose(aEvent.detail.dialog);
+        break;
+      }
+    }
+  },
+
+  _onDialogOpen() {
+    let lowerDialog = this._dialogs.length > 1 ? this._dialogs[this._dialogs.length - 2] : undefined;
+    if (lowerDialog) {
+      lowerDialog._overlay.removeAttribute("topmost");
+      lowerDialog._removeDialogEventListeners();
+    }
+  },
+
+  _onDialogClose(dialog) {
+    let fm = Services.focus;
+    if (this._topDialog == dialog) {
+      // XXX: When a top-most dialog is closed, we reuse the closed dialog and
+      //      remove the preloadDialog. This is a temporary solution before we
+      //      rewrite all the test cases in Bug 1359023.
+      this._preloadDialog._overlay.remove();
+      this._preloadDialog = this._dialogs.pop();
+    } else {
+      dialog._overlay.remove();
+      this._dialogs.splice(this._dialogs.indexOf(dialog), 1);
+    }
+
+    if (this._topDialog) {
+      fm.moveFocus(this._topDialog._frame.contentWindow, null, fm.MOVEFOCUS_FIRST, fm.FLAG_BYKEY);
+      this._topDialog._overlay.setAttribute("topmost", true);
+      this._topDialog._addDialogEventListeners();
+    } else {
+      fm.moveFocus(window, null, fm.MOVEFOCUS_ROOT, fm.FLAG_BYKEY);
+      this._dialogStack.hidden = true;
+      this._removeStackEventListeners();
+    }
+  },
+
+  _ensureStackEventListeners() {
+    this._dialogStack.addEventListener("dialogopen", this);
+    this._dialogStack.addEventListener("dialogclose", this);
+  },
+
+  _removeStackEventListeners() {
+    this._dialogStack.removeEventListener("dialogopen", this);
+    this._dialogStack.removeEventListener("dialogclose", this);
+  },
+};
--- a/browser/components/preferences/in-content/tests/browser_advanced_siteData.js
+++ b/browser/components/preferences/in-content/tests/browser_advanced_siteData.js
@@ -122,35 +122,36 @@ const cacheUsageGetter = {
   },
   QueryInterface: XPCOMUtils.generateQI([
     Components.interfaces.nsICacheStorageConsumptionObserver,
     Components.interfaces.nsISupportsWeakReference
   ]),
 };
 
 function openSettingsDialog() {
+  let win = gBrowser.selectedBrowser.contentWindow;
   let doc = gBrowser.selectedBrowser.contentDocument;
   let settingsBtn = doc.getElementById("siteDataSettings");
-  let dialogOverlay = doc.getElementById("dialogOverlay");
+  let dialogOverlay = win.gSubDialog._preloadDialog._overlay;
   let dialogLoadPromise = promiseLoadSubDialog("chrome://browser/content/preferences/siteDataSettings.xul");
   let dialogInitPromise = TestUtils.topicObserved("sitedata-settings-init", () => true);
   let fullyLoadPromise = Promise.all([ dialogLoadPromise, dialogInitPromise ]).then(() => {
     is(dialogOverlay.style.visibility, "visible", "The Settings dialog should be visible");
   });
   settingsBtn.doCommand();
   return fullyLoadPromise;
 }
 
 function promiseSettingsDialogClose() {
   return new Promise(resolve => {
-    let doc = gBrowser.selectedBrowser.contentDocument;
-    let dialogOverlay = doc.getElementById("dialogOverlay");
-    let win = content.gSubDialog._frame.contentWindow;
-    win.addEventListener("unload", function unload() {
-      if (win.document.documentURI === "chrome://browser/content/preferences/siteDataSettings.xul") {
+    let win = gBrowser.selectedBrowser.contentWindow;
+    let dialogOverlay = win.gSubDialog._topDialog._overlay;
+    let dialogWin = win.gSubDialog._topDialog._frame.contentWindow;
+    dialogWin.addEventListener("unload", function unload() {
+      if (dialogWin.document.documentURI === "chrome://browser/content/preferences/siteDataSettings.xul") {
         isnot(dialogOverlay.style.visibility, "visible", "The Settings dialog should be hidden");
         resolve();
       }
     }, { once: true });
   });
 }
 
 function promiseSitesUpdated() {
@@ -159,17 +160,18 @@ function promiseSitesUpdated() {
 
 function promiseCookiesCleared() {
   return TestUtils.topicObserved("cookie-changed", (subj, data) => {
     return data === "cleared";
   });
 }
 
 function assertSitesListed(doc, hosts) {
-  let frameDoc = doc.getElementById("dialogFrame").contentDocument;
+  let win = gBrowser.selectedBrowser.contentWindow;
+  let frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
   let removeBtn = frameDoc.getElementById("removeSelected");
   let removeAllBtn = frameDoc.getElementById("removeAll");
   let sitesList = frameDoc.getElementById("sitesList");
   let totalSitesNumber = sitesList.getElementsByTagName("richlistitem").length;
   is(totalSitesNumber, hosts.length, "Should list the right sites number");
   hosts.forEach(host => {
     let site = sitesList.querySelector(`richlistitem[host="${host}"]`);
     ok(site, `Should list the site of ${host}`);
@@ -216,17 +218,17 @@ add_task(async function() {
       persisted: false
     },
   ];
 
   let updatedPromise = promiseSitesUpdated();
   await openPreferencesViaOpenPreferencesAPI("advanced", "networkTab", { leaveOpen: true });
   await updatedPromise;
   await openSettingsDialog();
-  let dialogFrame = gBrowser.selectedBrowser.contentDocument.getElementById("dialogFrame");
+  let dialogFrame = content.gSubDialog._topDialog._frame;
   let frameDoc = dialogFrame.contentDocument;
 
   let siteItems = frameDoc.getElementsByTagName("richlistitem");
   is(siteItems.length, 1, "Should group sites across scheme, port and origin attributes");
 
   let expected = "account.xyz.com";
   let hostCol = siteItems[0].getAttribute("host");
   is(hostCol, expected, "Should group and list sites by host");
@@ -257,18 +259,17 @@ add_task(async function() {
   await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_QUOTA_USAGE_URL);
   await waitForEvent(gBrowser.selectedBrowser.contentWindow, "test-indexedDB-done");
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 
   let updatedPromise = promiseSitesUpdated();
   await openPreferencesViaOpenPreferencesAPI("advanced", "networkTab", { leaveOpen: true });
   await updatedPromise;
   await openSettingsDialog();
-  let doc = gBrowser.selectedBrowser.contentDocument;
-  let dialogFrame = doc.getElementById("dialogFrame");
+  let dialogFrame = content.gSubDialog._topDialog._frame;
   let frameDoc = dialogFrame.contentDocument;
 
   let siteItems = frameDoc.getElementsByTagName("richlistitem");
   is(siteItems.length, 2, "Should list sites using quota usage or appcache");
 
   let appcacheSite = frameDoc.querySelector(`richlistitem[host="${TEST_OFFLINE_HOST}"]`);
   ok(appcacheSite, "Should list site using appcache");
 
@@ -426,18 +427,19 @@ add_task(async function() {
     },
   ];
 
   let updatePromise = promiseSitesUpdated();
   await openPreferencesViaOpenPreferencesAPI("advanced", "networkTab", { leaveOpen: true });
   await updatePromise;
   await openSettingsDialog();
 
-  let doc = gBrowser.selectedBrowser.contentDocument;
-  let dialogFrame = doc.getElementById("dialogFrame");
+  let win = gBrowser.selectedBrowser.contentWindow;
+  let dialog = win.gSubDialog._topDialog;
+  let dialogFrame = dialog._frame;
   let frameDoc = dialogFrame.contentDocument;
   let hostCol = frameDoc.getElementById("hostCol");
   let usageCol = frameDoc.getElementById("usageCol");
   let statusCol = frameDoc.getElementById("statusCol");
   let sitesList = frameDoc.getElementById("sitesList");
 
   // Test default sorting
   assertSortByUsage("descending");
@@ -551,18 +553,19 @@ add_task(async function() {
   ];
   let fakeHosts = mockSiteDataManager.fakeSites.map(site => site.principal.URI.host);
 
   let updatePromise = promiseSitesUpdated();
   await openPreferencesViaOpenPreferencesAPI("advanced", "networkTab", { leaveOpen: true });
   await updatePromise;
   await openSettingsDialog();
 
+  let win = gBrowser.selectedBrowser.contentWindow;
   let doc = gBrowser.selectedBrowser.contentDocument;
-  let frameDoc = doc.getElementById("dialogFrame").contentDocument;
+  let frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
   let searchBox = frameDoc.getElementById("searchBox");
 
   searchBox.value = "xyz";
   searchBox.doCommand();
   assertSitesListed(doc, fakeHosts.filter(host => host.includes("xyz")));
 
   searchBox.value = "bar";
   searchBox.doCommand();
@@ -608,80 +611,81 @@ add_task(async function() {
   ];
   let fakeHosts = mockSiteDataManager.fakeSites.map(site => site.principal.URI.host);
 
   let updatePromise = promiseSitesUpdated();
   await openPreferencesViaOpenPreferencesAPI("advanced", "networkTab", { leaveOpen: true });
   await updatePromise;
   await openSettingsDialog();
 
+  let win = gBrowser.selectedBrowser.contentWindow;
   let doc = gBrowser.selectedBrowser.contentDocument;
   let frameDoc = null;
   let saveBtn = null;
   let cancelBtn = null;
   let settingsDialogClosePromise = null;
 
   // Test the initial state
   assertSitesListed(doc, fakeHosts);
 
   // Test the "Cancel" button
   settingsDialogClosePromise = promiseSettingsDialogClose();
-  frameDoc = doc.getElementById("dialogFrame").contentDocument;
+  frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
   cancelBtn = frameDoc.getElementById("cancel");
   removeAllSitesOneByOne();
   assertAllSitesNotListed();
   cancelBtn.doCommand();
   await settingsDialogClosePromise;
   await openSettingsDialog();
   assertSitesListed(doc, fakeHosts);
 
   // Test the "Save Changes" button but cancelling save
   let cancelPromise = promiseAlertDialogOpen("cancel");
   settingsDialogClosePromise = promiseSettingsDialogClose();
-  frameDoc = doc.getElementById("dialogFrame").contentDocument;
+  frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
   saveBtn = frameDoc.getElementById("save");
   removeAllSitesOneByOne();
   assertAllSitesNotListed();
   saveBtn.doCommand();
   await cancelPromise;
   await settingsDialogClosePromise;
   await openSettingsDialog();
   assertSitesListed(doc, fakeHosts);
 
   // Test the "Save Changes" button and accepting save
   let acceptPromise = promiseAlertDialogOpen("accept");
   settingsDialogClosePromise = promiseSettingsDialogClose();
   updatePromise = promiseSitesUpdated();
-  frameDoc = doc.getElementById("dialogFrame").contentDocument;
+  frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
   saveBtn = frameDoc.getElementById("save");
   removeAllSitesOneByOne();
   assertAllSitesNotListed();
   saveBtn.doCommand();
   await acceptPromise;
   await settingsDialogClosePromise;
   await updatePromise;
   await openSettingsDialog();
   assertAllSitesNotListed();
 
   mockSiteDataManager.unregister();
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 
   function removeAllSitesOneByOne() {
-    frameDoc = doc.getElementById("dialogFrame").contentDocument;
+    frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
     let removeBtn = frameDoc.getElementById("removeSelected");
     let sitesList = frameDoc.getElementById("sitesList");
     let sites = sitesList.getElementsByTagName("richlistitem");
     for (let i = sites.length - 1; i >= 0; --i) {
       sites[i].click();
       removeBtn.doCommand();
     }
   }
 
   function assertAllSitesNotListed() {
-    frameDoc = doc.getElementById("dialogFrame").contentDocument;
+    frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
     let removeBtn = frameDoc.getElementById("removeSelected");
     let removeAllBtn = frameDoc.getElementById("removeAll");
     let sitesList = frameDoc.getElementById("sitesList");
     let sites = sitesList.getElementsByTagName("richlistitem");
     is(sites.length, 0, "Should not list all sites");
     is(removeBtn.disabled, true, "Should disable the removeSelected button");
     is(removeAllBtn.disabled, true, "Should disable the removeAllBtn button");
   }
@@ -737,68 +741,69 @@ add_task(async function() {
   ];
   let fakeHosts = mockSiteDataManager.fakeSites.map(site => site.principal.URI.host);
 
   let updatePromise = promiseSitesUpdated();
   await openPreferencesViaOpenPreferencesAPI("advanced", "networkTab", { leaveOpen: true });
   await updatePromise;
   await openSettingsDialog();
 
+  let win = gBrowser.selectedBrowser.contentWindow;
   let doc = gBrowser.selectedBrowser.contentDocument;
   let frameDoc = null;
   let saveBtn = null;
   let cancelBtn = null;
   let removeDialogOpenPromise = null;
   let settingsDialogClosePromise = null;
 
   // Test the initial state
   assertSitesListed(doc, fakeHosts);
 
   // Test the "Cancel" button
   settingsDialogClosePromise = promiseSettingsDialogClose();
-  frameDoc = doc.getElementById("dialogFrame").contentDocument;
+  frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
   cancelBtn = frameDoc.getElementById("cancel");
   removeSelectedSite(fakeHosts.slice(0, 2));
   assertSitesListed(doc, fakeHosts.slice(2));
   cancelBtn.doCommand();
   await settingsDialogClosePromise;
   await openSettingsDialog();
   assertSitesListed(doc, fakeHosts);
 
   // Test the "Save Changes" button but canceling save
   removeDialogOpenPromise = promiseWindowDialogOpen("cancel", REMOVE_DIALOG_URL);
   settingsDialogClosePromise = promiseSettingsDialogClose();
-  frameDoc = doc.getElementById("dialogFrame").contentDocument;
+  frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
   saveBtn = frameDoc.getElementById("save");
   removeSelectedSite(fakeHosts.slice(0, 2));
   assertSitesListed(doc, fakeHosts.slice(2));
   saveBtn.doCommand();
   await removeDialogOpenPromise;
   await settingsDialogClosePromise;
   await openSettingsDialog();
   assertSitesListed(doc, fakeHosts);
 
   // Test the "Save Changes" button and accepting save
   removeDialogOpenPromise = promiseWindowDialogOpen("accept", REMOVE_DIALOG_URL);
   settingsDialogClosePromise = promiseSettingsDialogClose();
-  frameDoc = doc.getElementById("dialogFrame").contentDocument;
+  frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
   saveBtn = frameDoc.getElementById("save");
   removeSelectedSite(fakeHosts.slice(0, 2));
   assertSitesListed(doc, fakeHosts.slice(2));
   saveBtn.doCommand();
   await removeDialogOpenPromise;
   await settingsDialogClosePromise;
   await openSettingsDialog();
   assertSitesListed(doc, fakeHosts.slice(2));
 
   mockSiteDataManager.unregister();
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 
   function removeSelectedSite(hosts) {
-    frameDoc = doc.getElementById("dialogFrame").contentDocument;
+    frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
     let removeBtn = frameDoc.getElementById("removeSelected");
     let sitesList = frameDoc.getElementById("sitesList");
     hosts.forEach(host => {
       let site = sitesList.querySelector(`richlistitem[host="${host}"]`);
       if (site) {
         site.click();
         removeBtn.doCommand();
       } else {
@@ -841,18 +846,19 @@ add_task(async function() {
   let fakeHosts = mockSiteDataManager.fakeSites.map(site => site.principal.URI.host);
 
   let updatePromise = promiseSitesUpdated();
   await openPreferencesViaOpenPreferencesAPI("advanced", "networkTab", { leaveOpen: true });
   await updatePromise;
   await openSettingsDialog();
 
   // Search "foo" to only list foo.com sites
+  let win = gBrowser.selectedBrowser.contentWindow;
   let doc = gBrowser.selectedBrowser.contentDocument;
-  let frameDoc = doc.getElementById("dialogFrame").contentDocument;
+  let frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
   let searchBox = frameDoc.getElementById("searchBox");
   searchBox.value = "xyz";
   searchBox.doCommand();
   assertSitesListed(doc, fakeHosts.filter(host => host.includes("xyz")));
 
   // Test only removing all visible sites listed
   updatePromise = promiseSitesUpdated();
   let acceptRemovePromise = promiseWindowDialogOpen("accept", REMOVE_DIALOG_URL);
--- a/browser/components/preferences/in-content/tests/browser_advanced_update.js
+++ b/browser/components/preferences/in-content/tests/browser_advanced_update.js
@@ -111,25 +111,26 @@ add_task(async function() {
 
 add_task(async function() {
   mockUpdateManager.register();
 
   await openPreferencesViaOpenPreferencesAPI("advanced", "updateTab", { leaveOpen: true });
   let doc = gBrowser.selectedBrowser.contentDocument;
 
   let showBtn = doc.getElementById("showUpdateHistory");
-  let dialogOverlay = doc.getElementById("dialogOverlay");
+  let dialogOverlay = content.gSubDialog._preloadDialog._overlay;
 
   // Test the dialog window opens
   is(dialogOverlay.style.visibility, "", "The dialog should be invisible");
+  let promiseSubDialogLoaded = promiseLoadSubDialog("chrome://mozapps/content/update/history.xul");
   showBtn.doCommand();
-  await promiseLoadSubDialog("chrome://mozapps/content/update/history.xul");
+  await promiseSubDialogLoaded;
   is(dialogOverlay.style.visibility, "visible", "The dialog should be visible");
 
-  let dialogFrame = doc.getElementById("dialogFrame");
+  let dialogFrame = dialogOverlay.querySelector(".dialogFrame");
   let frameDoc = dialogFrame.contentDocument;
   let updates = frameDoc.querySelectorAll("update");
 
   // Test the update history numbers are correct
   is(updates.length, mockUpdateManager.updateCount, "The update count is incorrect.");
 
   // Test the updates are displayed correctly
   let update = null;
@@ -141,15 +142,15 @@ add_task(async function() {
     is(update.name, updateData.name + " (" + updateData.buildID + ")", "Wrong update name");
     is(update.type, updateData.type == "major" ? "New Version" : "Security Update", "Wrong update type");
     is(update.installDate, formatInstallDate(updateData.installDate), "Wrong update installDate");
     is(update.detailsURL, updateData.detailsURL, "Wrong update detailsURL");
     is(update.status, updateData.statusText, "Wrong update status");
   }
 
   // Test the dialog window closes
-  let closeBtn = doc.getElementById("dialogClose");
+  let closeBtn = dialogOverlay.querySelector(".dialogClose");
   closeBtn.doCommand();
   is(dialogOverlay.style.visibility, "", "The dialog should be invisible");
 
   mockUpdateManager.unregister();
   gBrowser.removeCurrentTab();
 });
--- a/browser/components/preferences/in-content/tests/browser_basic_rebuild_fonts_test.js
+++ b/browser/components/preferences/in-content/tests/browser_basic_rebuild_fonts_test.js
@@ -15,18 +15,19 @@ add_task(async function() {
   let fontFamily = Services.prefs.getCharPref("font.name." + defaultFontType + "." + langGroup);
   let fontFamilyField = doc.getElementById("defaultFont");
   is(fontFamilyField.value, fontFamily, "Font family should be set correctly.");
 
   let defaultFontSize = Services.prefs.getIntPref("font.size.variable." + langGroup);
   let fontSizeField = doc.getElementById("defaultFontSize");
   is(fontSizeField.value, defaultFontSize, "Font size should be set correctly.");
 
+  let promiseSubDialogLoaded = promiseLoadSubDialog("chrome://browser/content/preferences/fonts.xul");
   doc.getElementById("advancedFonts").click();
-  let win = await promiseLoadSubDialog("chrome://browser/content/preferences/fonts.xul");
+  let win = await promiseSubDialogLoaded;
   doc = win.document;
 
   // Simulate a dumb font backend.
   win.FontBuilder._enumerator = {
     _list: ["MockedFont1", "MockedFont2", "MockedFont3"],
     _defaultFont: null,
     EnumerateFonts(lang, type, list) {
       return this._list;
--- a/browser/components/preferences/in-content/tests/browser_cookies_exceptions.js
+++ b/browser/components/preferences/in-content/tests/browser_cookies_exceptions.js
@@ -334,18 +334,20 @@ var testRunner = {
         Services.prefs.clearUserPref("privacy.history.custom");
       });
 
       openPreferencesViaOpenPreferencesAPI("panePrivacy", null, {leaveOpen: true}).then(function() {
         let doc = gBrowser.contentDocument;
         let historyMode = doc.getElementById("historyMode");
         historyMode.value = "custom";
         historyMode.doCommand();
+
+        let promiseSubDialogLoaded =
+          promiseLoadSubDialog("chrome://browser/content/preferences/permissions.xul");
         doc.getElementById("cookieExceptions").doCommand();
 
-        let subDialogURL = "chrome://browser/content/preferences/permissions.xul";
-        promiseLoadSubDialog(subDialogURL).then(function(win) {
+        promiseSubDialogLoaded.then(function(win) {
           helperFunctions.windowLoad(win);
         });
       });
     });
   },
 };
--- a/browser/components/preferences/in-content/tests/browser_subdialogs.js
+++ b/browser/components/preferences/in-content/tests/browser_subdialogs.js
@@ -12,29 +12,29 @@ const gDialogURL2 = getRootDirectory(gTe
 
 function open_subdialog_and_test_generic_start_state(browser, domcontentloadedFn, url = gDialogURL) {
   let domcontentloadedFnStr = domcontentloadedFn ?
     "(" + domcontentloadedFn.toString() + ")()" :
     "";
   return ContentTask.spawn(browser, {url, domcontentloadedFnStr}, async function(args) {
     let rv = { acceptCount: 0 };
     let win = content.window;
-    let subdialog = win.gSubDialog;
-    subdialog.open(args.url, null, rv);
+    content.gSubDialog.open(args.url, null, rv);
+    let subdialog = content.gSubDialog._topDialog;
 
     info("waiting for subdialog DOMFrameContentLoaded");
     await ContentTaskUtils.waitForEvent(win, "DOMFrameContentLoaded", true);
     let result;
     if (args.domcontentloadedFnStr) {
       // eslint-disable-next-line no-eval
       result = eval(args.domcontentloadedFnStr);
     }
 
     info("waiting for subdialog load");
-    await ContentTaskUtils.waitForEvent(subdialog._frame, "load");
+    await ContentTaskUtils.waitForEvent(subdialog._overlay, "dialogopen");
     info("subdialog window is loaded");
 
     let expectedStyleSheetURLs = subdialog._injectedStyleSheets.slice(0);
     for (let styleSheet of subdialog._frame.contentDocument.styleSheets) {
       let index = expectedStyleSheetURLs.indexOf(styleSheet.href);
       if (index >= 0) {
         expectedStyleSheetURLs.splice(index, 1);
       }
@@ -47,19 +47,27 @@ function open_subdialog_and_test_generic
       "Overlay should be visible");
     Assert.equal(expectedStyleSheetURLs.length, 0,
       "No stylesheets that were expected are missing");
     return result;
   });
 }
 
 async function close_subdialog_and_test_generic_end_state(browser, closingFn, closingButton, acceptCount, options) {
+  let getDialogsCount = () => {
+    return ContentTask.spawn(browser, null, () =>
+      content.window.gSubDialog._dialogs.length);
+  };
+  let getStackChildrenCount = () => {
+    return ContentTask.spawn(browser, null, () =>
+      content.window.gSubDialog._dialogStack.children.length);
+  };
   let dialogclosingPromise = ContentTask.spawn(browser, {closingButton, acceptCount}, async function(expectations) {
     let win = content.window;
-    let subdialog = win.gSubDialog;
+    let subdialog = win.gSubDialog._topDialog;
     let frame = subdialog._frame;
     info("waiting for dialogclosing");
     let closingEvent =
       await ContentTaskUtils.waitForEvent(frame.contentWindow, "dialogclosing");
     let contentClosingButton = closingEvent.detail.button;
     let actualAcceptCount = frame.contentWindow.arguments &&
                             frame.contentWindow.arguments[0].acceptCount;
 
@@ -71,134 +79,162 @@ async function close_subdialog_and_test_
     Assert.equal(frame.getAttribute("style"), "", "inline styles should be cleared");
     Assert.equal(frame.contentWindow.location.href.toString(), "about:blank",
       "sub-dialog should be unloaded");
     Assert.equal(contentClosingButton, expectations.closingButton,
       "closing event should indicate button was '" + expectations.closingButton + "'");
     Assert.equal(actualAcceptCount, expectations.acceptCount,
       "should be 1 if accepted, 0 if canceled, undefined if closed w/out button");
   });
-
+  let initialDialogsCount = await getDialogsCount();
+  let initialStackChildrenCount = await getStackChildrenCount();
   if (options && options.runClosingFnOutsideOfContentTask) {
     await closingFn();
   } else {
     ContentTask.spawn(browser, null, closingFn);
   }
 
   await dialogclosingPromise;
+  let endDialogsCount = await getDialogsCount();
+  let endStackChildrenCount = await getStackChildrenCount();
+  Assert.equal(initialDialogsCount - 1, endDialogsCount,
+    "dialog count should decrease by 1");
+  Assert.equal(initialStackChildrenCount - 1, endStackChildrenCount,
+    "stack children count should decrease by 1");
 }
 
 let tab;
 
 add_task(async function test_initialize() {
   tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:preferences");
 });
 
 add_task(async function check_titlebar_focus_returnval_titlechanges_accepting() {
   await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
 
   let domtitlechangedPromise = BrowserTestUtils.waitForEvent(tab.linkedBrowser, "DOMTitleChanged");
   await ContentTask.spawn(tab.linkedBrowser, null, async function() {
-    let dialog = content.window.gSubDialog._frame.contentWindow;
-    let dialogTitleElement = content.document.getElementById("dialogTitle");
+    let dialog = content.window.gSubDialog._topDialog;
+    let dialogWin = dialog._frame.contentWindow;
+    let dialogTitleElement = dialog._titleElement;
     Assert.equal(dialogTitleElement.textContent, "Sample sub-dialog",
        "Title should be correct initially");
-    Assert.equal(dialog.document.activeElement.value, "Default text",
+    Assert.equal(dialogWin.document.activeElement.value, "Default text",
        "Textbox with correct text is focused");
-    dialog.document.title = "Updated title";
+    dialogWin.document.title = "Updated title";
   });
 
   info("waiting for DOMTitleChanged event");
   await domtitlechangedPromise;
 
   ContentTask.spawn(tab.linkedBrowser, null, async function() {
-    let dialogTitleElement = content.document.getElementById("dialogTitle");
+    let dialogTitleElement = content.window.gSubDialog._topDialog._titleElement;
     Assert.equal(dialogTitleElement.textContent, "Updated title",
       "subdialog should have updated title");
   });
 
   // Accept the dialog
   await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
-    function() { content.window.gSubDialog._frame.contentDocument.documentElement.acceptDialog(); },
+    function() { content.window.gSubDialog._topDialog._frame.contentDocument.documentElement.acceptDialog(); },
     "accept", 1);
 });
 
 add_task(async function check_canceling_dialog() {
   await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
 
   info("canceling the dialog");
   await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
-    function() { content.window.gSubDialog._frame.contentDocument.documentElement.cancelDialog(); },
+    function() { content.window.gSubDialog._topDialog._frame.contentDocument.documentElement.cancelDialog(); },
     "cancel", 0);
 });
 
 add_task(async function check_reopening_dialog() {
   await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
   info("opening another dialog which will close the first");
   await open_subdialog_and_test_generic_start_state(tab.linkedBrowser, "", gDialogURL2);
-  info("closing as normal");
+
+  ContentTask.spawn(tab.linkedBrowser, null, async function() {
+    let win = content.window;
+    let dialogs = win.gSubDialog._dialogs;
+    let lowerDialog = dialogs[0];
+    let topDialog = dialogs[1];
+    Assert.equal(dialogs.length, 2, "There should be two visible dialogs");
+    Assert.equal(win.getComputedStyle(topDialog._overlay).visibility, "visible",
+      "The top dialog should be visible");
+    Assert.equal(win.getComputedStyle(lowerDialog._overlay).visibility, "visible",
+      "The lower dialog should be visible");
+    Assert.equal(win.getComputedStyle(topDialog._overlay).backgroundColor, "rgba(0, 0, 0, 0.5)",
+      "The top dialog should have a semi-transparent overlay");
+    Assert.equal(win.getComputedStyle(lowerDialog._overlay).backgroundColor, "rgba(0, 0, 0, 0)",
+      "The lower dialog should not have an overlay");
+  });
+
+  info("closing two dialogs");
   await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
-    function() { content.window.gSubDialog._frame.contentDocument.documentElement.acceptDialog(); },
+    function() { content.window.gSubDialog._topDialog._frame.contentDocument.documentElement.acceptDialog(); },
+    "accept", 1);
+  await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
+    function() { content.window.gSubDialog._topDialog._frame.contentDocument.documentElement.acceptDialog(); },
     "accept", 1);
 });
 
 add_task(async function check_opening_while_closing() {
   await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
   info("closing");
-  content.window.gSubDialog.close();
+  content.window.gSubDialog._topDialog.close();
   info("reopening immediately after calling .close()");
   await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
   await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
-    function() { content.window.gSubDialog._frame.contentDocument.documentElement.acceptDialog(); },
+    function() { content.window.gSubDialog._topDialog._frame.contentDocument.documentElement.acceptDialog(); },
     "accept", 1);
 
 });
 
 add_task(async function window_close_on_dialog() {
   await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
 
   info("canceling the dialog");
   await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
-    function() { content.window.gSubDialog._frame.contentWindow.window.close(); },
+    function() { content.window.gSubDialog._topDialog._frame.contentWindow.window.close(); },
     null, 0);
 });
 
 add_task(async function click_close_button_on_dialog() {
   await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
 
   info("canceling the dialog");
   await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
-    function() { return BrowserTestUtils.synthesizeMouseAtCenter("#dialogClose", {}, tab.linkedBrowser); },
+    function() { return BrowserTestUtils.synthesizeMouseAtCenter(".dialogClose", {}, tab.linkedBrowser); },
     null, 0, {runClosingFnOutsideOfContentTask: true});
 });
 
 add_task(async function background_click_should_close_dialog() {
   await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
 
   // Clicking on an inactive part of dialog itself should not close the dialog.
   // Click the dialog title bar here to make sure nothing happens.
   info("clicking the dialog title bar");
-  BrowserTestUtils.synthesizeMouseAtCenter("#dialogTitle", {}, tab.linkedBrowser);
+  BrowserTestUtils.synthesizeMouseAtCenter(".dialogTitle", {}, tab.linkedBrowser);
 
   // Close the dialog by clicking on the overlay background. Simulate a click
   // at point (2,2) instead of (0,0) so we are sure we're clicking on the
   // overlay background instead of some boundary condition that a real user
   // would never click.
   info("clicking the overlay background");
   await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
     function() { return BrowserTestUtils.synthesizeMouseAtPoint(2, 2, {}, tab.linkedBrowser); },
     null, 0, {runClosingFnOutsideOfContentTask: true});
 });
 
 add_task(async function back_navigation_on_subdialog_should_close_dialog() {
   await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
 
   info("canceling the dialog");
   await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
-    function() { content.window.gSubDialog._frame.goBack(); },
+    function() { content.window.gSubDialog._topDialog._frame.goBack(); },
     null, undefined);
 });
 
 add_task(async function back_navigation_on_browser_tab_should_close_dialog() {
   await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
 
   info("canceling the dialog");
   await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
@@ -214,31 +250,31 @@ add_task(async function escape_should_cl
     function() { return BrowserTestUtils.synthesizeKey("VK_ESCAPE", {}, tab.linkedBrowser); },
     "cancel", 0, {runClosingFnOutsideOfContentTask: true});
 });
 
 add_task(async function correct_width_and_height_should_be_used_for_dialog() {
   await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
 
   await ContentTask.spawn(tab.linkedBrowser, null, async function() {
-    let frameStyle = content.window.gSubDialog._frame.style;
+    let frameStyle = content.window.gSubDialog._topDialog._frame.style;
     Assert.equal(frameStyle.width, "32em",
       "Width should be set on the frame from the dialog");
     Assert.equal(frameStyle.height, "5em",
       "Height should be set on the frame from the dialog");
   });
 
   await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
-    function() { content.window.gSubDialog._frame.contentWindow.window.close(); },
+    function() { content.window.gSubDialog._topDialog._frame.contentWindow.window.close(); },
     null, 0);
 });
 
 add_task(async function wrapped_text_in_dialog_should_have_expected_scrollHeight() {
   let oldHeight = await open_subdialog_and_test_generic_start_state(tab.linkedBrowser, function domcontentloadedFn() {
-    let frame = content.window.gSubDialog._frame;
+    let frame = content.window.gSubDialog._topDialog._frame;
     let doc = frame.contentDocument;
     let scrollHeight = doc.documentElement.scrollHeight;
     doc.documentElement.style.removeProperty("height");
     doc.getElementById("desc").textContent = `
       Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque
       laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi
       architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas
       sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione
@@ -248,64 +284,64 @@ add_task(async function wrapped_text_in_
       laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi
       architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas
       sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione
       voluptatem sequi nesciunt.`
     return scrollHeight;
   });
 
   await ContentTask.spawn(tab.linkedBrowser, oldHeight, async function(contentOldHeight) {
-    let frame = content.window.gSubDialog._frame;
+    let frame = content.window.gSubDialog._topDialog._frame;
     let docEl = frame.contentDocument.documentElement;
     Assert.equal(frame.style.width, "32em",
       "Width should be set on the frame from the dialog");
     Assert.ok(docEl.scrollHeight > contentOldHeight,
       "Content height increased (from " + contentOldHeight + " to " + docEl.scrollHeight + ").");
     Assert.equal(frame.style.height, docEl.scrollHeight + "px",
       "Height on the frame should be higher now");
   });
 
   await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
-    function() { content.window.gSubDialog._frame.contentWindow.window.close(); },
+    function() { content.window.gSubDialog._topDialog._frame.contentWindow.window.close(); },
     null, 0);
 });
 
 add_task(async function dialog_too_tall_should_get_reduced_in_height() {
   await open_subdialog_and_test_generic_start_state(tab.linkedBrowser, function domcontentloadedFn() {
-    let frame = content.window.gSubDialog._frame;
+    let frame = content.window.gSubDialog._topDialog._frame;
     frame.contentDocument.documentElement.style.height = "100000px";
   });
 
   await ContentTask.spawn(tab.linkedBrowser, null, async function() {
-    let frame = content.window.gSubDialog._frame;
+    let frame = content.window.gSubDialog._topDialog._frame;
     Assert.equal(frame.style.width, "32em", "Width should be set on the frame from the dialog");
     Assert.ok(parseInt(frame.style.height, 10) < content.window.innerHeight,
        "Height on the frame should be smaller than window's innerHeight");
   });
 
   await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
-    function() { content.window.gSubDialog._frame.contentWindow.window.close(); },
+    function() { content.window.gSubDialog._topDialog._frame.contentWindow.window.close(); },
     null, 0);
 });
 
 add_task(async function scrollWidth_and_scrollHeight_from_subdialog_should_size_the_browser() {
   await open_subdialog_and_test_generic_start_state(tab.linkedBrowser, function domcontentloadedFn() {
-    let frame = content.window.gSubDialog._frame;
+    let frame = content.window.gSubDialog._topDialog._frame;
     frame.contentDocument.documentElement.style.removeProperty("height");
     frame.contentDocument.documentElement.style.removeProperty("width");
   });
 
   await ContentTask.spawn(tab.linkedBrowser, null, async function() {
-    let frame = content.window.gSubDialog._frame;
+    let frame = content.window.gSubDialog._topDialog._frame;
     Assert.ok(frame.style.width.endsWith("px"),
        "Width (" + frame.style.width + ") should be set to a px value of the scrollWidth from the dialog");
     Assert.ok(frame.style.height.endsWith("px"),
        "Height (" + frame.style.height + ") should be set to a px value of the scrollHeight from the dialog");
   });
 
   await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
-    function() { content.window.gSubDialog._frame.contentWindow.window.close(); },
+    function() { content.window.gSubDialog._topDialog._frame.contentWindow.window.close(); },
     null, 0);
 });
 
 add_task(async function test_shutdown() {
   gBrowser.removeTab(tab);
 });
--- a/browser/components/preferences/in-content/tests/head.js
+++ b/browser/components/preferences/in-content/tests/head.js
@@ -49,39 +49,39 @@ function open_preferences(aCallback) {
 function openAndLoadSubDialog(aURL, aFeatures = null, aParams = null, aClosingCallback = null) {
   let promise = promiseLoadSubDialog(aURL);
   content.gSubDialog.open(aURL, aFeatures, aParams, aClosingCallback);
   return promise;
 }
 
 function promiseLoadSubDialog(aURL) {
   return new Promise((resolve, reject) => {
-    content.gSubDialog._frame.addEventListener("load", function load(aEvent) {
-      if (aEvent.target.contentWindow.location == "about:blank")
+    content.gSubDialog._dialogStack.addEventListener("dialogopen", function dialogopen(aEvent) {
+      if (aEvent.detail.dialog._frame.contentWindow.location == "about:blank")
         return;
-      content.gSubDialog._frame.removeEventListener("load", load);
+      content.gSubDialog._dialogStack.removeEventListener("dialogopen", dialogopen);
 
-      is(content.gSubDialog._frame.contentWindow.location.toString(), aURL,
+      is(aEvent.detail.dialog._frame.contentWindow.location.toString(), aURL,
          "Check the proper URL is loaded");
 
       // Check visibility
-      is_element_visible(content.gSubDialog._overlay, "Overlay is visible");
+      is_element_visible(aEvent.detail.dialog._overlay, "Overlay is visible");
 
       // Check that stylesheets were injected
-      let expectedStyleSheetURLs = content.gSubDialog._injectedStyleSheets.slice(0);
-      for (let styleSheet of content.gSubDialog._frame.contentDocument.styleSheets) {
+      let expectedStyleSheetURLs = aEvent.detail.dialog._injectedStyleSheets.slice(0);
+      for (let styleSheet of aEvent.detail.dialog._frame.contentDocument.styleSheets) {
         let i = expectedStyleSheetURLs.indexOf(styleSheet.href);
         if (i >= 0) {
           info("found " + styleSheet.href);
           expectedStyleSheetURLs.splice(i, 1);
         }
       }
       is(expectedStyleSheetURLs.length, 0, "All expectedStyleSheetURLs should have been found");
 
-      resolve(content.gSubDialog._frame.contentWindow);
+      resolve(aEvent.detail.dialog._frame.contentWindow);
     });
   });
 }
 
 /**
  * Waits a specified number of miliseconds for a specified event to be
  * fired on a specified element.
  *
--- a/browser/themes/shared/incontentprefs-old/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs-old/preferences.inc.css
@@ -286,70 +286,73 @@ description > html|a {
 #showUpdateHistory {
   margin-inline-start: 0;
 }
 
 /**
  * Dialog
  */
 
-#dialogOverlay {
-  background-color: rgba(0,0,0,0.5);
+.dialogOverlay {
   visibility: hidden;
 }
 
-#dialogBox {
+.dialogOverlay[topmost="true"] {
+  background-color: rgba(0,0,0,0.5);
+}
+
+.dialogBox {
   background-color: #fbfbfb;
   background-clip: content-box;
   color: #424e5a;
   font-size: 14px;
   /* `transparent` will use the dialogText color in high-contrast themes and
      when page colors are disabled */
   border: 1px solid transparent;
   border-radius: 3.5px;
   box-shadow: 0 2px 6px 0 rgba(0,0,0,0.3);
   display: -moz-box;
   margin: 0;
   padding: 0;
 }
 
-#dialogBox[resizable="true"] {
+.dialogBox[resizable="true"] {
   resize: both;
   overflow: hidden;
   min-height: 20em;
   min-width: 66ch;
 }
 
-#dialogBox > .groupbox-title {
+.dialogBox > .groupbox-title {
   padding: 3.5px 0;
   background-color: #F1F1F1;
   border-bottom: 1px solid #C1C1C1;
 }
 
-#dialogTitle {
+.dialogTitle {
   text-align: center;
   -moz-user-select: none;
 }
 
 .close-icon {
   background-color: transparent !important;
   border: none;
   box-shadow: none;
   padding: 0;
   height: auto;
   min-height: 16px;
   min-width: 0;
 }
 
-#dialogBox > .groupbox-body {
+.dialogBox > .groupbox-body {
   -moz-appearance: none;
   padding: 20px;
 }
 
-#dialogFrame {
+.dialogFrame {
   -moz-box-flex: 1;
   /* Default dialog dimensions */
   width: 66ch;
 }
 
 .largeDialogContainer.doScroll {
   overflow-y: auto;
   -moz-box-flex: 1;