Merge mozilla-central to inbound. a=merge CLOSED TREE
authorNarcis Beleuzu <nbeleuzu@mozilla.com>
Fri, 19 Oct 2018 16:34:08 +0300
changeset 500692 16864944bab1d02697d9d08b7fdeaebaa62d8f59
parent 500691 646f2d021c1163ac0c58c3878aa6921dc92311cb (current diff)
parent 500578 18859d2fec94f35e924e9093a99169623e0b2d78 (diff)
child 500693 fc083f953b3e7afbfd697b8c1091bee5ad88ea74
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to inbound. a=merge CLOSED TREE
browser/components/payments/res/paymentRequest.js
browser/components/payments/test/browser/browser_onboarding_wizard.js
docshell/base/nsDocShellLoadState.cpp
docshell/base/nsDocShellLoadState.h
mobile/android/extensions/webcompat/content/lib/ua_overrider.jsm
mobile/android/extensions/webcompat/webextension/background.js
mobile/android/extensions/webcompat/webextension/injections/css/bug0000000-dummy-css-injection.css
mobile/android/extensions/webcompat/webextension/injections/js/bug0000000-dummy-js-injection.js
mobile/android/extensions/webcompat/webextension/injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js
mobile/android/extensions/webcompat/webextension/injections/js/bug1457335-histography.io-ua-change.js
mobile/android/extensions/webcompat/webextension/injections/js/bug1472075-bankofamerica.com-ua-change.js
mobile/android/extensions/webcompat/webextension/injections/js/bug1472081-election.gov.np-window.sidebar-shim.js
mobile/android/extensions/webcompat/webextension/injections/js/bug1482066-portalminasnet.com-window.sidebar-shim.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1514,25 +1514,21 @@ pref("network.cookie.cookieBehavior", 4 
 pref("browser.contentblocking.allowlist.storage.enabled", true);
 
 #ifdef NIGHTLY_BUILD
 pref("browser.contentblocking.global-toggle.enabled", true);
 #else
 pref("browser.contentblocking.global-toggle.enabled", false);
 #endif
 
-// Define a set of default features for the Content Blocking UI
-#ifdef EARLY_BETA_OR_EARLIER
-pref("browser.contentblocking.fastblock.ui.enabled", true);
-pref("browser.contentblocking.fastblock.control-center.ui.enabled", true);
-#else
+// Disable the UI for FastBlock in product.
 pref("browser.contentblocking.fastblock.ui.enabled", false);
 pref("browser.contentblocking.fastblock.control-center.ui.enabled", false);
-#endif
 
+// Define a set of default features for the Content Blocking UI.
 pref("browser.contentblocking.trackingprotection.ui.enabled", true);
 pref("browser.contentblocking.trackingprotection.control-center.ui.enabled", true);
 pref("browser.contentblocking.rejecttrackers.ui.enabled", true);
 pref("browser.contentblocking.rejecttrackers.ui.recommended", true);
 pref("browser.contentblocking.rejecttrackers.control-center.ui.enabled", true);
 pref("browser.contentblocking.cookies-site-data.ui.reject-trackers.recommended", true);
 pref("browser.contentblocking.cookies-site-data.ui.reject-trackers.enabled", true);
 
@@ -1774,13 +1770,8 @@ pref("browser.fission.simulate", false);
 pref("prio.publicKeyA", "35AC1C7576C7C6EDD7FED6BCFC337B34D48CB4EE45C86BEEFB40BD8875707733");
 pref("prio.publicKeyB", "26E6674E65425B823F1F1D5F96E3BB3EF9E406EC7FBA7DEF8B08A35DD135AF50");
 #endif
 
 // Whether or not Prio-encoded Telemetry will be sent along with the main ping.
 #if defined(NIGHTLY_BUILD) && defined(MOZ_LIBPRIO)
 pref("prio.enabled", true);
 #endif
-
-#ifdef NIGHTLY_BUILD
-pref("browser.fastblock.enabled", true);
-#endif
-
--- a/browser/base/content/test/trackingUI/browser_trackingUI_telemetry.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_telemetry.js
@@ -34,19 +34,20 @@ add_task(async function setup() {
 
   let enabledCounts =
     Services.telemetry.getHistogramById("TRACKING_PROTECTION_ENABLED").snapshot().counts;
   is(enabledCounts[0], 1, "TP was not enabled on start up");
 
   let scalars = Services.telemetry.snapshotScalars(
     Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT, false).parent;
 
-  is(scalars["contentblocking.enabled"], true, "CB was enabled at startup");
-  is(scalars["contentblocking.fastblock_enabled"], AppConstants.NIGHTLY_BUILD,
-     "FB is enabled in Nightly");
+  is(scalars["contentblocking.enabled"], Services.prefs.getBoolPref("browser.contentblocking.enabled"),
+    "CB enabled status was recorded at startup");
+  is(scalars["contentblocking.fastblock_enabled"], Services.prefs.getBoolPref("browser.fastblock.enabled"),
+    "FB enabled status was recorded at startup");
   is(scalars["contentblocking.exceptions"], 0, "no CB exceptions at startup");
 });
 
 
 add_task(async function testShieldHistogram() {
   Services.prefs.setBoolPref(PREF, true);
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
 
--- a/browser/components/pocket/content/Pocket.jsm
+++ b/browser/components/pocket/content/Pocket.jsm
@@ -29,23 +29,18 @@ var Pocket = {
    * Functions related to the Pocket panel UI.
    */
   onShownInPhotonPageActionPanel(panel, iframe) {
     let window = panel.ownerGlobal;
     window.pktUI.setPhotonPageActionPanelFrame(iframe);
     Pocket._initPanelView(window);
   },
 
-  onPanelViewShowing(event) {
-    Pocket._initPanelView(event.target.ownerGlobal);
-  },
-
   _initPanelView(window) {
     let document = window.document;
-    let iframe = window.pktUI.getPanelFrame();
 
     let libraryButton = document.getElementById("library-button");
     if (libraryButton) {
       BrowserUtils.setToolbarButtonHeightProperty(libraryButton);
     }
 
     let urlToSave = Pocket._urlToSave;
     let titleToSave = Pocket._titleToSave;
@@ -54,46 +49,19 @@ var Pocket = {
     // ViewShowing fires immediately before it creates the contents,
     // in lieu of an AfterViewShowing event, just spin the event loop.
     window.setTimeout(function() {
       if (urlToSave) {
         window.pktUI.tryToSaveUrl(urlToSave, titleToSave);
       } else {
         window.pktUI.tryToSaveCurrentPage();
       }
-
-      // pocketPanelDidHide in main.js set iframe to about:blank when it was
-      // hidden, make sure we're loading the save panel.
-      if (iframe.contentDocument &&
-          iframe.contentDocument.readyState == "complete" &&
-          iframe.contentDocument.documentURI != "about:blank") {
-        window.pktUI.pocketPanelDidShow();
-      } else {
-        // iframe didn't load yet. This seems to always be the case when in
-        // the toolbar panel, but never the case for a subview.
-        // XXX this only being fired when it's a _capturing_ listener!
-        iframe.addEventListener("load", Pocket.onFrameLoaded, true);
-      }
     }, 0);
   },
 
-  onFrameLoaded(event) {
-    let document = event.currentTarget.ownerDocument;
-    let window = document.defaultView;
-    let iframe = window.pktUI.getPanelFrame();
-
-    iframe.removeEventListener("load", Pocket.onFrameLoaded, true);
-    window.pktUI.pocketPanelDidShow();
-  },
-
-  onPanelViewHiding(event) {
-    let window = event.target.ownerGlobal;
-    window.pktUI.pocketPanelDidHide(event);
-  },
-
   _urlToSave: null,
   _titleToSave: null,
   savePage(browser, url, title) {
     if (this.pageAction) {
       this._urlToSave = url;
       this._titleToSave = title;
       this.pageAction.doCommand(browser.ownerGlobal);
     }
--- a/browser/components/pocket/content/main.js
+++ b/browser/components/pocket/content/main.js
@@ -50,90 +50,37 @@ ChromeUtils.defineModuleGetter(this, "Pr
 ChromeUtils.defineModuleGetter(this, "ReaderMode",
   "resource://gre/modules/ReaderMode.jsm");
 ChromeUtils.defineModuleGetter(this, "pktApi",
   "chrome://pocket/content/pktApi.jsm");
 
 var pktUI = (function() {
 
     // -- Initialization (on startup and new windows) -- //
-    var _currentPanelDidShow;
-    var _currentPanelDidHide;
 
     // Init panel id at 0. The first actual panel id will have the number 1 so
     // in case at some point any panel has the id 0 we know there is something
     // wrong
     var _panelId = 0;
 
     var overflowMenuWidth = 230;
     var overflowMenuHeight = 475;
     var savePanelWidth = 350;
     var savePanelHeights = {collapsed: 153, expanded: 272};
 
-    var _lastAddSucceeded = false;
-
-    // -- Event Handling -- //
-
-    /**
-     * Event handler when Pocket toolbar button is pressed
-     */
-
-    function pocketPanelDidShow(event) {
-        if (_currentPanelDidShow) {
-            _currentPanelDidShow(event);
-        }
-
-    }
-
-    function pocketPanelDidHide(event) {
-        if (_currentPanelDidHide) {
-            _currentPanelDidHide(event);
-        }
-
-        // clear the panel
-        getPanelFrame().setAttribute("src", "about:blank");
-
-        if (_lastAddSucceeded) {
-            var libraryButton = document.getElementById("library-button");
-            if (!Services.prefs.getBoolPref("toolkit.cosmeticAnimations.enabled") ||
-                !libraryButton ||
-                libraryButton.getAttribute("cui-areatype") == "menu-panel" ||
-                libraryButton.getAttribute("overflowedItem") == "true" ||
-                !libraryButton.closest("#nav-bar")) {
-                return;
-            }
-            libraryButton.removeAttribute("fade");
-            libraryButton.setAttribute("animate", "pocket");
-            libraryButton.addEventListener("animationend", onLibraryButtonAnimationEnd);
-        }
-    }
-
-    function onLibraryButtonAnimationEnd(event) {
-        let doc = event.target.ownerDocument;
-        let libraryButton = doc.getElementById("library-button");
-        if (event.animationName.startsWith("library-pocket-animation")) {
-            libraryButton.setAttribute("fade", "true");
-        } else if (event.animationName == "library-pocket-fade") {
-            libraryButton.removeEventListener("animationend", onLibraryButtonAnimationEnd);
-            libraryButton.removeAttribute("animate");
-            libraryButton.removeAttribute("fade");
-        }
-    }
-
     // -- Communication to API -- //
 
     /**
      * Either save or attempt to log the user in
      */
     function tryToSaveCurrentPage() {
         tryToSaveUrl(getCurrentUrl(), getCurrentTitle());
     }
 
     function tryToSaveUrl(url, title) {
-
         // If the user is logged in, go ahead and save the current page
         if (pktApi.isUserLoggedIn()) {
             saveAndShowConfirmation(url, title);
             return;
         }
 
         // If the user is not logged in, show the logged-out state to prompt them to authenticate
         showSignUp();
@@ -190,19 +137,16 @@ var pktUI = (function() {
                 + "&variant="
                 + variant
                 + "&controlvariant="
                 + controlvariant
                 + "&inoverflowmenu="
                 + inOverflowMenu
                 + "&locale="
                 + getUILocale(), {
-                    onShow() {
-                    },
-                    onHide: panelDidHide,
                     width: inOverflowMenu ? overflowMenuWidth : 300,
                     height: startheight,
             });
         });
     }
 
     /**
      * Show the logged-out state / sign-up panel
@@ -226,17 +170,16 @@ var pktUI = (function() {
             var panelId = showPanel("about:pocket-saved?pockethost="
                 + Services.prefs.getCharPref("extensions.pocket.site")
                 + "&premiumStatus=" + (pktApi.isPremiumUser() ? "1" : "0")
                 + "&fxasignedin=" + ((typeof userdata == "object" && userdata !== null) ? "1" : "0")
                 + "&inoverflowmenu=" + inOverflowMenu
                 + "&locale=" + getUILocale(), {
                 onShow() {
                     var saveLinkMessageId = "saveLink";
-                    _lastAddSucceeded = false;
                     getPanelFrame().setAttribute("itemAdded", "false");
 
                     // Send error message for invalid url
                     if (!isValidURL) {
                         // TODO: Pass key for localized error in error object
                         let error = {
                             message: "Only links can be saved",
                             localizedKey: "onlylinkssaved",
@@ -265,17 +208,16 @@ var pktUI = (function() {
                             var successResponse = {
                                 status: "success",
                                 accountState,
                                 displayName,
                                 item,
                                 ho2,
                             };
                             pktUIMessaging.sendMessageToPanel(panelId, saveLinkMessageId, successResponse);
-                            _lastAddSucceeded = true;
                             getPanelFrame().setAttribute("itemAdded", "true");
                         },
                         error(error, request) {
                             // If user is not authorized show singup page
                             if (request.status === 401) {
                                 showSignUp();
                                 return;
                             }
@@ -293,52 +235,52 @@ var pktUI = (function() {
                     // Add title if given
                     if (typeof title !== "undefined") {
                         options.title = title;
                     }
 
                     // Send the link
                     pktApi.addLink(url, options);
                 },
-                onHide: panelDidHide,
                 width: inOverflowMenu ? overflowMenuWidth : savePanelWidth,
                 height: startheight,
             });
         });
     }
 
     /**
      * Open a generic panel
      */
     function showPanel(url, options) {
-
         // Add new panel id
         _panelId += 1;
         url += ("&panelId=" + _panelId);
 
         // We don't have to hide and show the panel again if it's already shown
         // as if the user tries to click again on the toolbar button the overlay
         // will close instead of the button will be clicked
         var iframe = getPanelFrame();
+        options.onShow = options.onShow || (() => {});
 
         // Register event handlers
         registerEventMessages();
 
         // Load the iframe
         iframe.setAttribute("src", url);
 
-        // Uncomment to leave panel open -- for debugging
-        // panel.setAttribute('noautohide', true);
-        // panel.setAttribute('consumeoutsideclicks', false);
-        //
-
-        // For some reason setting onpopupshown and onpopuphidden on the panel directly didn't work, so
-        // do it this hacky way for now
-        _currentPanelDidShow = options.onShow;
-        _currentPanelDidHide = options.onHide;
+        if (iframe.contentDocument &&
+            iframe.contentDocument.readyState == "complete" &&
+            iframe.contentDocument.documentURI != "about:blank") {
+          options.onShow();
+        } else {
+          // iframe didn't load yet. This seems to always be the case when in
+          // the toolbar panel, but never the case for a subview.
+          // XXX this only being fired when it's a _capturing_ listener!
+          iframe.addEventListener("load", options.onShow, { once: true, capture: true });
+        }
 
         resizePanel({
             width: options.width,
             height: options.height,
         });
         return _panelId;
     }
 
@@ -354,25 +296,16 @@ var pktUI = (function() {
         var iframe = getPanelFrame();
 
         // Set an explicit size, panel will adapt.
         iframe.style.width  = options.width + "px";
         iframe.style.height = options.height + "px";
     }
 
     /**
-     * Called when the signup and saved panel was hidden
-     */
-    function panelDidHide() {
-        // clear the onShow and onHide values
-        _currentPanelDidShow = null;
-        _currentPanelDidHide = null;
-    }
-
-    /**
      * Register all of the messages needed for the panels
      */
     function registerEventMessages() {
         var iframe = getPanelFrame();
 
         // Only register the messages once
         var didInitAttributeKey = "did_init";
         var didInitMessageListener = iframe.getAttribute(didInitAttributeKey);
@@ -512,17 +445,16 @@ var pktUI = (function() {
 
         // Based on clicking "remove page" CTA, and passed unique item id, remove the item
         var _deleteItemMessageId = "deleteItem";
         pktUIMessaging.addMessageListener(iframe, _deleteItemMessageId, function(panelId, data) {
             pktApi.deleteItem(data.itemId, {
                 success(data, response) {
                     var successResponse = {status: "success"};
                     pktUIMessaging.sendResponseMessageToPanel(panelId, _deleteItemMessageId, successResponse);
-                    _lastAddSucceeded = false;
                     getPanelFrame().setAttribute("itemAdded", "false");
                 },
                 error(error, response) {
                     pktUIMessaging.sendErrorResponseMessageToPanel(panelId, _deleteItemMessageId, error);
                 },
             });
         });
 
@@ -631,19 +563,16 @@ var pktUI = (function() {
      * Public functions
      */
     return {
         setPhotonPageActionPanelFrame,
         getPanelFrame,
 
         openTabWithUrl,
 
-        pocketPanelDidShow,
-        pocketPanelDidHide,
-
         tryToSaveUrl,
         tryToSaveCurrentPage,
     };
 }());
 
 // -- Communication to Background -- //
 // https://developer.mozilla.org/en-US/Add-ons/Code_snippets/Interaction_between_privileged_and_non-privileged_pages
 var pktUIMessaging = (function() {
--- a/browser/components/preferences/in-content/extensionControlled.js
+++ b/browser/components/preferences/in-content/extensionControlled.js
@@ -39,16 +39,17 @@ const API_PROXY_PREFS = [
   "network.proxy.autoconfig_url",
   "signon.autologin.proxy",
 ];
 
 let extensionControlledContentIds = {
   "privacy.containers": "browserContainersExtensionContent",
   "homepage_override": "browserHomePageExtensionContent",
   "newTabURL": "browserNewTabExtensionContent",
+  "webNotificationsDisabled": "browserNotificationsPermissionExtensionContent",
   "defaultSearch": "browserDefaultSearchExtensionContent",
   "proxy.settings": "proxyExtensionContent",
   get "websites.trackingProtectionMode"() {
     return {
       button: contentBlockingUiEnabled ?
         "contentBlockingDisableTrackingProtectionExtension" :
         "trackingProtectionExtensionContentButton",
       section: contentBlockingUiEnabled ?
@@ -56,16 +57,17 @@ let extensionControlledContentIds = {
         "trackingProtectionExtensionContentLabel",
     };
   },
 };
 
 const extensionControlledL10nKeys = {
   "homepage_override": "homepage-override",
   "newTabURL": "new-tab-url",
+  "webNotificationsDisabled": "web-notifications",
   "defaultSearch": "default-search",
   "privacy.containers": "privacy-containers",
   "websites.trackingProtectionMode": contentBlockingUiEnabled ?
                                        "websites-content-blocking-all-trackers" :
                                        "websites-tracking-protection-mode",
   "proxy.settings": "proxy-config",
 };
 
--- a/browser/components/preferences/in-content/tests/browser_extension_controlled.js
+++ b/browser/components/preferences/in-content/tests/browser_extension_controlled.js
@@ -6,16 +6,18 @@ ChromeUtils.defineModuleGetter(this, "Ex
                                "resource://gre/modules/ExtensionSettingsStore.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
 XPCOMUtils.defineLazyPreferenceGetter(this, "proxyType", PROXY_PREF);
 
 const TEST_DIR = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
 const CHROME_URL_ROOT = TEST_DIR + "/";
+const PERMISSIONS_URL = "chrome://browser/content/preferences/sitePermissions.xul";
+let sitePermissionsDialog;
 
 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);
 }
@@ -63,16 +65,29 @@ function waitForEnableMessage(messageId,
 
 function waitForMessageContent(messageId, l10nId, doc) {
   return waitForMessageChange(
     getElement(messageId, doc),
     target => doc.l10n.getAttributes(target).id === l10nId,
     { childList: true });
 }
 
+async function openNotificationsPermissionDialog() {
+  let dialogOpened = promiseLoadSubDialog(PERMISSIONS_URL);
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    let doc = content.document;
+    let settingsButton = doc.getElementById("notificationSettingsButton");
+    settingsButton.click();
+  });
+
+  sitePermissionsDialog = await dialogOpened;
+  await sitePermissionsDialog.document.mozSubdialogReady;
+}
+
 add_task(async function testExtensionControlledHomepage() {
   await openPreferencesViaOpenPreferencesAPI("paneHome", {leaveOpen: true});
   // eslint-disable-next-line mozilla/no-cpows-in-tests
   let doc = gBrowser.contentDocument;
   is(gBrowser.currentURI.spec, "about:preferences#home",
      "#home should be in the URI for about:preferences");
   let homepagePref = () => Services.prefs.getCharPref("browser.startup.homepage");
   let originalHomepagePref = homepagePref();
@@ -334,16 +349,85 @@ add_task(async function testExtensionCon
   is(controlledContent.hidden, true, "The extension controlled row is shown");
 
   // Cleanup the tab and add-on.
   BrowserTestUtils.removeTab(gBrowser.selectedTab);
   let addon = await AddonManager.getAddonByID("@set_newtab");
   await addon.uninstall();
 });
 
+add_task(async function testExtensionControlledWebNotificationsPermission() {
+  let manifest = {
+    manifest_version: 2,
+    name: "TestExtension",
+    version: "1.0",
+    description: "Testing WebNotificationsDisable",
+    applications: {gecko: {id: "@web_notifications_disable"}},
+    permissions: [
+      "browserSettings",
+    ],
+    browser_action: {
+      default_title: "Testing",
+    },
+  };
+
+  await openPreferencesViaOpenPreferencesAPI("privacy", {leaveOpen: true});
+  await openNotificationsPermissionDialog();
+
+  let doc = sitePermissionsDialog.document;
+  let extensionControlledContent = doc.getElementById("browserNotificationsPermissionExtensionContent");
+
+  // Test that extension content is initially hidden.
+  ok(extensionControlledContent.hidden, "Extension content is initially hidden");
+
+  // Install an extension that will disable web notifications permission.
+  let messageShown = waitForMessageShown("browserNotificationsPermissionExtensionContent", doc);
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest,
+    useAddonManager: "permanent",
+    background() {
+      browser.browserSettings.webNotificationsDisabled.set({value: true});
+      browser.test.sendMessage("load-extension");
+    },
+  });
+  await extension.startup();
+  await extension.awaitMessage("load-extension");
+  await messageShown;
+
+  let controlledDesc = extensionControlledContent.querySelector("description");
+  Assert.deepEqual(doc.l10n.getAttributes(controlledDesc), {
+    id: "extension-controlled-web-notifications",
+    args: {
+      name: "TestExtension",
+    },
+  }, "The user is notified that an extension is controlling the web notifications permission");
+  is(extensionControlledContent.hidden, false, "The extension controlled row is not hidden");
+
+  // Disable the extension.
+  doc.getElementById("disableNotificationsPermissionExtension").click();
+
+  // Verify the user is notified how to enable the extension.
+  await waitForEnableMessage(extensionControlledContent.id, doc);
+  is(doc.l10n.getAttributes(controlledDesc.querySelector("label")).id,
+    "extension-controlled-enable",
+    "The user is notified of how to enable the extension again");
+
+  // Verify the enable message can be dismissed.
+  let hidden = waitForMessageHidden(extensionControlledContent.id, doc);
+  let dismissButton = controlledDesc.querySelector("image:last-of-type");
+  dismissButton.click();
+  await hidden;
+
+  // Verify that the extension controlled content in hidden again.
+  is(extensionControlledContent.hidden, true, "The extension controlled row is now hidden");
+
+  await extension.unload();
+  BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
 add_task(async function testExtensionControlledDefaultSearch() {
   await openPreferencesViaOpenPreferencesAPI("paneSearch", {leaveOpen: true});
   let doc = gBrowser.contentDocument;
   let extensionId = "@set_default_search";
   let manifest = {
     manifest_version: 2,
     name: "set_default_search",
     applications: {gecko: {id: extensionId}},
--- a/browser/components/preferences/sitePermissions.css
+++ b/browser/components/preferences/sitePermissions.css
@@ -18,18 +18,22 @@
   min-height: 35px;
 }
 
 .website-status {
   margin: 1px;
   margin-inline-end: 5px;
 }
 
+#browserNotificationsPermissionExtensionContent,
 #permissionsDisableDescription {
   margin-inline-start: 32px;
+}
+
+#permissionsDisableDescription {
   color: #737373;
   line-height: 110%;
 }
 
 #permissionsDisableCheckbox {
   margin-inline-start: 4px;
   padding-top: 10px;
 }
--- a/browser/components/preferences/sitePermissions.js
+++ b/browser/components/preferences/sitePermissions.js
@@ -1,12 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+/* import-globals-from in-content/extensionControlled.js */
+
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 ChromeUtils.import("resource:///modules/SitePermissions.jsm");
 
 const sitePermissionsL10n = {
   "desktop-notification": {
     window: "permissions-site-notification-window",
     description: "permissions-site-notification-desc",
@@ -37,16 +39,18 @@ function Permission(principal, type, cap
   this.principal = principal;
   this.origin = principal.origin;
   this.type = type;
   this.capability = capability;
   this.l10nId = l10nId;
 }
 
 const PERMISSION_STATES = [SitePermissions.ALLOW, SitePermissions.BLOCK, SitePermissions.PROMPT];
+const NOTIFICATIONS_PERMISSION_OVERRIDE_KEY = "webNotificationsDisabled";
+const NOTIFICATIONS_PERMISSION_PREF = "permissions.default.desktop-notification";
 
 var gSitePermissionsManager = {
   _type: "",
   _isObserving: false,
   _permissions: new Map(),
   _permissionsToChange: new Map(),
   _permissionsToDelete: new Map(),
   _list: null,
@@ -69,52 +73,38 @@ var gSitePermissionsManager = {
     }
 
     this._type = params.permissionType;
     this._list = document.getElementById("permissionsBox");
     this._removeButton = document.getElementById("removePermission");
     this._removeAllButton = document.getElementById("removeAllPermissions");
     this._searchBox = document.getElementById("searchBox");
     this._checkbox = document.getElementById("permissionsDisableCheckbox");
+    this._disableExtensionButton = document.getElementById("disableNotificationsPermissionExtension");
+    this._permissionsDisableDescription = document.getElementById("permissionsDisableDescription");
 
-    let permissionsDisableDescription = document.getElementById("permissionsDisableDescription");
     let permissionsText = document.getElementById("permissionsText");
 
     let l10n = sitePermissionsL10n[this._type];
     document.l10n.setAttributes(permissionsText, l10n.description);
     document.l10n.setAttributes(this._checkbox, l10n.disableLabel);
-    document.l10n.setAttributes(permissionsDisableDescription, l10n.disableDescription);
+    document.l10n.setAttributes(this._permissionsDisableDescription, l10n.disableDescription);
     document.l10n.setAttributes(document.documentElement, l10n.window);
 
     await document.l10n.translateElements([
       permissionsText,
       this._checkbox,
-      permissionsDisableDescription,
+      this._permissionsDisableDescription,
       document.documentElement,
     ]);
 
-    // Initialize the checkbox state.
+    // Initialize the checkbox state and handle showing notification permission UI
+    // when it is disabled by an extension.
     this._defaultPermissionStatePrefName = "permissions.default." + this._type;
-    let pref = Services.prefs.getPrefType(this._defaultPermissionStatePrefName);
-    if (pref != Services.prefs.PREF_INVALID) {
-      this._currentDefaultPermissionsState = Services.prefs.getIntPref(this._defaultPermissionStatePrefName);
-    }
-
-    if (this._currentDefaultPermissionsState === null) {
-      this._checkbox.setAttribute("hidden", true);
-      permissionsDisableDescription.setAttribute("hidden", true);
-    } else if (this._currentDefaultPermissionsState == SitePermissions.BLOCK) {
-      this._checkbox.checked = true;
-    } else {
-      this._checkbox.checked = false;
-    }
-
-    if (Services.prefs.prefIsLocked(this._defaultPermissionStatePrefName)) {
-      this._checkbox.disabled = true;
-    }
+    this._watchPermissionPrefChange();
 
     this._loadPermissions();
     this.buildPermissionsList();
 
     this._searchBox.focus();
   },
 
   uninit() {
@@ -150,16 +140,78 @@ var gSitePermissionsManager = {
 
   _handleCapabilityChange(perm) {
     let permissionlistitem = document.getElementsByAttribute("origin", perm.origin)[0];
     let menulist = permissionlistitem.getElementsByTagName("menulist")[0];
     menulist.selectedItem =
       menulist.getElementsByAttribute("value", perm.capability)[0];
   },
 
+  _handleCheckboxUIUpdates() {
+    let pref = Services.prefs.getPrefType(this._defaultPermissionStatePrefName);
+    if (pref != Services.prefs.PREF_INVALID) {
+      this._currentDefaultPermissionsState = Services.prefs.getIntPref(this._defaultPermissionStatePrefName);
+    }
+
+    if (this._currentDefaultPermissionsState === null) {
+      this._checkbox.setAttribute("hidden", true);
+      this._permissionsDisableDescription.setAttribute("hidden", true);
+    } else if (this._currentDefaultPermissionsState == SitePermissions.BLOCK) {
+      this._checkbox.checked = true;
+    } else {
+      this._checkbox.checked = false;
+    }
+
+    if (Services.prefs.prefIsLocked(this._defaultPermissionStatePrefName)) {
+      this._checkbox.disabled = true;
+    }
+  },
+
+  /**
+  * Listen for changes to the permissions.default.* pref and make
+  * necessary changes to the UI.
+  */
+  _watchPermissionPrefChange() {
+    this._handleCheckboxUIUpdates();
+
+    if (this._type == "desktop-notification") {
+      this._handleWebNotificationsDisable();
+
+      this._disableExtensionButton.addEventListener(
+        "command",
+        makeDisableControllingExtension(PREF_SETTING_TYPE, NOTIFICATIONS_PERMISSION_OVERRIDE_KEY)
+      );
+    }
+
+    let observer = () => {
+      this._handleCheckboxUIUpdates();
+      if (this._type == "desktop-notification") {
+        this._handleWebNotificationsDisable();
+      }
+    };
+    Services.prefs.addObserver(this._defaultPermissionStatePrefName, observer);
+    window.addEventListener("unload", () => {
+      Services.prefs.removeObserver(this._defaultPermissionStatePrefName, observer);
+    });
+  },
+
+  /**
+  * Handles the UI update for web notifications disable by extensions.
+  */
+  async _handleWebNotificationsDisable() {
+    let prefLocked = Services.prefs.prefIsLocked(NOTIFICATIONS_PERMISSION_PREF);
+    if (prefLocked) {
+      // An extension can't control these settings if they're locked.
+      hideControllingExtension(NOTIFICATIONS_PERMISSION_OVERRIDE_KEY);
+    } else {
+      let isControlled = await handleControllingExtension(PREF_SETTING_TYPE, NOTIFICATIONS_PERMISSION_OVERRIDE_KEY);
+      this._checkbox.disabled = isControlled;
+    }
+  },
+
   _getCapabilityString(capability) {
     let stringKey = null;
     switch (capability) {
     case Services.perms.ALLOW_ACTION:
       stringKey = "permissions-capabilities-allow";
       break;
     case Services.perms.DENY_ACTION:
       stringKey = "permissions-capabilities-block";
--- a/browser/components/preferences/sitePermissions.xul
+++ b/browser/components/preferences/sitePermissions.xul
@@ -14,20 +14,22 @@
         data-l10n-id="permissions-window"
         data-l10n-attrs="title, style"
         onload="gSitePermissionsManager.onLoad();"
         onunload="gSitePermissionsManager.uninit();"
         persist="screenX screenY width height"
         onkeypress="gSitePermissionsManager.onWindowKeyPress(event);">
 
   <linkset>
+    <link rel="localization" href="browser/preferences/preferences.ftl"/>
     <link rel="localization" href="browser/preferences/permissions.ftl"/>
   </linkset>
 
   <script src="chrome://browser/content/preferences/sitePermissions.js"/>
+  <script type="application/javascript" src="chrome://browser/content/preferences/in-content/extensionControlled.js"/>
 
   <keyset>
     <key data-l10n-id="permissions-close-key" modifiers="accel" oncommand="window.close();"/>
   </keyset>
 
   <vbox class="contentPane largeDialogContainer" flex="1">
     <description id="permissionsText" control="url"/>
     <separator class="thin"/>
@@ -58,16 +60,23 @@
               data-l10n-id="permissions-remove-all"
               icon="clear"
               oncommand="gSitePermissionsManager.onAllPermissionsDelete();"/>
     </hbox>
     <spacer flex="1"/>
     <checkbox id="permissionsDisableCheckbox"/>
     <description id="permissionsDisableDescription"/>
     <spacer flex="1"/>
+    <hbox id="browserNotificationsPermissionExtensionContent" 
+          class="extension-controlled" align="center" hidden="true">
+      <description control="disableNotificationsPermissionExtension" flex="1"/>
+      <button id="disableNotificationsPermissionExtension"
+              class="extension-controlled-button accessory-button"
+              data-l10n-id="disable-extension"/>
+    </hbox>
     <hbox class="actionButtons" align="right" flex="1">
       <button oncommand="close();" icon="close" id="cancel"
               data-l10n-id="permissions-button-cancel" />
       <button id="btnApplyChanges" oncommand="gSitePermissionsManager.onApplyChanges();" icon="save"
               data-l10n-id="permissions-button-ok" />
     </hbox>
   </vbox>
 </window>
--- a/browser/locales/en-US/browser/preferences/preferences.ftl
+++ b/browser/locales/en-US/browser/preferences/preferences.ftl
@@ -92,16 +92,20 @@ restart-later = Restart Later
 # This string is shown to notify the user that their home page
 # is being controlled by an extension.
 extension-controlled-homepage-override = An extension, <img data-l10n-name="icon"/> { $name }, is controlling your home page.
 
 # This string is shown to notify the user that their new tab page
 # is being controlled by an extension.
 extension-controlled-new-tab-url = An extension, <img data-l10n-name="icon"/> { $name }, is controlling your New Tab page.
 
+# This string is shown to notify the user that their notifications permission
+# is being controlled by an extension.
+extension-controlled-web-notifications= An extension, <img data-l10n-name="icon"/> { $name }, is controlling this setting.
+
 # This string is shown to notify the user that the default search engine
 # is being controlled by an extension.
 extension-controlled-default-search = An extension, <img data-l10n-name="icon"/> { $name }, has set your default search engine.
 
 # This string is shown to notify the user that Container Tabs
 # are being enabled by an extension.
 extension-controlled-privacy-containers = An extension, <img data-l10n-name="icon"/> { $name }, requires Container Tabs.
 
--- a/caps/NullPrincipal.cpp
+++ b/caps/NullPrincipal.cpp
@@ -33,34 +33,29 @@ NS_IMPL_QUERY_INTERFACE_CI(NullPrincipal
                            nsISerializable)
 NS_IMPL_CI_INTERFACE_GETTER(NullPrincipal,
                             nsIPrincipal,
                             nsISerializable)
 
 /* static */ already_AddRefed<NullPrincipal>
 NullPrincipal::CreateWithInheritedAttributes(nsIPrincipal* aInheritFrom)
 {
-  MOZ_ASSERT(aInheritFrom);
-  return CreateWithInheritedAttributes(Cast(aInheritFrom)->OriginAttributesRef(), false);
+  RefPtr<NullPrincipal> nullPrin = new NullPrincipal();
+  nsresult rv = nullPrin->Init(Cast(aInheritFrom)->OriginAttributesRef());
+  MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+  return nullPrin.forget();
 }
 
 /* static */ already_AddRefed<NullPrincipal>
 NullPrincipal::CreateWithInheritedAttributes(nsIDocShell* aDocShell, bool aIsFirstParty)
 {
-  MOZ_ASSERT(aDocShell);
-
   OriginAttributes attrs = nsDocShell::Cast(aDocShell)->GetOriginAttributes();
-  return CreateWithInheritedAttributes(attrs, aIsFirstParty);
-}
 
-/* static */ already_AddRefed<NullPrincipal>
-NullPrincipal::CreateWithInheritedAttributes(const OriginAttributes& aOriginAttributes, bool aIsFirstParty)
-{
   RefPtr<NullPrincipal> nullPrin = new NullPrincipal();
-  nsresult rv = nullPrin->Init(aOriginAttributes, aIsFirstParty);
+  nsresult rv = nullPrin->Init(attrs, aIsFirstParty);
   MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
   return nullPrin.forget();
 }
 
 /* static */ already_AddRefed<NullPrincipal>
 NullPrincipal::Create(const OriginAttributes& aOriginAttributes, nsIURI* aURI)
 {
   RefPtr<NullPrincipal> nullPrin = new NullPrincipal();
--- a/caps/NullPrincipal.h
+++ b/caps/NullPrincipal.h
@@ -51,29 +51,24 @@ public:
   NS_IMETHOD GetHashValue(uint32_t* aHashValue) override;
   NS_IMETHOD SetCsp(nsIContentSecurityPolicy* aCsp) override;
   NS_IMETHOD GetURI(nsIURI** aURI) override;
   NS_IMETHOD GetDomain(nsIURI** aDomain) override;
   NS_IMETHOD SetDomain(nsIURI* aDomain) override;
   NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override;
   NS_IMETHOD GetAddonId(nsAString& aAddonId) override;
 
-  static already_AddRefed<NullPrincipal>
-  CreateWithInheritedAttributes(nsIPrincipal* aInheritFrom);
+  static already_AddRefed<NullPrincipal> CreateWithInheritedAttributes(nsIPrincipal* aInheritFrom);
 
   // Create NullPrincipal with origin attributes from docshell.
   // If aIsFirstParty is true, and the pref 'privacy.firstparty.isolate' is also
   // enabled, the mFirstPartyDomain value of the origin attributes will be set
   // to an unique value.
   static already_AddRefed<NullPrincipal>
-  CreateWithInheritedAttributes(nsIDocShell* aDocShell,
-                                bool aIsFirstParty = false);
-  static already_AddRefed<NullPrincipal>
-  CreateWithInheritedAttributes(const OriginAttributes& aOriginAttributes,
-                                bool aIsFirstParty = false);
+  CreateWithInheritedAttributes(nsIDocShell* aDocShell, bool aIsFirstParty = false);
 
   static already_AddRefed<NullPrincipal>
   Create(const OriginAttributes& aOriginAttributes,
          nsIURI* aURI = nullptr);
 
   static already_AddRefed<NullPrincipal>
   CreateWithoutOriginAttributes();
 
@@ -85,18 +80,17 @@ public:
   nsresult GetSiteIdentifier(SiteIdentifier& aSite) override {
     aSite.Init(this);
     return NS_OK;
   }
 
  protected:
   virtual ~NullPrincipal() = default;
 
-  bool SubsumesInternal(nsIPrincipal* aOther,
-                        DocumentDomainConsideration aConsideration) override
+  bool SubsumesInternal(nsIPrincipal* aOther, DocumentDomainConsideration aConsideration) override
   {
     return aOther == this;
   }
 
   bool MayLoadInternal(nsIURI* aURI) override;
 
   nsCOMPtr<nsIURI> mURI;
 
--- a/devtools/client/webconsole/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/test/mochitest/browser.ini
@@ -200,16 +200,17 @@ skip-if = verify
 [browser_jsterm_autocomplete_inside_text.js]
 [browser_jsterm_autocomplete_native_getters.js]
 [browser_jsterm_autocomplete_nav_and_tab_key.js]
 [browser_jsterm_autocomplete_paste_undo.js]
 [browser_jsterm_autocomplete_return_key_no_selection.js]
 [browser_jsterm_autocomplete_return_key.js]
 [browser_jsterm_autocomplete_width.js]
 [browser_jsterm_autocomplete-properties-with-non-alphanumeric-names.js]
+[browser_jsterm_await_concurrent.js]
 [browser_jsterm_await_error.js]
 [browser_jsterm_await_helper_dollar_underscore.js]
 [browser_jsterm_await_paused.js]
 [browser_jsterm_await.js]
 [browser_jsterm_completion_bracket_cached_results.js]
 [browser_jsterm_completion_bracket.js]
 [browser_jsterm_completion_case_sensitivity.js]
 [browser_jsterm_completion.js]
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_await.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_await.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-// Test that top-level await expression work as expected.
+// Test that top-level await expressions work as expected.
 
 "use strict";
 
 const TEST_URI = "data:text/html;charset=utf-8,Web Console test top-level await";
 
 add_task(async function() {
   // Enable await mapping.
   await pushPref("devtools.debugger.features.map-await-expression", true);
@@ -16,31 +16,30 @@ add_task(async function() {
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
   const hud = await openNewTabAndConsole(TEST_URI);
-  const {jsterm} = hud;
 
   const executeAndWaitForResultMessage = (input, expectedOutput) =>
     executeAndWaitForMessage(hud, input, expectedOutput, ".result");
 
   info("Evaluate a top-level await expression");
   const simpleAwait = `await new Promise(r => setTimeout(() => r(["await1"]), 500))`;
   await executeAndWaitForResultMessage(
     simpleAwait,
     `Array [ "await1" ]`,
   );
 
   // Check that the resulting promise of the async iife is not displayed.
-  let messages = hud.ui.outputNode.querySelectorAll(".message .message-body");
-  let messagesText = Array.from(messages).map(n => n.textContent).join(" - ");
+  const messages = hud.ui.outputNode.querySelectorAll(".message .message-body");
+  const messagesText = Array.from(messages).map(n => n.textContent).join(" - ");
   is(messagesText, `${simpleAwait} - Array [ "await1" ]`,
     "The output contains the the expected messages");
 
   // Check that the timestamp of the result is accurate
   const {
     visibleMessages,
     messagesById
   } = hud.ui.consoleOutput.getStore().getState().messages;
@@ -57,40 +56,15 @@ async function performTests() {
   );
 
   let message = await executeAndWaitForResultMessage(
     `"-" + x + "-"`,
     `"-await2-"`,
   );
   ok(message.node, "`x` was assigned as expected");
 
-  info("Check that concurrent await expression work fine");
-  hud.ui.clearOutput();
-  const delays = [1000, 500, 2000, 1500];
-  const inputs = delays.map(delay => `await new Promise(
-    r => setTimeout(() => r("await-concurrent-" + ${delay}), ${delay}))`);
-
-  // Let's wait for the message that sould be displayed last.
-  const onMessage = waitForMessage(hud, "await-concurrent-2000", ".message.result");
-  for (const input of inputs) {
-    jsterm.execute(input);
-  }
-  await onMessage;
-
-  messages = hud.ui.outputNode.querySelectorAll(".message .message-body");
-  messagesText = Array.from(messages).map(n => n.textContent);
-  const expectedMessages = [
-    ...inputs,
-    `"await-concurrent-500"`,
-    `"await-concurrent-1000"`,
-    `"await-concurrent-1500"`,
-    `"await-concurrent-2000"`,
-  ];
-  is(JSON.stringify(messagesText, null, 2), JSON.stringify(expectedMessages, null, 2),
-    "The output contains the the expected messages, in the expected order");
-
   info("Check that a logged promise is still displayed as a promise");
   message = await executeAndWaitForResultMessage(
     `new Promise(r => setTimeout(() => r(1), 1000))`,
     `Promise {`,
   );
   ok(message, "Promise are displayed as expected");
 }
copy from devtools/client/webconsole/test/mochitest/browser_jsterm_await.js
copy to devtools/client/webconsole/test/mochitest/browser_jsterm_await_concurrent.js
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_await.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_await_concurrent.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-// Test that top-level await expression work as expected.
+// Test that multiple concurrent top-level await expressions work as expected.
 
 "use strict";
 
 const TEST_URI = "data:text/html;charset=utf-8,Web Console test top-level await";
 
 add_task(async function() {
   // Enable await mapping.
   await pushPref("devtools.debugger.features.map-await-expression", true);
@@ -18,79 +18,32 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
   const hud = await openNewTabAndConsole(TEST_URI);
   const {jsterm} = hud;
 
-  const executeAndWaitForResultMessage = (input, expectedOutput) =>
-    executeAndWaitForMessage(hud, input, expectedOutput, ".result");
-
-  info("Evaluate a top-level await expression");
-  const simpleAwait = `await new Promise(r => setTimeout(() => r(["await1"]), 500))`;
-  await executeAndWaitForResultMessage(
-    simpleAwait,
-    `Array [ "await1" ]`,
-  );
-
-  // Check that the resulting promise of the async iife is not displayed.
-  let messages = hud.ui.outputNode.querySelectorAll(".message .message-body");
-  let messagesText = Array.from(messages).map(n => n.textContent).join(" - ");
-  is(messagesText, `${simpleAwait} - Array [ "await1" ]`,
-    "The output contains the the expected messages");
-
-  // Check that the timestamp of the result is accurate
-  const {
-    visibleMessages,
-    messagesById
-  } = hud.ui.consoleOutput.getStore().getState().messages;
-  const [commandId, resultId] = visibleMessages;
-  const delta = messagesById.get(resultId).timeStamp -
-    messagesById.get(commandId).timeStamp;
-  ok(delta >= 500,
-    `The result has a timestamp at least 500ms (${delta}ms) older than the command`);
-
-  info("Check that assigning the result of a top-level await expression works");
-  await executeAndWaitForResultMessage(
-    `x = await new Promise(r => setTimeout(() => r("await2"), 500))`,
-    `await2`,
-  );
-
-  let message = await executeAndWaitForResultMessage(
-    `"-" + x + "-"`,
-    `"-await2-"`,
-  );
-  ok(message.node, "`x` was assigned as expected");
-
-  info("Check that concurrent await expression work fine");
   hud.ui.clearOutput();
-  const delays = [1000, 500, 2000, 1500];
+  const delays = [3000, 500, 9000, 6000];
   const inputs = delays.map(delay => `await new Promise(
     r => setTimeout(() => r("await-concurrent-" + ${delay}), ${delay}))`);
 
   // Let's wait for the message that sould be displayed last.
-  const onMessage = waitForMessage(hud, "await-concurrent-2000", ".message.result");
+  const onMessage = waitForMessage(hud, "await-concurrent-9000", ".message.result");
   for (const input of inputs) {
     jsterm.execute(input);
   }
   await onMessage;
 
-  messages = hud.ui.outputNode.querySelectorAll(".message .message-body");
-  messagesText = Array.from(messages).map(n => n.textContent);
+  const messages = hud.ui.outputNode.querySelectorAll(".message .message-body");
+  const messagesText = Array.from(messages).map(n => n.textContent);
   const expectedMessages = [
     ...inputs,
     `"await-concurrent-500"`,
-    `"await-concurrent-1000"`,
-    `"await-concurrent-1500"`,
-    `"await-concurrent-2000"`,
+    `"await-concurrent-3000"`,
+    `"await-concurrent-6000"`,
+    `"await-concurrent-9000"`,
   ];
   is(JSON.stringify(messagesText, null, 2), JSON.stringify(expectedMessages, null, 2),
     "The output contains the the expected messages, in the expected order");
-
-  info("Check that a logged promise is still displayed as a promise");
-  message = await executeAndWaitForResultMessage(
-    `new Promise(r => setTimeout(() => r(1), 1000))`,
-    `Promise {`,
-  );
-  ok(message, "Promise are displayed as expected");
 }
--- a/devtools/client/webconsole/webconsole-output-wrapper.js
+++ b/devtools/client/webconsole/webconsole-output-wrapper.js
@@ -61,17 +61,17 @@ WebConsoleOutputWrapper.prototype = {
             timeStamp,
           }]));
         },
         hudProxy: hud.proxy,
         openLink: (url, e) => {
           hud.owner.openLink(url, e);
         },
         canRewind: () => {
-          if (!hud.owner.target.activeTab) {
+          if (!(hud.owner && hud.owner.target && hud.owner.target.activeTab)) {
             return false;
           }
 
           return hud.owner.target.activeTab.traits.canRewind;
         },
         createElement: nodename => {
           return this.document.createElement(nodename);
         },
--- a/docshell/base/moz.build
+++ b/docshell/base/moz.build
@@ -56,17 +56,17 @@ XPIDL_SOURCES += [
     'nsIWebPageDescriptor.idl',
 ]
 
 XPIDL_MODULE = 'docshell'
 
 EXPORTS += [
     'nsCTooltipTextProvider.h',
     'nsDocShell.h',
-    'nsDocShellLoadState.h',
+    'nsDocShellLoadInfo.h',
     'nsDocShellLoadTypes.h',
     'nsDocShellTreeOwner.h',
     'nsILinkHandler.h',
     'nsIScrollObserver.h',
     'SerializedLoadContext.h',
 ]
 
 EXPORTS.mozilla += [
@@ -83,17 +83,17 @@ UNIFIED_SOURCES += [
     'BrowsingContext.cpp',
     'ChromeBrowsingContext.cpp',
     'LoadContext.cpp',
     'nsAboutRedirector.cpp',
     'nsDefaultURIFixup.cpp',
     'nsDocShell.cpp',
     'nsDocShellEditorData.cpp',
     'nsDocShellEnumerator.cpp',
-    'nsDocShellLoadState.cpp',
+    'nsDocShellLoadInfo.cpp',
     'nsDocShellTreeOwner.cpp',
     'nsDSURIContentListener.cpp',
     'nsPingListener.cpp',
     'nsRefreshTimer.cpp',
     'nsWebNavigationInfo.cpp',
     'SerializedLoadContext.cpp',
 ]
 
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -164,17 +164,17 @@
 #include "nsContentDLF.h"
 #include "nsContentPolicyUtils.h" // NS_CheckContentLoadPolicy(...)
 #include "nsContentSecurityManager.h"
 #include "nsContentUtils.h"
 #include "nsCURILoader.h"
 #include "nsDocShellCID.h"
 #include "nsDocShellEditorData.h"
 #include "nsDocShellEnumerator.h"
-#include "nsDocShellLoadState.h"
+#include "nsDocShellLoadInfo.h"
 #include "nsDocShellLoadTypes.h"
 #include "nsDOMCID.h"
 #include "nsDOMNavigationTiming.h"
 #include "nsDSURIContentListener.h"
 #include "nsError.h"
 #include "nsEscape.h"
 #include "nsFocusManager.h"
 #include "nsGlobalWindow.h"
@@ -638,247 +638,385 @@ nsDocShell::GetInterface(const nsIID& aI
     return nsDocLoader::GetInterface(aIID, aSink);
   }
 
   NS_IF_ADDREF(((nsISupports*)*aSink));
   return *aSink ? NS_OK : NS_NOINTERFACE;
 }
 
 NS_IMETHODIMP
-nsDocShell::LoadURI(nsDocShellLoadState* aLoadState)
-{
-  MOZ_ASSERT(aLoadState, "Must have a valid load state!");
-  MOZ_ASSERT((aLoadState->LoadFlags() & INTERNAL_LOAD_FLAGS_LOADURI_SETUP_FLAGS) == 0,
-             "Should not have these flags set");
-  MOZ_ASSERT(aLoadState->URI(), "Should have a valid URI to load");
+nsDocShell::LoadURI(nsIURI* aURI,
+                    nsDocShellLoadInfo* aLoadInfo,
+                    uint32_t aLoadFlags,
+                    bool aFirstParty)
+{
+  MOZ_ASSERT(aLoadInfo || (aLoadFlags & EXTRA_LOAD_FLAGS) == 0,
+             "Unexpected flags");
+  MOZ_ASSERT((aLoadFlags & 0xf) == 0, "Should not have these flags set");
 
   // Note: we allow loads to get through here even if mFiredUnloadEvent is
   // true; that case will get handled in LoadInternal or LoadHistoryEntry,
   // so we pass false as the second parameter to IsNavigationAllowed.
   // However, we don't allow the page to change location *in the middle of*
   // firing beforeunload, so we do need to check if *beforeunload* is currently
   // firing, so we call IsNavigationAllowed rather than just IsPrintingOrPP.
   if (!IsNavigationAllowed(true, false)) {
     return NS_OK; // JS may not handle returning of an error code
   }
 
+  nsCOMPtr<nsIURI> referrer;
+  nsCOMPtr<nsIURI> originalURI;
+  Maybe<nsCOMPtr<nsIURI>> resultPrincipalURI;
+  bool keepResultPrincipalURIIfSet = false;
+  bool loadReplace = false;
+  nsCOMPtr<nsIInputStream> postStream;
+  nsCOMPtr<nsIInputStream> headersStream;
+  nsCOMPtr<nsIPrincipal> triggeringPrincipal;
+  bool inheritPrincipal = false;
+  bool principalIsExplicit = false;
+  bool sendReferrer = true;
+  uint32_t referrerPolicy = RP_Unset;
+  bool isSrcdoc = false;
+  nsCOMPtr<nsISHEntry> shEntry;
+  nsString target;
+  nsAutoString srcdoc;
+  bool forceAllowDataURI = false;
+  bool originalFrameSrc = false;
+  nsCOMPtr<nsIDocShell> sourceDocShell;
+  nsCOMPtr<nsIURI> baseURI;
+
+  uint32_t loadType = MAKE_LOAD_TYPE(LOAD_NORMAL, aLoadFlags);
+
+  NS_ENSURE_ARG(aURI);
+
   if (!StartupTimeline::HasRecord(StartupTimeline::FIRST_LOAD_URI) &&
-      mItemType == typeContent && !NS_IsAboutBlank(aLoadState->URI())) {
+      mItemType == typeContent && !NS_IsAboutBlank(aURI)) {
     StartupTimeline::RecordOnce(StartupTimeline::FIRST_LOAD_URI);
   }
 
+  // Extract the info from the DocShellLoadInfo struct...
+  if (aLoadInfo) {
+    referrer = aLoadInfo->Referrer();
+    originalURI = aLoadInfo->OriginalURI();
+    aLoadInfo->GetMaybeResultPrincipalURI(resultPrincipalURI);
+    keepResultPrincipalURIIfSet = aLoadInfo->KeepResultPrincipalURIIfSet();
+    loadReplace = aLoadInfo->LoadReplace();
+    // Get the appropriate loadType from nsIDocShellLoadInfo type
+    loadType = aLoadInfo->LoadType();
+
+    triggeringPrincipal = aLoadInfo->TriggeringPrincipal();
+    inheritPrincipal = aLoadInfo->InheritPrincipal();
+    principalIsExplicit = aLoadInfo->PrincipalIsExplicit();
+    shEntry = aLoadInfo->SHEntry();
+    aLoadInfo->GetTarget(target);
+    postStream = aLoadInfo->PostDataStream();
+    headersStream = aLoadInfo->HeadersStream();
+    sendReferrer = aLoadInfo->SendReferrer();
+    referrerPolicy = aLoadInfo->ReferrerPolicy();
+    isSrcdoc = aLoadInfo->IsSrcdocLoad();
+    aLoadInfo->GetSrcdocData(srcdoc);
+    sourceDocShell = aLoadInfo->SourceDocShell();
+    baseURI = aLoadInfo->BaseURI();
+    forceAllowDataURI = aLoadInfo->ForceAllowDataURI();
+    originalFrameSrc = aLoadInfo->OriginalFrameSrc();
+  }
+
   MOZ_LOG(gDocShellLeakLog, LogLevel::Debug,
           ("nsDocShell[%p]: loading %s with flags 0x%08x",
-           this, aLoadState->URI()->GetSpecOrDefault().get(),
-           aLoadState->LoadFlags()));
-
-  if (!aLoadState->SHEntry() &&
-      !LOAD_TYPE_HAS_FLAGS(aLoadState->LoadType(),
-                           LOAD_FLAGS_REPLACE_HISTORY)) {
-    // This is possibly a subframe, so handle it accordingly.
-    //
-    // If history exists, it will be loaded into the aLoadState object, and the
-    // LoadType will be changed.
-    MaybeHandleSubframeHistory(aLoadState);
-  }
-
-  if (aLoadState->SHEntry()) {
-#ifdef DEBUG
-    MOZ_LOG(gDocShellLog, LogLevel::Debug,
-           ("nsDocShell[%p]: loading from session history", this));
-#endif
-
-    return LoadHistoryEntry(aLoadState->SHEntry(), aLoadState->LoadType());
-  }
-
-  // On history navigation via Back/Forward buttons, don't execute
-  // automatic JavaScript redirection such as |location.href = ...| or
-  // |window.open()|
-  //
-  // LOAD_NORMAL:        window.open(...) etc.
-  // LOAD_STOP_CONTENT:  location.href = ..., location.assign(...)
-  if ((aLoadState->LoadType() == LOAD_NORMAL ||
-       aLoadState->LoadType() == LOAD_STOP_CONTENT) &&
-      ShouldBlockLoadingForBackButton()) {
-    return NS_OK;
-  }
-
-  // Set up the inheriting principal in LoadState.
-  nsresult rv = aLoadState->SetupInheritingPrincipal(mItemType, mOriginAttributes);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = aLoadState->SetupTriggeringPrincipal(mOriginAttributes);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  aLoadState->CalculateDocShellInternalLoadFlags();
-
-  mozilla::Maybe<nsCOMPtr<nsIURI>> resultPrincipalURI;
-  aLoadState->GetMaybeResultPrincipalURI(resultPrincipalURI);
-
-  MOZ_ASSERT(aLoadState->TypeHint().IsVoid(),
-             "Typehint should be null when calling InternalLoad from LoadURI");
-  MOZ_ASSERT(aLoadState->FileName().IsVoid(),
-             "FileName should be null when calling InternalLoad from LoadURI");
-  MOZ_ASSERT(aLoadState->SHEntry() == nullptr,
-             "SHEntry should be null when calling InternalLoad from LoadURI");
-
-  return InternalLoad(aLoadState->URI(),
-                      aLoadState->OriginalURI(),
-                      resultPrincipalURI,
-                      aLoadState->KeepResultPrincipalURIIfSet(),
-                      aLoadState->LoadReplace(),
-                      aLoadState->Referrer(),
-                      aLoadState->ReferrerPolicy(),
-                      aLoadState->TriggeringPrincipal(),
-                      aLoadState->PrincipalToInherit(),
-                      aLoadState->DocShellInternalLoadFlags(),
-                      aLoadState->Target(),
-                      aLoadState->TypeHint(),
-                      aLoadState->FileName(),
-                      aLoadState->PostDataStream(),
-                      aLoadState->HeadersStream(),
-                      aLoadState->LoadType(),
-                      aLoadState->SHEntry(),
-                      aLoadState->FirstParty(),
-                      aLoadState->SrcdocData(),
-                      aLoadState->SourceDocShell(),
-                      aLoadState->BaseURI(),
-                      nullptr, // no nsIDocShell
-                      nullptr); // no nsIRequest
-}
-
-void
-nsDocShell::MaybeHandleSubframeHistory(nsDocShellLoadState* aLoadState)
-{
-  // First, verify if this is a subframe.
+           this, aURI->GetSpecOrDefault().get(), aLoadFlags));
+
+  if (!shEntry &&
+      !LOAD_TYPE_HAS_FLAGS(loadType, LOAD_FLAGS_REPLACE_HISTORY)) {
+    // First verify if this is a subframe.
     nsCOMPtr<nsIDocShellTreeItem> parentAsItem;
     GetSameTypeParent(getter_AddRefs(parentAsItem));
     nsCOMPtr<nsIDocShell> parentDS(do_QueryInterface(parentAsItem));
-
-  if (!parentDS || parentDS == static_cast<nsIDocShell*>(this)) {
-    // This is the root docshell. If we got here while
-    // executing an onLoad Handler,this load will not go
-    // into session history.
-    bool inOnLoadHandler = false;
-    GetIsExecutingOnLoadHandler(&inOnLoadHandler);
-    if (inOnLoadHandler) {
-      aLoadState->SetLoadType(LOAD_NORMAL_REPLACE);
-    }
-    return;
-  }
-
-  /* OK. It is a subframe. Checkout the parent's loadtype. If the parent was
-   * loaded through a history mechanism, then get the SH entry for the child from
-   * the parent. This is done to restore frameset navigation while going
-   * back/forward. If the parent was loaded through any other loadType, set the
-   * child's loadType too accordingly, so that session history does not get
-   * confused.
+    uint32_t parentLoadType;
+
+    if (parentDS && parentDS != static_cast<nsIDocShell*>(this)) {
+      /* OK. It is a subframe. Checkout the
+       * parent's loadtype. If the parent was loaded thro' a history
+       * mechanism, then get the SH entry for the child from the parent.
+       * This is done to restore frameset navigation while going back/forward.
+       * If the parent was loaded through any other loadType, set the
+       * child's loadType too accordingly, so that session history does not
+       * get confused.
        */
 
       // Get the parent's load type
-  uint32_t parentLoadType;
       parentDS->GetLoadType(&parentLoadType);
 
       // Get the ShEntry for the child from the parent
       nsCOMPtr<nsISHEntry> currentSH;
       bool oshe = false;
       parentDS->GetCurrentSHEntry(getter_AddRefs(currentSH), &oshe);
       bool dynamicallyAddedChild = mDynamicallyCreated;
-
       if (!dynamicallyAddedChild && !oshe && currentSH) {
         currentSH->HasDynamicallyAddedChild(&dynamicallyAddedChild);
       }
-
       if (!dynamicallyAddedChild) {
         // Only use the old SHEntry, if we're sure enough that
         // it wasn't originally for some other frame.
-    nsCOMPtr<nsISHEntry> shEntry;
         parentDS->GetChildSHEntry(mChildOffset, getter_AddRefs(shEntry));
-    aLoadState->SetSHEntry(shEntry);
       }
 
       // Make some decisions on the child frame's loadType based on the
       // parent's loadType, if the subframe hasn't loaded anything into it.
       //
       // In some cases privileged scripts may try to get the DOMWindow
       // reference of this docshell before the loading starts, causing the
       // initial about:blank content viewer being created and mCurrentURI being
       // set. To handle this case we check if mCurrentURI is about:blank and
       // currentSHEntry is null.
       nsCOMPtr<nsISHEntry> currentChildEntry;
       GetCurrentSHEntry(getter_AddRefs(currentChildEntry), &oshe);
-
-  if (mCurrentURI && (!NS_IsAboutBlank(mCurrentURI) || currentChildEntry)) {
+      if (!mCurrentURI || (NS_IsAboutBlank(mCurrentURI) && !currentChildEntry)) {
+        // This is a newly created frame. Check for exception cases first.
+        // By default the subframe will inherit the parent's loadType.
+        if (shEntry && (parentLoadType == LOAD_NORMAL ||
+                        parentLoadType == LOAD_LINK   ||
+                        parentLoadType == LOAD_NORMAL_EXTERNAL)) {
+          // The parent was loaded normally. In this case, this *brand new*
+          // child really shouldn't have a SHEntry. If it does, it could be
+          // because the parent is replacing an existing frame with a new frame,
+          // in the onLoadHandler. We don't want this url to get into session
+          // history. Clear off shEntry, and set load type to
+          // LOAD_BYPASS_HISTORY.
+          bool inOnLoadHandler = false;
+          parentDS->GetIsExecutingOnLoadHandler(&inOnLoadHandler);
+          if (inOnLoadHandler) {
+            loadType = LOAD_NORMAL_REPLACE;
+            shEntry = nullptr;
+          }
+        } else if (parentLoadType == LOAD_REFRESH) {
+          // Clear shEntry. For refresh loads, we have to load
+          // what comes thro' the pipe, not what's in history.
+          shEntry = nullptr;
+        } else if ((parentLoadType == LOAD_BYPASS_HISTORY) ||
+                   (shEntry &&
+                    ((parentLoadType & LOAD_CMD_HISTORY) ||
+                     (parentLoadType == LOAD_RELOAD_NORMAL) ||
+                     (parentLoadType == LOAD_RELOAD_CHARSET_CHANGE) ||
+                     (parentLoadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE) ||
+                     (parentLoadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE)))) {
+          // If the parent url, bypassed history or was loaded from
+          // history, pass on the parent's loadType to the new child
+          // frame too, so that the child frame will also
+          // avoid getting into history.
+          loadType = parentLoadType;
+        } else if (parentLoadType == LOAD_ERROR_PAGE) {
+          // If the parent document is an error page, we don't
+          // want to update global/session history. However,
+          // this child frame is not an error page.
+          loadType = LOAD_BYPASS_HISTORY;
+        } else if ((parentLoadType == LOAD_RELOAD_BYPASS_CACHE) ||
+                   (parentLoadType == LOAD_RELOAD_BYPASS_PROXY) ||
+                   (parentLoadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE)) {
+          // the new frame should inherit the parent's load type so that it also
+          // bypasses the cache and/or proxy
+          loadType = parentLoadType;
+        }
+      } else {
         // This is a pre-existing subframe. If
         // 1. The load of this frame was not originally initiated by session
         //    history directly (i.e. (!shEntry) condition succeeded, but it can
         //    still be a history load on parent which causes this frame being
-    //    loaded), which we checked with the above assert, and
+        //    loaded), and
         // 2. mCurrentURI is not null, nor the initial about:blank,
         // it is possible that a parent's onLoadHandler or even self's
         // onLoadHandler is loading a new page in this child. Check parent's and
         // self's busy flag and if it is set, we don't want this onLoadHandler
         // load to get in to session history.
         uint32_t parentBusy = BUSY_FLAGS_NONE;
         uint32_t selfBusy = BUSY_FLAGS_NONE;
         parentDS->GetBusyFlags(&parentBusy);
         GetBusyFlags(&selfBusy);
         if (parentBusy & BUSY_FLAGS_BUSY ||
             selfBusy & BUSY_FLAGS_BUSY) {
-      aLoadState->SetLoadType(LOAD_NORMAL_REPLACE);
-      aLoadState->SetSHEntry(nullptr);
-    }
-    return;
-  }
-
-  // This is a newly created frame. Check for exception cases first.
-  // By default the subframe will inherit the parent's loadType.
-  if (aLoadState->SHEntry() && (parentLoadType == LOAD_NORMAL ||
-                                parentLoadType == LOAD_LINK   ||
-                                parentLoadType == LOAD_NORMAL_EXTERNAL)) {
-    // The parent was loaded normally. In this case, this *brand new*
-    // child really shouldn't have a SHEntry. If it does, it could be
-    // because the parent is replacing an existing frame with a new frame,
-    // in the onLoadHandler. We don't want this url to get into session
-    // history. Clear off shEntry, and set load type to
-    // LOAD_BYPASS_HISTORY.
+          loadType = LOAD_NORMAL_REPLACE;
+          shEntry = nullptr;
+        }
+      }
+    } // parentDS
+    else {
+      // This is the root docshell. If we got here while
+      // executing an onLoad Handler,this load will not go
+      // into session history.
       bool inOnLoadHandler = false;
-    parentDS->GetIsExecutingOnLoadHandler(&inOnLoadHandler);
+      GetIsExecutingOnLoadHandler(&inOnLoadHandler);
       if (inOnLoadHandler) {
-      aLoadState->SetLoadType(LOAD_NORMAL_REPLACE);
-      aLoadState->SetSHEntry(nullptr);
-    }
-  } else if (parentLoadType == LOAD_REFRESH) {
-    // Clear shEntry. For refresh loads, we have to load
-    // what comes through the pipe, not what's in history.
-    aLoadState->SetSHEntry(nullptr);
-  } else if ((parentLoadType == LOAD_BYPASS_HISTORY) ||
-             (aLoadState->SHEntry() &&
-              ((parentLoadType & LOAD_CMD_HISTORY) ||
-               (parentLoadType == LOAD_RELOAD_NORMAL) ||
-               (parentLoadType == LOAD_RELOAD_CHARSET_CHANGE) ||
-               (parentLoadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE) ||
-               (parentLoadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE)))) {
-    // If the parent url, bypassed history or was loaded from
-    // history, pass on the parent's loadType to the new child
-    // frame too, so that the child frame will also
-    // avoid getting into history.
-    aLoadState->SetLoadType(parentLoadType);
-  } else if (parentLoadType == LOAD_ERROR_PAGE) {
-    // If the parent document is an error page, we don't
-    // want to update global/session history. However,
-    // this child frame is not an error page.
-    aLoadState->SetLoadType(LOAD_BYPASS_HISTORY);
-  } else if ((parentLoadType == LOAD_RELOAD_BYPASS_CACHE) ||
-             (parentLoadType == LOAD_RELOAD_BYPASS_PROXY) ||
-             (parentLoadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE)) {
-    // the new frame should inherit the parent's load type so that it also
-    // bypasses the cache and/or proxy
-    aLoadState->SetLoadType(parentLoadType);
-  }
+        loadType = LOAD_NORMAL_REPLACE;
+      }
+    }
+  } // !shEntry
+
+  if (shEntry) {
+#ifdef DEBUG
+    MOZ_LOG(gDocShellLog, LogLevel::Debug,
+           ("nsDocShell[%p]: loading from session history", this));
+#endif
+
+    return LoadHistoryEntry(shEntry, loadType);
+  }
+
+  // On history navigation via Back/Forward buttons, don't execute
+  // automatic JavaScript redirection such as |location.href = ...| or
+  // |window.open()|
+  //
+  // LOAD_NORMAL:        window.open(...) etc.
+  // LOAD_STOP_CONTENT:  location.href = ..., location.assign(...)
+  if ((loadType == LOAD_NORMAL || loadType == LOAD_STOP_CONTENT) &&
+      ShouldBlockLoadingForBackButton()) {
+    return NS_OK;
+  }
+
+  // Perform the load...
+
+  // We need a principalToInherit.
+  //
+  // If principalIsExplicit is not set there are 4 possibilities:
+  // (1) If the system principal or an expanded principal was passed
+  //     in and we're a typeContent docshell, inherit the principal
+  //     from the current document instead.
+  // (2) In all other cases when the principal passed in is not null,
+  //     use that principal.
+  // (3) If the caller has allowed inheriting from the current document,
+  //     or if we're being called from system code (eg chrome JS or pure
+  //     C++) then inheritPrincipal should be true and InternalLoad will get
+  //     a principal from the current document. If none of these things are
+  //     true, then
+  // (4) we don't pass a principal into the channel, and a principal will be
+  //     created later from the channel's internal data.
+  //
+  // If principalIsExplicit *is* set, there are 4 possibilities
+  // (1) If the system principal or an expanded principal was passed in
+  //     and we're a typeContent docshell, return an error.
+  // (2) In all other cases when the principal passed in is not null,
+  //     use that principal.
+  // (3) If the caller has allowed inheriting from the current document,
+  //     then inheritPrincipal should be true and InternalLoad will get
+  //     a principal from the current document. If none of these things are
+  //     true, then
+  // (4) we dont' pass a principal into the channel, and a principal will be
+  //     created later from the channel's internal data.
+  nsCOMPtr<nsIPrincipal> principalToInherit = triggeringPrincipal;
+  if (principalToInherit && mItemType != typeChrome) {
+    if (nsContentUtils::IsSystemPrincipal(principalToInherit)) {
+      if (principalIsExplicit) {
+        return NS_ERROR_DOM_SECURITY_ERR;
+      }
+      principalToInherit = nullptr;
+      inheritPrincipal = true;
+    } else if (nsContentUtils::IsExpandedPrincipal(principalToInherit)) {
+      if (principalIsExplicit) {
+        return NS_ERROR_DOM_SECURITY_ERR;
+      }
+      // Don't inherit from the current page.  Just do the safe thing
+      // and pretend that we were loaded by a nullprincipal.
+      //
+      // We didn't inherit OriginAttributes here as ExpandedPrincipal doesn't
+      // have origin attributes.
+      principalToInherit = NullPrincipal::CreateWithInheritedAttributes(this);
+      inheritPrincipal = false;
+    }
+  }
+  if (!principalToInherit && !inheritPrincipal && !principalIsExplicit) {
+    // See if there's system or chrome JS code running
+    inheritPrincipal = nsContentUtils::LegacyIsCallerChromeOrNativeCode();
+  }
+
+  if (aLoadFlags & LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL) {
+    inheritPrincipal = false;
+    // If aFirstParty is true and the pref 'privacy.firstparty.isolate' is
+    // enabled, we will set firstPartyDomain on the origin attributes.
+    principalToInherit = NullPrincipal::CreateWithInheritedAttributes(this, aFirstParty);
+  }
+
+  // If the triggeringPrincipal is not passed explicitly, we first try to create
+  // a principal from the referrer, since the referrer URI reflects the web origin
+  // that triggered the load. If there is no referrer URI, we fall back to using
+  // the SystemPrincipal. It's safe to assume that no provided triggeringPrincipal
+  // and no referrer simulate a load that was triggered by the system.
+  // It's important to note that this block of code needs to appear *after* the block
+  // where we munge the principalToInherit, because otherwise we would never enter
+  // code blocks checking if the principalToInherit is null and we will end up with
+  // a wrong inheritPrincipal flag.
+  if (!triggeringPrincipal) {
+    if (referrer) {
+      nsresult rv = CreatePrincipalFromReferrer(referrer,
+                                                getter_AddRefs(triggeringPrincipal));
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+    else {
+      triggeringPrincipal = nsContentUtils::GetSystemPrincipal();
+    }
+  }
+
+  uint32_t flags = 0;
+
+  if (inheritPrincipal) {
+    MOZ_ASSERT(!nsContentUtils::IsSystemPrincipal(principalToInherit), "Should not inherit SystemPrincipal");
+    flags |= INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL;
+  }
+
+  if (!sendReferrer) {
+    flags |= INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER;
+  }
+
+  if (aLoadFlags & LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) {
+    flags |= INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+  }
+
+  if (aLoadFlags & LOAD_FLAGS_FIRST_LOAD) {
+    flags |= INTERNAL_LOAD_FLAGS_FIRST_LOAD;
+  }
+
+  if (aLoadFlags & LOAD_FLAGS_BYPASS_CLASSIFIER) {
+    flags |= INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER;
+  }
+
+  if (aLoadFlags & LOAD_FLAGS_FORCE_ALLOW_COOKIES) {
+    flags |= INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES;
+  }
+
+  if (isSrcdoc) {
+    flags |= INTERNAL_LOAD_FLAGS_IS_SRCDOC;
+  }
+
+  if (forceAllowDataURI) {
+    flags |= INTERNAL_LOAD_FLAGS_FORCE_ALLOW_DATA_URI;
+  }
+
+  if (originalFrameSrc) {
+    flags |= INTERNAL_LOAD_FLAGS_ORIGINAL_FRAME_SRC;
+  }
+
+  return InternalLoad(aURI,
+                      originalURI,
+                      resultPrincipalURI,
+                      keepResultPrincipalURIIfSet,
+                      loadReplace,
+                      referrer,
+                      referrerPolicy,
+                      triggeringPrincipal,
+                      principalToInherit,
+                      flags,
+                      target,
+                      nullptr,      // No type hint
+                      VoidString(), // No forced download
+                      postStream,
+                      headersStream,
+                      loadType,
+                      nullptr, // No SHEntry
+                      aFirstParty,
+                      srcdoc,
+                      sourceDocShell,
+                      baseURI,
+                      nullptr,  // No nsIDocShell
+                      nullptr); // No nsIRequest
 }
 
 /*
  * Reset state to a new content model within the current document and the
  * document viewer. Called by the document before initiating an out of band
  * document.write().
  */
 NS_IMETHODIMP
@@ -4083,17 +4221,17 @@ nsDocShell::LoadURIWithOptions(const nsA
                                uint32_t aLoadFlags,
                                nsIURI* aReferringURI,
                                uint32_t aReferrerPolicy,
                                nsIInputStream* aPostStream,
                                nsIInputStream* aHeaderStream,
                                nsIURI* aBaseURI,
                                nsIPrincipal* aTriggeringPrincipal)
 {
-  NS_ASSERTION((aLoadFlags & INTERNAL_LOAD_FLAGS_LOADURI_SETUP_FLAGS) == 0, "Unexpected flags");
+  NS_ASSERTION((aLoadFlags & 0xf) == 0, "Unexpected flags");
 
   if (!IsNavigationAllowed()) {
     return NS_OK; // JS may not handle returning of an error code
   }
   nsCOMPtr<nsIURI> uri;
   nsCOMPtr<nsIInputStream> postStream(aPostStream);
   nsresult rv = NS_OK;
 
@@ -4178,47 +4316,46 @@ nsDocShell::LoadURIWithOptions(const nsA
     aLoadFlags & LOAD_FLAGS_FORCE_ALLOW_DATA_URI;
 
   // Don't pass certain flags that aren't needed and end up confusing
   // ConvertLoadTypeToDocShellInfoLoadType.  We do need to ensure that they are
   // passed to LoadURI though, since it uses them.
   uint32_t extraFlags = (aLoadFlags & EXTRA_LOAD_FLAGS);
   aLoadFlags &= ~EXTRA_LOAD_FLAGS;
 
-  RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState();
+  RefPtr<nsDocShellLoadInfo> loadInfo = new nsDocShellLoadInfo();
 
   /*
    * If the user "Disables Protection on This Page", we have to make sure to
    * remember the users decision when opening links in child tabs [Bug 906190]
    */
+  uint32_t loadType;
   if (aLoadFlags & LOAD_FLAGS_ALLOW_MIXED_CONTENT) {
-    loadState->SetLoadType(MAKE_LOAD_TYPE(LOAD_NORMAL_ALLOW_MIXED_CONTENT, aLoadFlags));
+    loadType = MAKE_LOAD_TYPE(LOAD_NORMAL_ALLOW_MIXED_CONTENT, aLoadFlags);
   } else {
-    loadState->SetLoadType(MAKE_LOAD_TYPE(LOAD_NORMAL, aLoadFlags));
-  }
-
-  loadState->SetURI(uri);
-  loadState->SetLoadFlags(extraFlags);
-  loadState->SetFirstParty(true);
-  loadState->SetPostDataStream(postStream);
-  loadState->SetReferrer(aReferringURI);
-  loadState->SetReferrerPolicy((mozilla::net::ReferrerPolicy)aReferrerPolicy);
-  loadState->SetHeadersStream(aHeaderStream);
-  loadState->SetBaseURI(aBaseURI);
-  loadState->SetTriggeringPrincipal(aTriggeringPrincipal);
-  loadState->SetForceAllowDataURI(forceAllowDataURI);
+    loadType = MAKE_LOAD_TYPE(LOAD_NORMAL, aLoadFlags);
+  }
+
+  loadInfo->SetLoadType(loadType);
+  loadInfo->SetPostDataStream(postStream);
+  loadInfo->SetReferrer(aReferringURI);
+  loadInfo->SetReferrerPolicy((mozilla::net::ReferrerPolicy)aReferrerPolicy);
+  loadInfo->SetHeadersStream(aHeaderStream);
+  loadInfo->SetBaseURI(aBaseURI);
+  loadInfo->SetTriggeringPrincipal(aTriggeringPrincipal);
+  loadInfo->SetForceAllowDataURI(forceAllowDataURI);
 
   if (fixupInfo) {
     nsAutoString searchProvider, keyword;
     fixupInfo->GetKeywordProviderName(searchProvider);
     fixupInfo->GetKeywordAsSent(keyword);
     MaybeNotifyKeywordSearchLoading(searchProvider, keyword);
   }
 
-  rv = LoadURI(loadState);
+  rv = LoadURI(uri, loadInfo, extraFlags, true);
 
   // Save URI string in case it's needed later when
   // sending to search engine service in EndPageLoad()
   mOriginalUriString = uriString;
 
   return rv;
 }
 
@@ -4776,29 +4913,29 @@ nsDocShell::LoadErrorPage(nsIURI* aError
     // we go back or forward to another SHEntry with the same doc
     // identifier, the error page won't persist.
     mLSHE->AbandonBFCacheEntry();
   }
 
   return InternalLoad(aErrorURI, nullptr, Nothing(), false, false, nullptr, RP_Unset,
                       nsContentUtils::GetSystemPrincipal(), nullptr,
                       INTERNAL_LOAD_FLAGS_NONE, EmptyString(),
-                      VoidCString(), VoidString(), nullptr, nullptr,
+                      nullptr, VoidString(), nullptr, nullptr,
                       LOAD_ERROR_PAGE, nullptr, true, VoidString(), this,
                       nullptr, nullptr, nullptr);
 }
 
 NS_IMETHODIMP
 nsDocShell::Reload(uint32_t aReloadFlags)
 {
   if (!IsNavigationAllowed()) {
     return NS_OK; // JS may not handle returning of an error code
   }
   nsresult rv;
-  NS_ASSERTION(((aReloadFlags & INTERNAL_LOAD_FLAGS_LOADURI_SETUP_FLAGS) == 0),
+  NS_ASSERTION(((aReloadFlags & 0xf) == 0),
                "Reload command not updated to use load flags!");
   NS_ASSERTION((aReloadFlags & EXTRA_LOAD_FLAGS) == 0,
                "Don't pass these flags to Reload");
 
   uint32_t loadType = MAKE_LOAD_TYPE(LOAD_RELOAD_NORMAL, aReloadFlags);
   NS_ENSURE_TRUE(IsValidLoadType(loadType), NS_ERROR_INVALID_ARG);
 
   // Send notifications to the HistoryListener if any, about the impending
@@ -4876,17 +5013,17 @@ nsDocShell::Reload(uint32_t aReloadFlags
                       false,
                       loadReplace,
                       referrerURI,
                       referrerPolicy,
                       triggeringPrincipal,
                       triggeringPrincipal,
                       flags,
                       EmptyString(),   // No window target
-                      NS_LossyConvertUTF16toASCII(contentTypeHint),
+                      NS_LossyConvertUTF16toASCII(contentTypeHint).get(),
                       VoidString(),    // No forced download
                       nullptr,         // No post data
                       nullptr,         // No headers data
                       loadType,        // Load type
                       nullptr,         // No SHEntry
                       true,
                       srcdoc,          // srcdoc argument for iframe
                       this,            // For reloads we are the source
@@ -6128,80 +6265,76 @@ nsDocShell::ForceRefreshURIFromTimer(nsI
   return ForceRefreshURI(aURI, aPrincipal, aDelay, aMetaRefresh);
 }
 
 NS_IMETHODIMP
 nsDocShell::ForceRefreshURI(nsIURI* aURI, nsIPrincipal* aPrincipal, int32_t aDelay, bool aMetaRefresh)
 {
   NS_ENSURE_ARG(aURI);
 
-  RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState();
+  RefPtr<nsDocShellLoadInfo> loadInfo = new nsDocShellLoadInfo();
 
   /* We do need to pass in a referrer, but we don't want it to
    * be sent to the server.
    */
-  loadState->SetSendReferrer(false);
+  loadInfo->SetSendReferrer(false);
 
   /* for most refreshes the current URI is an appropriate
    * internal referrer
    */
-  loadState->SetReferrer(mCurrentURI);
-
-  loadState->SetOriginalURI(mCurrentURI);
-  loadState->SetResultPrincipalURI(aURI);
-  loadState->SetResultPrincipalURIIsSome(true);
-  loadState->SetKeepResultPrincipalURIIfSet(true);
+  loadInfo->SetReferrer(mCurrentURI);
+
+  loadInfo->SetOriginalURI(mCurrentURI);
+  loadInfo->SetResultPrincipalURI(aURI);
+  loadInfo->SetResultPrincipalURIIsSome(true);
+  loadInfo->SetKeepResultPrincipalURIIfSet(true);
 
   // Set the triggering pricipal to aPrincipal if available, or current
   // document's principal otherwise.
   nsCOMPtr<nsIPrincipal> principal = aPrincipal;
   if (!principal) {
     nsCOMPtr<nsIDocument> doc = GetDocument();
     if (!doc) {
       return NS_ERROR_FAILURE;
     }
     principal = doc->NodePrincipal();
   }
-  loadState->SetTriggeringPrincipal(principal);
-  loadState->SetPrincipalIsExplicit(true);
+  loadInfo->SetTriggeringPrincipal(principal);
+  loadInfo->SetPrincipalIsExplicit(true);
 
   /* Check if this META refresh causes a redirection
    * to another site.
    */
   bool equalUri = false;
   nsresult rv = aURI->Equals(mCurrentURI, &equalUri);
   if (NS_SUCCEEDED(rv) && (!equalUri) && aMetaRefresh &&
       aDelay <= REFRESH_REDIRECT_TIMER) {
     /* It is a META refresh based redirection within the threshold time
      * we have in mind (15000 ms as defined by REFRESH_REDIRECT_TIMER).
      * Pass a REPLACE flag to LoadURI().
      */
-    loadState->SetLoadType(LOAD_NORMAL_REPLACE);
+    loadInfo->SetLoadType(LOAD_NORMAL_REPLACE);
 
     /* for redirects we mimic HTTP, which passes the
      *  original referrer
      */
     nsCOMPtr<nsIURI> internalReferrer;
     GetReferringURI(getter_AddRefs(internalReferrer));
     if (internalReferrer) {
-      loadState->SetReferrer(internalReferrer);
+      loadInfo->SetReferrer(internalReferrer);
     }
   } else {
-    loadState->SetLoadType(LOAD_REFRESH);
-  }
-
-  loadState->SetURI(aURI);
-  loadState->SetLoadFlags(nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL);
-  loadState->SetFirstParty(true);
+    loadInfo->SetLoadType(LOAD_REFRESH);
+  }
 
   /*
    * LoadURI(...) will cancel all refresh timers... This causes the
    * Timer and its refreshData instance to be released...
    */
-  LoadURI(loadState);
+  LoadURI(aURI, loadInfo, nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL, true);
 
   return NS_OK;
 }
 
 nsresult
 nsDocShell::SetupRefreshURIFromHeader(nsIURI* aBaseURI,
                                       nsIPrincipal* aPrincipal,
                                       const nsACString& aHeader)
@@ -8957,27 +9090,26 @@ public:
                     nsIURI* aOriginalURI,
                     Maybe<nsCOMPtr<nsIURI>> const& aResultPrincipalURI,
                     bool aKeepResultPrincipalURIIfSet,
                     bool aLoadReplace,
                     nsIURI* aReferrer, uint32_t aReferrerPolicy,
                     nsIPrincipal* aTriggeringPrincipal,
                     nsIPrincipal* aPrincipalToInherit,
                     uint32_t aFlags,
-                    const nsACString& aTypeHint,
+                    const char* aTypeHint,
                     nsIInputStream* aPostData,
                     nsIInputStream* aHeadersData,
                     uint32_t aLoadType,
                     nsISHEntry* aSHEntry,
                     bool aFirstParty,
                     const nsAString& aSrcdoc,
                     nsIDocShell* aSourceDocShell,
                     nsIURI* aBaseURI)
     : mozilla::Runnable("InternalLoadEvent")
-    , mTypeHint(aTypeHint)
     , mSrcdoc(aSrcdoc)
     , mDocShell(aDocShell)
     , mURI(aURI)
     , mOriginalURI(aOriginalURI)
     , mResultPrincipalURI(aResultPrincipalURI)
     , mKeepResultPrincipalURIIfSet(aKeepResultPrincipalURIIfSet)
     , mLoadReplace(aLoadReplace)
     , mReferrer(aReferrer)
@@ -8988,29 +9120,36 @@ public:
     , mHeadersData(aHeadersData)
     , mSHEntry(aSHEntry)
     , mFlags(aFlags)
     , mLoadType(aLoadType)
     , mFirstParty(aFirstParty)
     , mSourceDocShell(aSourceDocShell)
     , mBaseURI(aBaseURI)
   {
+    // Make sure to keep null things null as needed
+    if (aTypeHint) {
+      mTypeHint = aTypeHint;
+    } else {
+      mTypeHint.SetIsVoid(true);
+    }
   }
 
   NS_IMETHOD
   Run() override
   {
     return mDocShell->InternalLoad(mURI, mOriginalURI, mResultPrincipalURI,
                                    mKeepResultPrincipalURIIfSet,
                                    mLoadReplace,
                                    mReferrer,
                                    mReferrerPolicy,
                                    mTriggeringPrincipal, mPrincipalToInherit,
                                    mFlags, EmptyString(),
-                                   mTypeHint,
+                                   mTypeHint.IsVoid() ? nullptr
+                                                      : mTypeHint.get(),
                                    VoidString(), mPostData,
                                    mHeadersData, mLoadType, mSHEntry,
                                    mFirstParty, mSrcdoc, mSourceDocShell,
                                    mBaseURI, nullptr,
                                    nullptr);
   }
 
 private:
@@ -9071,17 +9210,17 @@ nsDocShell::InternalLoad(nsIURI* aURI,
                          bool aKeepResultPrincipalURIIfSet,
                          bool aLoadReplace,
                          nsIURI* aReferrer,
                          uint32_t aReferrerPolicy,
                          nsIPrincipal* aTriggeringPrincipal,
                          nsIPrincipal* aPrincipalToInherit,
                          uint32_t aFlags,
                          const nsAString& aWindowTarget,
-                         const nsACString& aTypeHint,
+                         const char* aTypeHint,
                          const nsAString& aFileName,
                          nsIInputStream* aPostData,
                          nsIInputStream* aHeadersData,
                          uint32_t aLoadType,
                          nsISHEntry* aSHEntry,
                          bool aFirstParty,
                          const nsAString& aSrcdoc,
                          nsIDocShell* aSourceDocShell,
@@ -9352,41 +9491,41 @@ nsDocShell::InternalLoad(nsIURI* aURI,
         // If OnLinkClickSync was invoked inside the onload handler, the load
         // type would be set to LOAD_NORMAL_REPLACE; otherwise it should be
         // LOAD_LINK.
         MOZ_ASSERT(aLoadType == LOAD_LINK ||
                    aLoadType == LOAD_NORMAL_REPLACE);
         MOZ_ASSERT(!aSHEntry);
         MOZ_ASSERT(aFirstParty); // Windowwatcher will assume this.
 
-        RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState();
+        RefPtr<nsDocShellLoadInfo> loadInfo = new nsDocShellLoadInfo();
 
         // Set up our loadinfo so it will do the load as much like we would have
         // as possible.
-        loadState->SetReferrer(aReferrer);
-        loadState->SetReferrerPolicy((mozilla::net::ReferrerPolicy)aReferrerPolicy);
-        loadState->SetSendReferrer(!(aFlags &
+        loadInfo->SetReferrer(aReferrer);
+        loadInfo->SetReferrerPolicy((mozilla::net::ReferrerPolicy)aReferrerPolicy);
+        loadInfo->SetSendReferrer(!(aFlags &
                                     INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER));
-        loadState->SetOriginalURI(aOriginalURI);
-        loadState->SetMaybeResultPrincipalURI(aResultPrincipalURI);
-        loadState->SetKeepResultPrincipalURIIfSet(aKeepResultPrincipalURIIfSet);
-        loadState->SetLoadReplace(aLoadReplace);
-        loadState->SetTriggeringPrincipal(aTriggeringPrincipal);
-        loadState->SetInheritPrincipal(
+        loadInfo->SetOriginalURI(aOriginalURI);
+        loadInfo->SetMaybeResultPrincipalURI(aResultPrincipalURI);
+        loadInfo->SetKeepResultPrincipalURIIfSet(aKeepResultPrincipalURIIfSet);
+        loadInfo->SetLoadReplace(aLoadReplace);
+        loadInfo->SetTriggeringPrincipal(aTriggeringPrincipal);
+        loadInfo->SetInheritPrincipal(
           aFlags & INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL);
         // Explicit principal because we do not want any guesses as to what the
         // principal to inherit is: it should be aTriggeringPrincipal.
-        loadState->SetPrincipalIsExplicit(true);
-        loadState->SetLoadType(LOAD_LINK);
-        loadState->SetForceAllowDataURI(aFlags & INTERNAL_LOAD_FLAGS_FORCE_ALLOW_DATA_URI);
+        loadInfo->SetPrincipalIsExplicit(true);
+        loadInfo->SetLoadType(LOAD_LINK);
+        loadInfo->SetForceAllowDataURI(aFlags & INTERNAL_LOAD_FLAGS_FORCE_ALLOW_DATA_URI);
 
         rv = win->Open(NS_ConvertUTF8toUTF16(spec),
                        aWindowTarget, // window name
                        EmptyString(), // Features
-                       loadState,
+                       loadInfo,
                        true, // aForceNoOpener
                        getter_AddRefs(newWin));
         MOZ_ASSERT(!newWin);
         return rv;
       }
 
       rv = win->OpenNoNavigate(NS_ConvertUTF8toUTF16(spec),
                                aWindowTarget,  // window name
@@ -10187,17 +10326,17 @@ nsDocShell::DoURILoad(nsIURI* aURI,
                       bool aLoadFromExternal,
                       bool aForceAllowDataURI,
                       bool aOriginalFrameSrc,
                       nsIURI* aReferrerURI,
                       bool aSendReferrer,
                       uint32_t aReferrerPolicy,
                       nsIPrincipal* aTriggeringPrincipal,
                       nsIPrincipal* aPrincipalToInherit,
-                      const nsACString& aTypeHint,
+                      const char* aTypeHint,
                       const nsAString& aFileName,
                       nsIInputStream* aPostData,
                       nsIInputStream* aHeadersData,
                       bool aFirstParty,
                       nsIDocShell** aDocShell,
                       nsIRequest** aRequest,
                       bool aIsNewWindowTarget,
                       bool aBypassClassifier,
@@ -10536,18 +10675,18 @@ nsDocShell::DoURILoad(nsIURI* aURI,
   loadInfo->GetResultPrincipalURI(getter_AddRefs(rpURI));
   if (aResultPrincipalURI &&
       (!aKeepResultPrincipalURIIfSet || !rpURI)) {
     // Unconditionally override, we want the replay to be equal to what has
     // been captured.
     loadInfo->SetResultPrincipalURI(aResultPrincipalURI.ref());
   }
 
-  if (!aTypeHint.IsVoid()) {
-    channel->SetContentType(aTypeHint);
+  if (aTypeHint && *aTypeHint) {
+    channel->SetContentType(nsDependentCString(aTypeHint));
     mContentTypeHint = aTypeHint;
   } else {
     mContentTypeHint.Truncate();
   }
 
   if (!aFileName.IsVoid()) {
     rv = channel->SetContentDisposition(nsIChannel::DISPOSITION_ATTACHMENT);
     NS_ENSURE_SUCCESS(rv, rv);
@@ -12091,17 +12230,17 @@ nsDocShell::LoadHistoryEntry(nsISHEntry*
                     false,
                     loadReplace,
                     referrerURI,
                     referrerPolicy,
                     triggeringPrincipal,
                     principalToInherit,
                     flags,
                     EmptyString(),      // No window target
-                    contentType,        // Type hint
+                    contentType.get(),  // Type hint
                     VoidString(),       // No forced file download
                     postData,           // Post data stream
                     nullptr,            // No headers stream
                     aLoadType,          // Load type
                     aEntry,             // SHEntry
                     true,
                     srcdoc,
                     nullptr,            // Source docshell, see comment above
@@ -13279,17 +13418,17 @@ nsDocShell::OnLinkClickSync(nsIContent* 
                              false,
                              false,                     // LoadReplace
                              referer,                   // Referer URI
                              refererPolicy,             // Referer policy
                              triggeringPrincipal,
                              aContent->NodePrincipal(),
                              flags,
                              target,                    // Window target
-                             NS_LossyConvertUTF16toASCII(typeHint),
+                             NS_LossyConvertUTF16toASCII(typeHint).get(),
                              aFileName,                 // Download as file
                              aPostDataStream,           // Post data stream
                              aHeadersDataStream,        // Headers stream
                              loadType,                  // Load type
                              nullptr,                   // No SHEntry
                              true,                      // first party site
                              VoidString(),              // No srcdoc
                              this,                      // We are the source
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -529,17 +529,17 @@ private: // member functions
                      bool aLoadFromExternal,
                      bool aForceAllowDataURI,
                      bool aOriginalFrameSrc,
                      nsIURI* aReferrer,
                      bool aSendReferrer,
                      uint32_t aReferrerPolicy,
                      nsIPrincipal* aTriggeringPrincipal,
                      nsIPrincipal* aPrincipalToInherit,
-                     const nsACString& aTypeHint,
+                     const char* aTypeHint,
                      const nsAString& aFileName,
                      nsIInputStream* aPostData,
                      nsIInputStream* aHeadersData,
                      bool aFirstParty,
                      nsIDocShell** aDocShell,
                      nsIRequest** aRequest,
                      bool aIsNewWindowTarget,
                      bool aBypassClassifier,
@@ -900,24 +900,16 @@ private: // member functions
     return (mObserveErrorPages ? sUseErrorPages : mUseErrorPages);
   }
 
   bool CSSErrorReportingEnabled() const
   {
     return mCSSErrorReportingEnabled;
   }
 
-  // Handles retrieval of subframe session history for nsDocShell::LoadURI. If a
-  // load is requested in a subframe of the current DocShell, the subframe
-  // loadType may need to reflect the loadType of the parent document, or in
-  // some cases (like reloads), the history load may need to be cancelled. See
-  // function comments for in-depth logic descriptions.
-  void
-  MaybeHandleSubframeHistory(nsDocShellLoadState* aLoadState);
-
 private: // data members
   static nsIURIFixup* sURIFixup;
 
   // Cached value of the "browser.xul.error_pages.enabled" preference.
   static bool sUseErrorPages;
 
 #ifdef DEBUG
   // We're counting the number of |nsDocShells| to help find leaks
rename from docshell/base/nsDocShellLoadState.cpp
rename to docshell/base/nsDocShellLoadInfo.cpp
--- a/docshell/base/nsDocShellLoadState.cpp
+++ b/docshell/base/nsDocShellLoadInfo.cpp
@@ -1,554 +1,318 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "nsDocShellLoadState.h"
+#include "nsDocShellLoadInfo.h"
+#include "nsISHEntry.h"
+#include "nsIInputStream.h"
+#include "nsIURI.h"
 #include "nsIDocShell.h"
-#include "nsIDocShellTreeItem.h"
-#include "nsIScriptSecurityManager.h"
-#include "nsIWebNavigation.h"
+#include "mozilla/net/ReferrerPolicy.h"
+#include "mozilla/Unused.h"
+#include "mozilla/Maybe.h"
 
-#include "mozilla/OriginAttributes.h"
-#include "mozilla/NullPrincipal.h"
+namespace mozilla {
+
 
-nsDocShellLoadState::nsDocShellLoadState()
+} // mozilla
+
+nsDocShellLoadInfo::nsDocShellLoadInfo()
   : mResultPrincipalURIIsSome(false)
   , mKeepResultPrincipalURIIfSet(false)
   , mLoadReplace(false)
   , mInheritPrincipal(false)
   , mPrincipalIsExplicit(false)
   , mForceAllowDataURI(false)
   , mOriginalFrameSrc(false)
   , mSendReferrer(true)
   , mReferrerPolicy(mozilla::net::RP_Unset)
   , mLoadType(LOAD_NORMAL)
   , mIsSrcdocLoad(false)
-  , mLoadFlags(0)
-  , mFirstParty(false)
-  , mTypeHint(VoidCString())
-  , mFileName(VoidString())
-  , mDocShellInternalLoadFlags(0)
 {
 }
 
-nsDocShellLoadState::~nsDocShellLoadState()
+nsDocShellLoadInfo::~nsDocShellLoadInfo()
 {
 }
 
 nsIURI*
-nsDocShellLoadState::Referrer() const
+nsDocShellLoadInfo::Referrer() const
 {
   return mReferrer;
 }
 
 void
-nsDocShellLoadState::SetReferrer(nsIURI* aReferrer)
+nsDocShellLoadInfo::SetReferrer(nsIURI* aReferrer)
 {
   mReferrer = aReferrer;
 }
 
 nsIURI*
-nsDocShellLoadState::URI() const
-{
-  return mURI;
-}
-
-void
-nsDocShellLoadState::SetURI(nsIURI* aURI)
-{
-  mURI = aURI;
-}
-
-nsIURI*
-nsDocShellLoadState::OriginalURI() const
+nsDocShellLoadInfo::OriginalURI() const
 {
   return mOriginalURI;
 }
 
 void
-nsDocShellLoadState::SetOriginalURI(nsIURI* aOriginalURI)
+nsDocShellLoadInfo::SetOriginalURI(nsIURI* aOriginalURI)
 {
   mOriginalURI = aOriginalURI;
 }
 
 nsIURI*
-nsDocShellLoadState::ResultPrincipalURI() const
+nsDocShellLoadInfo::ResultPrincipalURI() const
 {
   return mResultPrincipalURI;
 }
 
 void
-nsDocShellLoadState::SetResultPrincipalURI(nsIURI* aResultPrincipalURI)
+nsDocShellLoadInfo::SetResultPrincipalURI(nsIURI* aResultPrincipalURI)
 {
   mResultPrincipalURI = aResultPrincipalURI;
 }
 
 bool
-nsDocShellLoadState::ResultPrincipalURIIsSome() const
+nsDocShellLoadInfo::ResultPrincipalURIIsSome() const
 {
   return mResultPrincipalURIIsSome;
 }
 
 void
-nsDocShellLoadState::SetResultPrincipalURIIsSome(bool aIsSome)
+nsDocShellLoadInfo::SetResultPrincipalURIIsSome(bool aIsSome)
 {
   mResultPrincipalURIIsSome = aIsSome;
 }
 
 bool
-nsDocShellLoadState::KeepResultPrincipalURIIfSet() const
+nsDocShellLoadInfo::KeepResultPrincipalURIIfSet() const
 {
   return mKeepResultPrincipalURIIfSet;
 }
 
 void
-nsDocShellLoadState::SetKeepResultPrincipalURIIfSet(bool aKeep)
+nsDocShellLoadInfo::SetKeepResultPrincipalURIIfSet(bool aKeep)
 {
   mKeepResultPrincipalURIIfSet = aKeep;
 }
 
 bool
-nsDocShellLoadState::LoadReplace() const
+nsDocShellLoadInfo::LoadReplace() const
 {
   return mLoadReplace;
 }
 
 void
-nsDocShellLoadState::SetLoadReplace(bool aLoadReplace)
+nsDocShellLoadInfo::SetLoadReplace(bool aLoadReplace)
 {
   mLoadReplace = aLoadReplace;
 }
 
 nsIPrincipal*
-nsDocShellLoadState::TriggeringPrincipal() const
+nsDocShellLoadInfo::TriggeringPrincipal() const
 {
   return mTriggeringPrincipal;
 }
 
 void
-nsDocShellLoadState::SetTriggeringPrincipal(nsIPrincipal* aTriggeringPrincipal)
+nsDocShellLoadInfo::SetTriggeringPrincipal(nsIPrincipal* aTriggeringPrincipal)
 {
   mTriggeringPrincipal = aTriggeringPrincipal;
 }
 
-nsIPrincipal*
-nsDocShellLoadState::PrincipalToInherit() const
-{
-  return mPrincipalToInherit;
-}
-
-void
-nsDocShellLoadState::SetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit)
-{
-  mPrincipalToInherit = aPrincipalToInherit;
-}
-
 bool
-nsDocShellLoadState::InheritPrincipal() const
+nsDocShellLoadInfo::InheritPrincipal() const
 {
   return mInheritPrincipal;
 }
 
 void
-nsDocShellLoadState::SetInheritPrincipal(bool aInheritPrincipal)
+nsDocShellLoadInfo::SetInheritPrincipal(bool aInheritPrincipal)
 {
   mInheritPrincipal = aInheritPrincipal;
 }
 
 bool
-nsDocShellLoadState::PrincipalIsExplicit() const
+nsDocShellLoadInfo::PrincipalIsExplicit() const
 {
   return mPrincipalIsExplicit;
 }
 
 void
-nsDocShellLoadState::SetPrincipalIsExplicit(bool aPrincipalIsExplicit)
+nsDocShellLoadInfo::SetPrincipalIsExplicit(bool aPrincipalIsExplicit)
 {
   mPrincipalIsExplicit = aPrincipalIsExplicit;
 }
 
 bool
-nsDocShellLoadState::ForceAllowDataURI() const
+nsDocShellLoadInfo::ForceAllowDataURI() const
 {
   return mForceAllowDataURI;
 }
 
 void
-nsDocShellLoadState::SetForceAllowDataURI(bool aForceAllowDataURI)
+nsDocShellLoadInfo::SetForceAllowDataURI(bool aForceAllowDataURI)
 {
   mForceAllowDataURI = aForceAllowDataURI;
 }
 
 bool
-nsDocShellLoadState::OriginalFrameSrc() const
+nsDocShellLoadInfo::OriginalFrameSrc() const
 {
   return mOriginalFrameSrc;
 }
 
 void
-nsDocShellLoadState::SetOriginalFrameSrc(bool aOriginalFrameSrc)
+nsDocShellLoadInfo::SetOriginalFrameSrc(bool aOriginalFrameSrc)
 {
   mOriginalFrameSrc = aOriginalFrameSrc;
 }
 
 uint32_t
-nsDocShellLoadState::LoadType() const
+nsDocShellLoadInfo::LoadType() const
 {
   return mLoadType;
 }
 
 void
-nsDocShellLoadState::SetLoadType(uint32_t aLoadType)
+nsDocShellLoadInfo::SetLoadType(uint32_t aLoadType)
 {
   mLoadType = aLoadType;
 }
 
 nsISHEntry*
-nsDocShellLoadState::SHEntry() const
+nsDocShellLoadInfo::SHEntry() const
 {
   return mSHEntry;
 }
 
 void
-nsDocShellLoadState::SetSHEntry(nsISHEntry* aSHEntry)
+nsDocShellLoadInfo::SetSHEntry(nsISHEntry* aSHEntry)
 {
   mSHEntry = aSHEntry;
 }
 
-const nsString&
-nsDocShellLoadState::Target() const
+void
+nsDocShellLoadInfo::GetTarget(nsAString& aTarget) const
 {
-  return mTarget;
+  aTarget = mTarget;
 }
 
 void
-nsDocShellLoadState::SetTarget(const nsAString& aTarget)
+nsDocShellLoadInfo::SetTarget(const nsAString& aTarget)
 {
   mTarget = aTarget;
 }
 
 nsIInputStream*
-nsDocShellLoadState::PostDataStream() const
+nsDocShellLoadInfo::PostDataStream() const
 {
   return mPostDataStream;
 }
 
 void
-nsDocShellLoadState::SetPostDataStream(nsIInputStream* aStream)
+nsDocShellLoadInfo::SetPostDataStream(nsIInputStream* aStream)
 {
   mPostDataStream = aStream;
 }
 
 nsIInputStream*
-nsDocShellLoadState::HeadersStream() const
+nsDocShellLoadInfo::HeadersStream() const
 {
   return mHeadersStream;
 }
 
 void
-nsDocShellLoadState::SetHeadersStream(nsIInputStream* aHeadersStream)
+nsDocShellLoadInfo::SetHeadersStream(nsIInputStream* aHeadersStream)
 {
   mHeadersStream = aHeadersStream;
 }
 
 bool
-nsDocShellLoadState::SendReferrer() const
+nsDocShellLoadInfo::SendReferrer() const
 {
   return mSendReferrer;
 }
 
 void
-nsDocShellLoadState::SetSendReferrer(bool aSendReferrer)
+nsDocShellLoadInfo::SetSendReferrer(bool aSendReferrer)
 {
   mSendReferrer = aSendReferrer;
 }
 
 uint32_t
-nsDocShellLoadState::ReferrerPolicy() const
+nsDocShellLoadInfo::ReferrerPolicy() const
 {
   return mReferrerPolicy;
 }
 
 void
-nsDocShellLoadState::SetReferrerPolicy(mozilla::net::ReferrerPolicy aReferrerPolicy)
+nsDocShellLoadInfo::SetReferrerPolicy(mozilla::net::ReferrerPolicy aReferrerPolicy)
 {
   mReferrerPolicy = aReferrerPolicy;
 }
 
 bool
-nsDocShellLoadState::IsSrcdocLoad() const
+nsDocShellLoadInfo::IsSrcdocLoad() const
 {
   return mIsSrcdocLoad;
 }
 
-const nsString&
-nsDocShellLoadState::SrcdocData() const
+void
+nsDocShellLoadInfo::GetSrcdocData(nsAString& aSrcdocData) const
 {
-  return mSrcdocData;
+  aSrcdocData = mSrcdocData;
 }
 
 void
-nsDocShellLoadState::SetSrcdocData(const nsAString& aSrcdocData)
+nsDocShellLoadInfo::SetSrcdocData(const nsAString& aSrcdocData)
 {
   mSrcdocData = aSrcdocData;
   mIsSrcdocLoad = true;
 }
 
 nsIDocShell*
-nsDocShellLoadState::SourceDocShell() const
+nsDocShellLoadInfo::SourceDocShell() const
 {
   return mSourceDocShell;
 }
 
 void
-nsDocShellLoadState::SetSourceDocShell(nsIDocShell* aSourceDocShell)
+nsDocShellLoadInfo::SetSourceDocShell(nsIDocShell* aSourceDocShell)
 {
   mSourceDocShell = aSourceDocShell;
 }
 
 nsIURI*
-nsDocShellLoadState::BaseURI() const
+nsDocShellLoadInfo::BaseURI() const
 {
   return mBaseURI;
 }
 
 void
-nsDocShellLoadState::SetBaseURI(nsIURI* aBaseURI)
+nsDocShellLoadInfo::SetBaseURI(nsIURI* aBaseURI)
 {
   mBaseURI = aBaseURI;
 }
 
 void
-nsDocShellLoadState::GetMaybeResultPrincipalURI(mozilla::Maybe<nsCOMPtr<nsIURI>>& aRPURI) const
+nsDocShellLoadInfo::GetMaybeResultPrincipalURI(mozilla::Maybe<nsCOMPtr<nsIURI>>& aRPURI) const
 {
   bool isSome = ResultPrincipalURIIsSome();
   aRPURI.reset();
 
   if (!isSome) {
     return;
   }
 
   nsCOMPtr<nsIURI> uri = ResultPrincipalURI();
   aRPURI.emplace(std::move(uri));
 }
 
 void
-nsDocShellLoadState::SetMaybeResultPrincipalURI(mozilla::Maybe<nsCOMPtr<nsIURI>> const& aRPURI)
+nsDocShellLoadInfo::SetMaybeResultPrincipalURI(mozilla::Maybe<nsCOMPtr<nsIURI>> const& aRPURI)
 {
   SetResultPrincipalURI(aRPURI.refOr(nullptr));
   SetResultPrincipalURIIsSome(aRPURI.isSome());
 }
-
-uint32_t
-nsDocShellLoadState::LoadFlags() const
-{
-  return mLoadFlags;
-}
-
-void
-nsDocShellLoadState::SetLoadFlags(uint32_t aLoadFlags)
-{
-  mLoadFlags = aLoadFlags;
-}
-
-bool
-nsDocShellLoadState::FirstParty() const
-{
-  return mFirstParty;
-}
-
-void
-nsDocShellLoadState::SetFirstParty(bool aFirstParty)
-{
-  mFirstParty = aFirstParty;
-}
-
-const nsCString&
-nsDocShellLoadState::TypeHint() const
-{
-  return mTypeHint;
-}
-
-void
-nsDocShellLoadState::SetTypeHint(const nsCString& aTypeHint)
-{
-  mTypeHint = aTypeHint;
-}
-
-const nsString&
-nsDocShellLoadState::FileName() const
-{
-  return mFileName;
-}
-
-void
-nsDocShellLoadState::SetFileName(const nsAString& aFileName)
-{
-  mFileName = aFileName;
-}
-
-uint32_t
-nsDocShellLoadState::DocShellInternalLoadFlags() const
-{
-  return mDocShellInternalLoadFlags;
-}
-
-void
-nsDocShellLoadState::SetDocShellInternalLoadFlags(uint32_t aFlags)
-{
-  mDocShellInternalLoadFlags = aFlags;
-}
-
-
-nsresult
-nsDocShellLoadState::SetupInheritingPrincipal(uint32_t aItemType,
-                                              const mozilla::OriginAttributes& aOriginAttributes)
-{
-  // We need a principalToInherit.
-  //
-  // If principalIsExplicit is not set there are 4 possibilities:
-  // (1) If the system principal or an expanded principal was passed
-  //     in and we're a typeContent docshell, inherit the principal
-  //     from the current document instead.
-  // (2) In all other cases when the principal passed in is not null,
-  //     use that principal.
-  // (3) If the caller has allowed inheriting from the current document,
-  //     or if we're being called from system code (eg chrome JS or pure
-  //     C++) then inheritPrincipal should be true and InternalLoad will get
-  //     a principal from the current document. If none of these things are
-  //     true, then
-  // (4) we don't pass a principal into the channel, and a principal will be
-  //     created later from the channel's internal data.
-  //
-  // If principalIsExplicit *is* set, there are 4 possibilities
-  // (1) If the system principal or an expanded principal was passed in
-  //     and we're a typeContent docshell, return an error.
-  // (2) In all other cases when the principal passed in is not null,
-  //     use that principal.
-  // (3) If the caller has allowed inheriting from the current document,
-  //     then inheritPrincipal should be true and InternalLoad will get
-  //     a principal from the current document. If none of these things are
-  //     true, then
-  // (4) we dont' pass a principal into the channel, and a principal will be
-  //     created later from the channel's internal data.
-  mPrincipalToInherit = mTriggeringPrincipal;
-  if (mPrincipalToInherit && aItemType != nsIDocShellTreeItem::typeChrome) {
-    if (nsContentUtils::IsSystemPrincipal(mPrincipalToInherit)) {
-      if (mPrincipalIsExplicit) {
-        return NS_ERROR_DOM_SECURITY_ERR;
-      }
-      mPrincipalToInherit = nullptr;
-      mInheritPrincipal = true;
-    } else if (nsContentUtils::IsExpandedPrincipal(mPrincipalToInherit)) {
-      if (mPrincipalIsExplicit) {
-        return NS_ERROR_DOM_SECURITY_ERR;
-      }
-      // Don't inherit from the current page.  Just do the safe thing
-      // and pretend that we were loaded by a nullprincipal.
-      //
-      // We didn't inherit OriginAttributes here as ExpandedPrincipal doesn't
-      // have origin attributes.
-      mPrincipalToInherit =
-        NullPrincipal::CreateWithInheritedAttributes(aOriginAttributes,
-                                                     false);
-      mInheritPrincipal = false;
-    }
-  }
-
-  if (!mPrincipalToInherit && !mInheritPrincipal && !mPrincipalIsExplicit) {
-    // See if there's system or chrome JS code running
-    mInheritPrincipal = nsContentUtils::LegacyIsCallerChromeOrNativeCode();
-  }
-
-  if (mLoadFlags & nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL) {
-    mInheritPrincipal = false;
-    // If mFirstParty is true and the pref 'privacy.firstparty.isolate' is
-    // enabled, we will set firstPartyDomain on the origin attributes.
-    mPrincipalToInherit =
-      NullPrincipal::CreateWithInheritedAttributes(aOriginAttributes,
-                                                   mFirstParty);
-  }
-
-  return NS_OK;
-}
-
-nsresult
-nsDocShellLoadState::SetupTriggeringPrincipal(const mozilla::OriginAttributes& aOriginAttributes)
-{
-  // If the triggeringPrincipal is not set, we first try to create a principal
-  // from the referrer, since the referrer URI reflects the web origin that
-  // triggered the load. If there is no referrer URI, we fall back to using the
-  // SystemPrincipal. It's safe to assume that no provided triggeringPrincipal
-  // and no referrer simulate a load that was triggered by the system. It's
-  // important to note that this block of code needs to appear *after* the block
-  // where we munge the principalToInherit, because otherwise we would never
-  // enter code blocks checking if the principalToInherit is null and we will
-  // end up with a wrong inheritPrincipal flag.
-  if (!mTriggeringPrincipal) {
-    if (mReferrer) {
-      mTriggeringPrincipal =
-        BasePrincipal::CreateCodebasePrincipal(mReferrer, aOriginAttributes);
-
-      if (!mTriggeringPrincipal) {
-        return NS_ERROR_FAILURE;
-      }
-    } else {
-      mTriggeringPrincipal = nsContentUtils::GetSystemPrincipal();
-    }
-  }
-  return NS_OK;
-}
-
-void
-nsDocShellLoadState::CalculateDocShellInternalLoadFlags()
-{
-  MOZ_ASSERT(mDocShellInternalLoadFlags == 0,
-             "Shouldn't have any load flags set at this point.");
-
-  if (mInheritPrincipal) {
-    MOZ_ASSERT(!nsContentUtils::IsSystemPrincipal(mPrincipalToInherit),
-               "Should not inherit SystemPrincipal");
-    mDocShellInternalLoadFlags |=
-      nsIDocShell::INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL;
-  }
-
-  if (!mSendReferrer) {
-    mDocShellInternalLoadFlags |=
-      nsIDocShell::INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER;
-  }
-
-  if (mDocShellInternalLoadFlags & nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) {
-    mDocShellInternalLoadFlags |=
-      nsIDocShell::INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
-  }
-
-  if (mDocShellInternalLoadFlags & nsIWebNavigation::LOAD_FLAGS_FIRST_LOAD) {
-    mDocShellInternalLoadFlags |= nsIDocShell::INTERNAL_LOAD_FLAGS_FIRST_LOAD;
-  }
-
-  if (mDocShellInternalLoadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CLASSIFIER) {
-    mDocShellInternalLoadFlags |=
-      nsIDocShell::INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER;
-  }
-
-  if (mDocShellInternalLoadFlags & nsIWebNavigation::LOAD_FLAGS_FORCE_ALLOW_COOKIES) {
-    mDocShellInternalLoadFlags |=
-      nsIDocShell::INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES;
-  }
-
-  if (mIsSrcdocLoad) {
-    mDocShellInternalLoadFlags |= nsIDocShell::INTERNAL_LOAD_FLAGS_IS_SRCDOC;
-  }
-
-  if (mForceAllowDataURI) {
-    mDocShellInternalLoadFlags |=
-      nsIDocShell::INTERNAL_LOAD_FLAGS_FORCE_ALLOW_DATA_URI;
-  }
-
-  if (mOriginalFrameSrc) {
-    mDocShellInternalLoadFlags |=
-      nsIDocShell::INTERNAL_LOAD_FLAGS_ORIGINAL_FRAME_SRC;
-  }
-}
rename from docshell/base/nsDocShellLoadState.h
rename to docshell/base/nsDocShellLoadInfo.h
--- a/docshell/base/nsDocShellLoadState.h
+++ b/docshell/base/nsDocShellLoadInfo.h
@@ -1,70 +1,59 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#ifndef nsDocShellLoadState_h__
-#define nsDocShellLoadState_h__
+#ifndef nsDocShellLoadInfo_h__
+#define nsDocShellLoadInfo_h__
 
 // Helper Classes
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "nsDocShellLoadTypes.h"
 #include "mozilla/net/ReferrerPolicy.h"
 
 class nsIInputStream;
 class nsISHEntry;
 class nsIURI;
 class nsIDocShell;
-class OriginAttibutes;
 
 /**
- * nsDocShellLoadState contains setup information used in a nsIDocShell::loadURI
+ * nsDocShellLoadInfo contains setup information used in a nsIDocShell::loadURI
  * call.
  */
-class nsDocShellLoadState final
+class nsDocShellLoadInfo
 {
 public:
-  NS_INLINE_DECL_REFCOUNTING(nsDocShellLoadState);
+  NS_INLINE_DECL_REFCOUNTING(nsDocShellLoadInfo);
 
-  nsDocShellLoadState();
-
-  // Getters and Setters
+  nsDocShellLoadInfo();
 
   nsIURI* Referrer() const;
 
   void SetReferrer(nsIURI* aReferrer);
 
-  nsIURI* URI() const;
-
-  void SetURI(nsIURI* aURI);
-
   nsIURI* OriginalURI() const;
 
   void SetOriginalURI(nsIURI* aOriginalURI);
 
   nsIURI* ResultPrincipalURI() const;
 
   void SetResultPrincipalURI(nsIURI* aResultPrincipalURI);
 
   bool ResultPrincipalURIIsSome() const;
 
   void SetResultPrincipalURIIsSome(bool aIsSome);
 
   bool KeepResultPrincipalURIIfSet() const;
 
   void SetKeepResultPrincipalURIIfSet(bool aKeep);
 
-  nsIPrincipal* PrincipalToInherit() const;
-
-  void SetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit);
-
   bool LoadReplace() const;
 
   void SetLoadReplace(bool aLoadReplace);
 
   nsIPrincipal* TriggeringPrincipal() const;
 
   void SetTriggeringPrincipal(nsIPrincipal* aTriggeringPrincipal);
 
@@ -87,17 +76,17 @@ public:
   uint32_t LoadType() const;
 
   void SetLoadType(uint32_t aLoadType);
 
   nsISHEntry* SHEntry() const;
 
   void SetSHEntry(nsISHEntry* aSHEntry);
 
-  const nsString& Target() const;
+  void GetTarget(nsAString& aTarget) const;
 
   void SetTarget(const nsAString& aTarget);
 
   nsIInputStream* PostDataStream() const;
 
   void SetPostDataStream(nsIInputStream* aStream);
 
   nsIInputStream* HeadersStream() const;
@@ -109,17 +98,17 @@ public:
   void SetSendReferrer(bool aSendReferrer);
 
   uint32_t ReferrerPolicy() const;
 
   void SetReferrerPolicy(mozilla::net::ReferrerPolicy aReferrerPolicy);
 
   bool IsSrcdocLoad() const;
 
-  const nsString& SrcdocData() const;
+  void GetSrcdocData(nsAString& aSrcdocData) const;
 
   void SetSrcdocData(const nsAString& aSrcdocData);
 
   nsIDocShell* SourceDocShell() const;
 
   void SetSourceDocShell(nsIDocShell* aSourceDocShell);
 
   nsIURI* BaseURI() const;
@@ -129,64 +118,23 @@ public:
   // Helper function allowing convenient work with mozilla::Maybe in C++, hiding
   // resultPrincipalURI and resultPrincipalURIIsSome attributes from the consumer.
   void
   GetMaybeResultPrincipalURI(mozilla::Maybe<nsCOMPtr<nsIURI>>& aRPURI) const;
 
   void
   SetMaybeResultPrincipalURI(mozilla::Maybe<nsCOMPtr<nsIURI>> const& aRPURI);
 
-  uint32_t LoadFlags() const;
-
-  void SetLoadFlags(uint32_t aFlags);
-
-  bool FirstParty() const;
-
-  void SetFirstParty(bool aFirstParty);
-
-  const nsCString& TypeHint() const;
-
-  void SetTypeHint(const nsCString& aTypeHint);
-
-  const nsString& FileName() const;
-
-  void SetFileName(const nsAString& aFileName);
-
-  uint32_t DocShellInternalLoadFlags() const;
-
-  void SetDocShellInternalLoadFlags(uint32_t aFlags);
-
-  // Give the type of DocShell we're loading into (chrome/content/etc) and
-  // origin attributes for the URI we're loading, figure out if we should
-  // inherit our principal from the document the load was requested from, or
-  // else if the principal should be set up later in the process (after loads).
-  // See comments in function for more info on principal selection algorithm
-  nsresult SetupInheritingPrincipal(uint32_t aItemType, const mozilla::OriginAttributes& aOriginAttributes);
-
-  // If no triggering principal exists at the moment, create one using referrer
-  // information and origin attributes.
-  nsresult SetupTriggeringPrincipal(const mozilla::OriginAttributes& aOriginAttributes);
-
-  // When loading a document through nsDocShell::LoadURI(), a special set of
-  // flags needs to be set based on other values in nsDocShellLoadState. This
-  // function calculates those flags, before the LoadState is passed to
-  // nsDocShell::InternalLoad.
-  void CalculateDocShellInternalLoadFlags();
 protected:
-  // Destructor can't be defaulted or inlined, as header doesn't have all type
-  // includes it needs to do so.
-  ~nsDocShellLoadState();
+  virtual ~nsDocShellLoadInfo();
 
 protected:
   // This is the referrer for the load.
   nsCOMPtr<nsIURI> mReferrer;
 
-  // The URI we are navigating to. Will not be null once set.
-  nsCOMPtr<nsIURI> mURI;
-
   // The originalURI to be passed to nsIDocShell.internalLoad. May be null.
   nsCOMPtr<nsIURI> mOriginalURI;
 
   // Result principal URL from nsILoadInfo, may be null. Valid only if
   // mResultPrincipalURIIsSome is true (has the same meaning as isSome() on
   // mozilla::Maybe.)
   nsCOMPtr<nsIURI> mResultPrincipalURI;
   bool mResultPrincipalURIIsSome;
@@ -209,22 +157,16 @@ protected:
 
   // If this attribute is true only ever use the principal specified
   // by the triggeringPrincipal and inheritPrincipal attributes.
   // If there are security reasons for why this is unsafe, such
   // as trying to use a systemprincipal as the triggeringPrincipal
   // for a content docshell the load fails.
   bool mPrincipalIsExplicit;
 
-  // Principal we're inheriting. If null, this means the principal should be
-  // inherited from the current document. If set to NullPrincipal, the channel
-  // will fill in principal information later in the load. See internal function
-  // comments for more info.
-  nsCOMPtr<nsIPrincipal> mPrincipalToInherit;
-
   // If this attribute is true, then a top-level navigation
   // to a data URI will be allowed.
   bool mForceAllowDataURI;
 
   // If this attribute is true, this load corresponds to a frame
   // element loading its original src (or srcdoc) attribute.
   bool mOriginalFrameSrc;
 
@@ -262,32 +204,11 @@ protected:
   nsString mSrcdocData;
 
   // When set, this is the Source Browsing Context for the navigation.
   nsCOMPtr<nsIDocShell> mSourceDocShell;
 
   // Used for srcdoc loads to give view-source knowledge of the load's base URI
   // as this information isn't embedded in the load's URI.
   nsCOMPtr<nsIURI> mBaseURI;
-
-  // Set of Load Flags, taken from nsDocShellLoadTypes.h
-  uint32_t mLoadFlags;
-
-  // Is this a First Party Load?
-  bool mFirstParty;
-
-  // A hint as to the content-type of the resulting data. If no hint, IsVoid()
-  // should return true.
-  nsCString mTypeHint;
-
-  // Non-void when the link should be downloaded as the given filename.
-  // mFileName being non-void but empty means that no filename hint was
-  // specified, but link should still trigger a download. If not a download,
-  // mFileName.IsVoid() should return true.
-  nsString mFileName;
-
-  // LoadFlags calculated in nsDocShell::LoadURI and passed to
-  // nsDocShell::InternalLoad, taken from the INTERNAL_LOAD consts in
-  // nsIDocShell.idl
-  uint32_t mDocShellInternalLoadFlags;
 };
 
-#endif /* nsDocShellLoadState_h__ */
+#endif /* nsDocShellLoadInfo_h__ */
--- a/docshell/base/nsDocShellLoadTypes.h
+++ b/docshell/base/nsDocShellLoadTypes.h
@@ -6,16 +6,17 @@
 
 #ifndef nsDocShellLoadTypes_h_
 #define nsDocShellLoadTypes_h_
 
 #ifdef MOZILLA_INTERNAL_API
 
 #include "nsDOMNavigationTiming.h"
 #include "nsIDocShell.h"
+#include "nsDocShellLoadInfo.h"
 #include "nsIWebNavigation.h"
 
 /**
  * Load flag for error pages. This uses one of the reserved flag
  * values from nsIWebNavigation.
  */
 #define LOAD_FLAGS_ERROR_PAGE 0x0001U
 
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -12,17 +12,17 @@
 #include "js/TypeDecls.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/NotNull.h"
 #include "mozilla/UniquePtr.h"
 #include "nsCOMPtr.h"
 #include "nsIURI.h"
 class nsPresContext;
 class nsIPresShell;
-class nsDocShellLoadState;
+class nsDocShellLoadInfo;
 namespace mozilla {
 class Encoding;
 class HTMLEditor;
 namespace dom {
 class BrowsingContext;
 class ClientSource;
 } // namespace dom
 }
@@ -58,50 +58,58 @@ interface nsIPrivacyTransitionObserver;
 interface nsIReflowObserver;
 interface nsIScrollObserver;
 interface nsITabParent;
 interface nsITabChild;
 interface nsICommandManager;
 interface nsICommandParams;
 interface nsILoadURIDelegate;
 native TabChildRef(already_AddRefed<nsITabChild>);
-native nsDocShellLoadStatePtr(nsDocShellLoadState*);
+native nsDocShellLoadInfoPtr(nsDocShellLoadInfo*);
 
 webidl BrowsingContext;
 webidl ContentFrameMessageManager;
 webidl EventTarget;
 
 [scriptable, builtinclass, uuid(049234fe-da10-478b-bc5d-bc6f9a1ba63d)]
 interface nsIDocShell : nsIDocShellTreeItem
 {
   /**
    * Loads a given URI.  This will give priority to loading the requested URI
    * in the object implementing this interface.  If it can't be loaded here
    * however, the URL dispatcher will go through its normal process of content
    * loading.
    *
-   * @param loadState   - This is the extended load info for this load.
+   * @param uri        - The URI to load.
+   * @param loadInfo   - This is the extended load info for this load.  This
+   *                     most often will be null, but if you need to do
+   *                     additional setup for this load you can get a loadInfo
+   *                     object by calling createLoadInfo.  Once you have this
+   *                     object you can set the needed properties on it and
+   *                     then pass it to loadURI.
+   * @param aLoadFlags - Flags to modify load behaviour. Flags are defined in
+   *                     nsIWebNavigation.  Note that using flags outside
+   *                     LOAD_FLAGS_MASK is only allowed if passing in a
+   *                     non-null loadInfo.  And even some of those might not
+   *                     be allowed.  Use at your own risk.
    */
-  [noscript]void loadURI(in nsDocShellLoadStatePtr loadState);
+  [noscript]void loadURI(in nsIURI uri,
+                         in nsDocShellLoadInfoPtr loadInfo,
+                         in unsigned long aLoadFlags,
+                         in boolean firstParty);
 
   const long INTERNAL_LOAD_FLAGS_NONE                    = 0x0;
   const long INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL       = 0x1;
   const long INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER      = 0x2;
   const long INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP = 0x4;
 
   // This flag marks the first load in this object
   // @see nsIWebNavigation::LOAD_FLAGS_FIRST_LOAD
   const long INTERNAL_LOAD_FLAGS_FIRST_LOAD              = 0x8;
 
-
-  // The set of flags that should not be set before calling into
-  // nsDocShell::LoadURI and other nsDocShell loading functions.
-  const long INTERNAL_LOAD_FLAGS_LOADURI_SETUP_FLAGS     = 0xf;
-
-
   const long INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER       = 0x10;
   const long INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES     = 0x20;
 
   // Whether the load should be treated as srcdoc load, rather than a URI one.
   const long INTERNAL_LOAD_FLAGS_IS_SRCDOC               = 0x40;
 
   // Whether this is the load of a frame's original src attribute
   const long INTERNAL_LOAD_FLAGS_ORIGINAL_FRAME_SRC      = 0x80;
@@ -185,17 +193,17 @@ interface nsIDocShell : nsIDocShellTreeI
                               in bool aKeepResultPrincipalURIIfSet,
                               in boolean aLoadReplace,
                               in nsIURI aReferrer,
                               in unsigned long aReferrerPolicy,
                               in nsIPrincipal aTriggeringPrincipal,
                               in nsIPrincipal aPrincipalToInherit,
                               in uint32_t aFlags,
                               in AString aWindowTarget,
-                              in ACString aTypeHint,
+                              in string aTypeHint,
                               in AString aFileName,
                               in nsIInputStream aPostDataStream,
                               in nsIInputStream aHeadersStream,
                               in unsigned long aLoadFlags,
                               in nsISHEntry aSHEntry,
                               in boolean aFirstParty,
                               in AString aSrcdoc,
                               in nsIDocShell aSourceDocShell,
--- a/docshell/shistory/nsSHEntry.cpp
+++ b/docshell/shistory/nsSHEntry.cpp
@@ -4,18 +4,18 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsSHEntry.h"
 
 #include <algorithm>
 
 #include "nsDocShellEditorData.h"
-#include "nsDocShellLoadTypes.h"
 #include "nsIContentViewer.h"
+#include "nsDocShellLoadInfo.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsIInputStream.h"
 #include "nsILayoutHistoryState.h"
 #include "nsIStructuredCloneContainer.h"
 #include "nsIURI.h"
 #include "nsSHEntryShared.h"
 #include "nsSHistory.h"
 
--- a/docshell/shistory/nsSHistory.cpp
+++ b/docshell/shistory/nsSHistory.cpp
@@ -8,17 +8,17 @@
 
 #include <algorithm>
 
 #include "nsCOMArray.h"
 #include "nsComponentManagerUtils.h"
 #include "nsDocShell.h"
 #include "nsIContentViewer.h"
 #include "nsIDocShell.h"
-#include "nsDocShellLoadState.h"
+#include "nsDocShellLoadInfo.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsILayoutHistoryState.h"
 #include "nsIObserverService.h"
 #include "nsISHEntry.h"
 #include "nsISHistoryListener.h"
 #include "nsIURI.h"
 #include "nsNetUtil.h"
 #include "nsTArray.h"
@@ -1570,39 +1570,37 @@ nsSHistory::LoadDifferingEntries(nsISHEn
 }
 
 nsresult
 nsSHistory::InitiateLoad(nsISHEntry* aFrameEntry, nsIDocShell* aFrameDS,
                          long aLoadType)
 {
   NS_ENSURE_STATE(aFrameDS && aFrameEntry);
 
-  RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState();
+  RefPtr<nsDocShellLoadInfo> loadInfo = new nsDocShellLoadInfo();
 
   /* Set the loadType in the SHEntry too to  what was passed on.
    * This will be passed on to child subframes later in nsDocShell,
    * so that proper loadType is maintained through out a frameset
    */
   aFrameEntry->SetLoadType(aLoadType);
 
-  loadState->SetLoadType(aLoadType);
-  loadState->SetSHEntry(aFrameEntry);
+  loadInfo->SetLoadType(aLoadType);
+  loadInfo->SetSHEntry(aFrameEntry);
 
   nsCOMPtr<nsIURI> originalURI = aFrameEntry->GetOriginalURI();
-  loadState->SetOriginalURI(originalURI);
+  loadInfo->SetOriginalURI(originalURI);
 
-  loadState->SetLoadReplace(aFrameEntry->GetLoadReplace());
+  loadInfo->SetLoadReplace(aFrameEntry->GetLoadReplace());
 
-  nsCOMPtr<nsIURI> newURI = aFrameEntry->GetURI();
-  loadState->SetURI(newURI);
-  loadState->SetLoadFlags(nsIWebNavigation::LOAD_FLAGS_NONE);
-  loadState->SetFirstParty(false);
+  nsCOMPtr<nsIURI> nextURI = aFrameEntry->GetURI();
+  // Time to initiate a document load
+  return aFrameDS->LoadURI(nextURI, loadInfo,
+                           nsIWebNavigation::LOAD_FLAGS_NONE, false);
 
-  // Time to initiate a document load
-  return aFrameDS->LoadURI(loadState);
 }
 
 NS_IMETHODIMP_(void)
 nsSHistory::SetRootDocShell(nsIDocShell* aDocShell)
 {
   mRootDocShell = aDocShell;
 
   // Init mHistoryTracker on setting mRootDocShell so we can bind its event
--- a/dom/animation/test/style/test_interpolation-from-interpolatematrix-to-none.html
+++ b/dom/animation/test/style/test_interpolation-from-interpolatematrix-to-none.html
@@ -32,12 +32,12 @@ test(function(t) {
                                     Math.sin(Math.PI / 8) + ',' +
                                    -Math.sin(Math.PI / 8) + ',' +
                                     Math.cos(Math.PI / 8) + ',' +
                                     '25, 0)';
   assert_matrix_equals(getComputedStyle(target).transform,
                        interpolated_matrix,
                        'the expected matrix from interpolatematrix(' +
                        'translateX(100px), rotate(90deg), 0.5) to none at 50%');
-}, 'Test interpolation from interpolatmatrix to none at 50%');
+}, 'Test interpolation from interpolatematrix to none at 50%');
 
 </script>
 </html>
--- a/dom/base/Location.cpp
+++ b/dom/base/Location.cpp
@@ -4,17 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "Location.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsIScriptContext.h"
 #include "nsIDocShell.h"
-#include "nsDocShellLoadState.h"
+#include "nsDocShellLoadInfo.h"
 #include "nsIWebNavigation.h"
 #include "nsCDefaultURIFixup.h"
 #include "nsIURIFixup.h"
 #include "nsIURL.h"
 #include "nsIURIMutator.h"
 #include "nsIJARURI.h"
 #include "nsNetUtil.h"
 #include "nsCOMPtr.h"
@@ -56,17 +56,17 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Location, mInnerWindow)
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(Location)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(Location)
 
-already_AddRefed<nsDocShellLoadState>
+already_AddRefed<nsDocShellLoadInfo>
 Location::CheckURL(nsIURI* aURI, nsIPrincipal& aSubjectPrincipal,
                    ErrorResult& aRv)
 {
   nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell));
   if (NS_WARN_IF(!docShell)) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
@@ -147,26 +147,26 @@ Location::CheckURL(nsIURI* aURI, nsIPrin
       }
     }
   } else {
     // No document; just use our subject principal as the triggering principal.
     triggeringPrincipal = &aSubjectPrincipal;
   }
 
   // Create load info
-  RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState();
+  RefPtr<nsDocShellLoadInfo> loadInfo = new nsDocShellLoadInfo();
 
-  loadState->SetTriggeringPrincipal(triggeringPrincipal);
+  loadInfo->SetTriggeringPrincipal(triggeringPrincipal);
 
   if (sourceURI) {
-    loadState->SetReferrer(sourceURI);
-    loadState->SetReferrerPolicy(referrerPolicy);
+    loadInfo->SetReferrer(sourceURI);
+    loadInfo->SetReferrerPolicy(referrerPolicy);
   }
 
-  return loadState.forget();
+  return loadInfo.forget();
 }
 
 nsresult
 Location::GetURI(nsIURI** aURI, bool aGetInnermostURI)
 {
   *aURI = nullptr;
 
   nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell));
@@ -207,41 +207,37 @@ Location::GetURI(nsIURI** aURI, bool aGe
 }
 
 void
 Location::SetURI(nsIURI* aURI, nsIPrincipal& aSubjectPrincipal,
                  ErrorResult& aRv, bool aReplace)
 {
   nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell));
   if (docShell) {
-
-    RefPtr<nsDocShellLoadState> loadState =
+    RefPtr<nsDocShellLoadInfo> loadInfo =
       CheckURL(aURI, aSubjectPrincipal, aRv);
     if (aRv.Failed()) {
       return;
     }
 
     if (aReplace) {
-      loadState->SetLoadType(LOAD_STOP_CONTENT_AND_REPLACE);
+      loadInfo->SetLoadType(LOAD_STOP_CONTENT_AND_REPLACE);
     } else {
-      loadState->SetLoadType(LOAD_STOP_CONTENT);
+      loadInfo->SetLoadType(LOAD_STOP_CONTENT);
     }
 
     // Get the incumbent script's browsing context to set as source.
     nsCOMPtr<nsPIDOMWindowInner> sourceWindow =
       do_QueryInterface(mozilla::dom::GetIncumbentGlobal());
     if (sourceWindow) {
-      loadState->SetSourceDocShell(sourceWindow->GetDocShell());
+      loadInfo->SetSourceDocShell(sourceWindow->GetDocShell());
     }
 
-    loadState->SetURI(aURI);
-    loadState->SetLoadFlags(nsIWebNavigation::LOAD_FLAGS_NONE);
-    loadState->SetFirstParty(true);
-
-    nsresult rv = docShell->LoadURI(loadState);
+    nsresult rv = docShell->LoadURI(aURI, loadInfo,
+                                    nsIWebNavigation::LOAD_FLAGS_NONE, true);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       aRv.Throw(rv);
     }
   }
 }
 
 void
 Location::GetHash(nsAString& aHash,
--- a/dom/base/Location.h
+++ b/dom/base/Location.h
@@ -12,17 +12,16 @@
 #include "nsCycleCollectionParticipant.h"
 #include "nsIWeakReferenceUtils.h"
 #include "nsPIDOMWindow.h"
 #include "nsString.h"
 #include "nsWrapperCache.h"
 
 class nsIDocShell;
 class nsIURI;
-class nsDocShellLoadState;
 
 namespace mozilla {
 namespace dom {
 
 //*****************************************************************************
 // Location: Script "location" object
 //*****************************************************************************
 
@@ -163,17 +162,16 @@ protected:
   virtual ~Location();
 
   // In the case of jar: uris, we sometimes want the place the jar was
   // fetched from as the URI instead of the jar: uri itself.  Pass in
   // true for aGetInnermostURI when that's the case.
   // Note, this method can return NS_OK with a null value for aURL. This happens
   // if the docShell is null.
   nsresult GetURI(nsIURI** aURL, bool aGetInnermostURI = false);
-
   void SetURI(nsIURI* aURL, nsIPrincipal& aSubjectPrincipal,
               ErrorResult& aRv, bool aReplace = false);
   void SetHrefWithBase(const nsAString& aHref, nsIURI* aBase,
                        nsIPrincipal& aSubjectPrincipal,
                        bool aReplace, ErrorResult& aRv);
 
   // Helper for Assign/SetHref/Replace
   void DoSetHref(const nsAString& aHref, nsIPrincipal& aSubjectPrincipal,
@@ -181,17 +179,17 @@ protected:
 
   // Get the base URL we should be using for our relative URL
   // resolution for SetHref/Assign/Replace.
   already_AddRefed<nsIURI> GetSourceBaseURL();
 
   // Check whether it's OK to load the given url with the given subject
   // principal, and if so construct the right nsDocShellLoadInfo for the load
   // and return it.
-  already_AddRefed<nsDocShellLoadState> CheckURL(nsIURI *url,
+  already_AddRefed<nsDocShellLoadInfo> CheckURL(nsIURI *url,
                                                 nsIPrincipal& aSubjectPrincipal,
                                                 ErrorResult& aRv);
 
   bool CallerSubsumes(nsIPrincipal* aSubjectPrincipal);
 
   nsString mCachedHash;
   nsCOMPtr<nsPIDOMWindowInner> mInnerWindow;
   nsWeakPtr mDocShell;
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -253,16 +253,17 @@
 #include "mozilla/ServoStyleSet.h"
 #include "mozilla/StyleSheet.h"
 #include "mozilla/StyleSheetInlines.h"
 #include "mozilla/dom/SVGDocument.h"
 #include "mozilla/dom/SVGSVGElement.h"
 #include "mozilla/dom/DocGroup.h"
 #include "mozilla/dom/TabGroup.h"
 #ifdef MOZ_XUL
+#include "mozilla/dom/XULBroadcastManager.h"
 #include "mozilla/dom/TreeBoxObject.h"
 #include "nsIXULWindow.h"
 #include "nsXULCommandDispatcher.h"
 #include "nsXULPopupManager.h"
 #include "nsIDocShellTreeOwner.h"
 #endif
 #include "nsIPresShellInlines.h"
 
@@ -1740,16 +1741,20 @@ nsDocument::~nsDocument()
     // Could be null here if Init() failed or if we have been unlinked.
     mCSSLoader->DropDocumentReference();
   }
 
   if (mStyleImageLoader) {
     mStyleImageLoader->DropDocumentReference();
   }
 
+  if (mXULBroadcastManager) {
+    mXULBroadcastManager->DropDocumentReference();
+  }
+
   delete mHeaderData;
 
   ClearAllBoxObjects();
 
   mPendingTitleChangeEvent.Revoke();
 
   mPlugins.Clear();
 }
@@ -5108,16 +5113,19 @@ nsDocument::EndUpdate()
 
   --mUpdateNestLevel;
 
   // This set of updates may have created XBL bindings.  Let the
   // binding manager know we're done.
   MaybeEndOutermostXBLUpdate();
 
   MaybeInitializeFinalizeFrameLoaders();
+  if (mXULBroadcastManager) {
+    mXULBroadcastManager->MaybeBroadcast();
+  }
 }
 
 void
 nsDocument::BeginLoad()
 {
   MOZ_ASSERT(!mDidCallBeginLoad);
   mDidCallBeginLoad = true;
 
@@ -10242,16 +10250,25 @@ nsIDocument::GetCommandDispatcher()
   }
   if (!mCommandDispatcher) {
     // Create our command dispatcher and hook it up.
     mCommandDispatcher = new nsXULCommandDispatcher(this);
   }
   return mCommandDispatcher;
 }
 
+void
+nsIDocument::InitializeXULBroadcastManager()
+{
+  if (mXULBroadcastManager) {
+    return;
+  }
+  mXULBroadcastManager = new XULBroadcastManager(this);
+}
+
 static JSObject*
 GetScopeObjectOfNode(nsINode* node)
 {
     MOZ_ASSERT(node, "Must not be called with null.");
 
     // Window root occasionally keeps alive a node of a document whose
     // window is already dead. If in this brief period someone calls
     // GetPopupNode and we return that node, we can end up creating a
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -20,17 +20,17 @@
 #include "nsIContentInlines.h"
 #include "nsIContentViewer.h"
 #include "nsIDocument.h"
 #include "nsPIDOMWindow.h"
 #include "nsIWebNavigation.h"
 #include "nsIWebProgress.h"
 #include "nsIDocShell.h"
 #include "nsIDocShellTreeOwner.h"
-#include "nsDocShellLoadState.h"
+#include "nsDocShellLoadInfo.h"
 #include "nsIBaseWindow.h"
 #include "nsIBrowser.h"
 #include "nsContentUtils.h"
 #include "nsIXPConnect.h"
 #include "nsUnicharUtils.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIScrollable.h"
@@ -385,97 +385,95 @@ nsFrameLoader::ReallyStartLoadingInterna
   }
   NS_ASSERTION(mDocShell,
                "MaybeCreateDocShell succeeded with a null mDocShell");
 
   // Just to be safe, recheck uri.
   rv = CheckURILoad(mURIToLoad, mTriggeringPrincipal);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState();
-
-  loadState->SetOriginalFrameSrc(mLoadingOriginalSrc);
+  RefPtr<nsDocShellLoadInfo> loadInfo = new nsDocShellLoadInfo();
+
+  loadInfo->SetOriginalFrameSrc(mLoadingOriginalSrc);
   mLoadingOriginalSrc = false;
 
   // If this frame is sandboxed with respect to origin we will set it up with
   // a null principal later in nsDocShell::DoURILoad.
   // We do it there to correctly sandbox content that was loaded into
   // the frame via other methods than the src attribute.
   // We'll use our principal, not that of the document loaded inside us.  This
   // is very important; needed to prevent XSS attacks on documents loaded in
   // subframes!
   if (mTriggeringPrincipal) {
-    loadState->SetTriggeringPrincipal(mTriggeringPrincipal);
+    loadInfo->SetTriggeringPrincipal(mTriggeringPrincipal);
   } else {
-    loadState->SetTriggeringPrincipal(mOwnerContent->NodePrincipal());
+    loadInfo->SetTriggeringPrincipal(mOwnerContent->NodePrincipal());
   }
 
   nsCOMPtr<nsIURI> referrer;
 
   nsAutoString srcdoc;
   bool isSrcdoc = mOwnerContent->IsHTMLElement(nsGkAtoms::iframe) &&
                   mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::srcdoc,
                                          srcdoc);
 
   if (isSrcdoc) {
     nsAutoString referrerStr;
     mOwnerContent->OwnerDoc()->GetReferrer(referrerStr);
     rv = NS_NewURI(getter_AddRefs(referrer), referrerStr);
 
-    loadState->SetSrcdocData(srcdoc);
+    loadInfo->SetSrcdocData(srcdoc);
     nsCOMPtr<nsIURI> baseURI = mOwnerContent->GetBaseURI();
-    loadState->SetBaseURI(baseURI);
+    loadInfo->SetBaseURI(baseURI);
   }
   else {
     rv = mOwnerContent->NodePrincipal()->GetURI(getter_AddRefs(referrer));
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // Use referrer as long as it is not an NullPrincipalURI.
   // We could add a method such as GetReferrerURI to principals to make this
   // cleaner, but given that we need to start using Source Browsing Context for
   // referrer (see Bug 960639) this may be wasted effort at this stage.
   if (referrer) {
     bool isNullPrincipalScheme;
     rv = referrer->SchemeIs(NS_NULLPRINCIPAL_SCHEME, &isNullPrincipalScheme);
     if (NS_SUCCEEDED(rv) && !isNullPrincipalScheme) {
-      loadState->SetReferrer(referrer);
+      loadInfo->SetReferrer(referrer);
     }
   }
 
   // get referrer policy for this iframe:
   // first load document wide policy, then
   // load iframe referrer attribute if enabled in preferences
   // per element referrer overrules document wide referrer if enabled
   net::ReferrerPolicy referrerPolicy = mOwnerContent->OwnerDoc()->GetReferrerPolicy();
   HTMLIFrameElement* iframe = HTMLIFrameElement::FromNode(mOwnerContent);
   if (iframe) {
     net::ReferrerPolicy iframeReferrerPolicy = iframe->GetReferrerPolicyAsEnum();
     if (iframeReferrerPolicy != net::RP_Unset) {
       referrerPolicy = iframeReferrerPolicy;
     }
   }
-  loadState->SetReferrerPolicy(referrerPolicy);
+  loadInfo->SetReferrerPolicy(referrerPolicy);
 
   // Default flags:
   int32_t flags = nsIWebNavigation::LOAD_FLAGS_NONE;
 
   // Flags for browser frame:
   if (OwnerIsMozBrowserFrame()) {
     flags = nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
             nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL;
   }
 
   // Kick off the load...
   bool tmpState = mNeedsAsyncDestroy;
   mNeedsAsyncDestroy = true;
-  loadState->SetURI(mURIToLoad);
-  loadState->SetLoadFlags(flags);
-  loadState->SetFirstParty(false);
-  rv = mDocShell->LoadURI(loadState);
+  nsCOMPtr<nsIURI> uriToLoad = mURIToLoad;
+  rv = mDocShell->LoadURI(uriToLoad, loadInfo, flags, false);
   mNeedsAsyncDestroy = tmpState;
   mURIToLoad = nullptr;
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -60,17 +60,17 @@
 #include "nsWindowSizes.h"
 #include "WindowNamedPropertiesHandler.h"
 #include "nsFrameSelection.h"
 #include "nsNetUtil.h"
 #include "nsVariant.h"
 #include "nsPrintfCString.h"
 #include "mozilla/intl/LocaleService.h"
 #include "WindowDestroyedEvent.h"
-#include "nsDocShellLoadState.h"
+#include "nsDocShellLoadInfo.h"
 
 // Helper Classes
 #include "nsJSUtils.h"
 #include "jsapi.h"
 #include "js/Wrapper.h"
 #include "nsCharSeparatedTokenizer.h"
 #include "nsReadableUtils.h"
 #include "nsJSEnvironment.h"
@@ -5515,43 +5515,43 @@ nsGlobalWindowOuter::OpenOuter(const nsA
 {
   nsCOMPtr<nsPIDOMWindowOuter> window;
   aError = OpenJS(aUrl, aName, aOptions, getter_AddRefs(window));
   return window.forget();
 }
 
 nsresult
 nsGlobalWindowOuter::Open(const nsAString& aUrl, const nsAString& aName,
-                          const nsAString& aOptions, nsDocShellLoadState* aLoadState,
+                          const nsAString& aOptions, nsDocShellLoadInfo* aLoadInfo,
                           bool aForceNoOpener, nsPIDOMWindowOuter **_retval)
 {
   return OpenInternal(aUrl, aName, aOptions,
                       false,          // aDialog
                       false,          // aContentModal
                       true,           // aCalledNoScript
                       false,          // aDoJSFixups
                       true,           // aNavigate
                       nullptr, nullptr,  // No args
-                      aLoadState,
+                      aLoadInfo,
                       aForceNoOpener,
                       _retval);
 }
 
 nsresult
 nsGlobalWindowOuter::OpenJS(const nsAString& aUrl, const nsAString& aName,
                             const nsAString& aOptions, nsPIDOMWindowOuter **_retval)
 {
   return OpenInternal(aUrl, aName, aOptions,
                       false,          // aDialog
                       false,          // aContentModal
                       false,          // aCalledNoScript
                       true,           // aDoJSFixups
                       true,           // aNavigate
                       nullptr, nullptr,  // No args
-                      nullptr,        // aLoadState
+                      nullptr,        // aLoadInfo
                       false,          // aForceNoOpener
                       _retval);
 }
 
 // like Open, but attaches to the new window any extra parameters past
 // [features] as a JS property named "arguments"
 nsresult
 nsGlobalWindowOuter::OpenDialog(const nsAString& aUrl, const nsAString& aName,
@@ -5561,17 +5561,17 @@ nsGlobalWindowOuter::OpenDialog(const ns
 {
   return OpenInternal(aUrl, aName, aOptions,
                       true,                    // aDialog
                       false,                   // aContentModal
                       true,                    // aCalledNoScript
                       false,                   // aDoJSFixups
                       true,                    // aNavigate
                       nullptr, aExtraArgument, // Arguments
-                      nullptr,                 // aLoadState
+                      nullptr,                 // aLoadInfo
                       false,                   // aForceNoOpener
                       _retval);
 }
 
 // Like Open, but passes aNavigate=false.
 /* virtual */ nsresult
 nsGlobalWindowOuter::OpenNoNavigate(const nsAString& aUrl,
                                     const nsAString& aName,
@@ -5580,17 +5580,17 @@ nsGlobalWindowOuter::OpenNoNavigate(cons
 {
   return OpenInternal(aUrl, aName, aOptions,
                       false,          // aDialog
                       false,          // aContentModal
                       true,           // aCalledNoScript
                       false,          // aDoJSFixups
                       false,          // aNavigate
                       nullptr, nullptr,  // No args
-                      nullptr,        // aLoadState
+                      nullptr,        // aLoadInfo
                       false,          // aForceNoOpener
                       _retval);
 
 }
 
 already_AddRefed<nsPIDOMWindowOuter>
 nsGlobalWindowOuter::OpenDialogOuter(JSContext* aCx, const nsAString& aUrl,
                                      const nsAString& aName, const nsAString& aOptions,
@@ -5608,17 +5608,17 @@ nsGlobalWindowOuter::OpenDialogOuter(JSC
   nsCOMPtr<nsPIDOMWindowOuter> dialog;
   aError = OpenInternal(aUrl, aName, aOptions,
                         true,             // aDialog
                         false,            // aContentModal
                         false,            // aCalledNoScript
                         false,            // aDoJSFixups
                         true,                // aNavigate
                         argvArray, nullptr,  // Arguments
-                        nullptr,          // aLoadState
+                        nullptr,          // aLoadInfo
                         false,            // aForceNoOpener
                         getter_AddRefs(dialog));
   return dialog.forget();
 }
 
 already_AddRefed<nsPIDOMWindowOuter>
 nsGlobalWindowOuter::GetFramesOuter()
 {
@@ -6932,17 +6932,17 @@ public:
 
 nsresult
 nsGlobalWindowOuter::OpenInternal(const nsAString& aUrl, const nsAString& aName,
                                   const nsAString& aOptions, bool aDialog,
                                   bool aContentModal, bool aCalledNoScript,
                                   bool aDoJSFixups, bool aNavigate,
                                   nsIArray *argv,
                                   nsISupports *aExtraArgument,
-                                  nsDocShellLoadState* aLoadState,
+                                  nsDocShellLoadInfo* aLoadInfo,
                                   bool aForceNoOpener,
                                   nsPIDOMWindowOuter **aReturn)
 {
 #ifdef DEBUG
   uint32_t argc = 0;
   if (argv)
       argv->GetLength(&argc);
 #endif
@@ -7081,17 +7081,17 @@ nsGlobalWindowOuter::OpenInternal(const 
       // We asserted at the top of this function that aNavigate is true for
       // !aCalledNoScript.
       rv = pwwatch->OpenWindow2(this, url.IsVoid() ? nullptr : url.get(),
                                 name_ptr,
                                 options_ptr, /* aCalledFromScript = */ true,
                                 aDialog, aNavigate, argv,
                                 isPopupSpamWindow,
                                 forceNoOpener,
-                                aLoadState,
+                                aLoadInfo,
                                 getter_AddRefs(domReturn));
     } else {
       // Force a system caller here so that the window watcher won't screw us
       // up.  We do NOT want this case looking at the JS context on the stack
       // when searching.  Compare comments on
       // nsIDOMWindow::OpenWindow and nsIWindowWatcher::OpenWindow.
 
       // Note: Because nsWindowWatcher is so broken, it's actually important
@@ -7104,17 +7104,17 @@ nsGlobalWindowOuter::OpenInternal(const 
       }
 
       rv = pwwatch->OpenWindow2(this, url.IsVoid() ? nullptr : url.get(),
                                 name_ptr,
                                 options_ptr, /* aCalledFromScript = */ false,
                                 aDialog, aNavigate, aExtraArgument,
                                 isPopupSpamWindow,
                                 forceNoOpener,
-                                aLoadState,
+                                aLoadInfo,
                                 getter_AddRefs(domReturn));
 
     }
   }
 
   NS_ENSURE_SUCCESS(rv, rv);
 
   // success!
--- a/dom/base/nsGlobalWindowOuter.h
+++ b/dom/base/nsGlobalWindowOuter.h
@@ -67,17 +67,17 @@ class nsIControllers;
 class nsIJSID;
 class nsIScriptContext;
 class nsIScriptTimeoutHandler;
 class nsITabChild;
 class nsITimeoutHandler;
 class nsIWebBrowserChrome;
 class mozIDOMWindowProxy;
 
-class nsDocShellLoadState;
+class nsDocShellLoadInfo;
 class nsDOMWindowList;
 class nsScreen;
 class nsHistory;
 class nsGlobalWindowObserver;
 class nsGlobalWindowInner;
 class nsDOMWindowUtils;
 class nsIIdleService;
 struct nsRect;
@@ -602,17 +602,17 @@ public:
   mozilla::dom::Element* GetFrameElement() override;
   already_AddRefed<nsPIDOMWindowOuter>
   OpenOuter(const nsAString& aUrl,
             const nsAString& aName,
             const nsAString& aOptions,
             mozilla::ErrorResult& aError);
   nsresult Open(const nsAString& aUrl, const nsAString& aName,
                 const nsAString& aOptions,
-                nsDocShellLoadState* aLoadState,
+                nsDocShellLoadInfo* aLoadInfo,
                 bool aForceNoOpener,
                 nsPIDOMWindowOuter **_retval) override;
   mozilla::dom::Navigator* GetNavigator() override;
 
 #if defined(MOZ_WIDGET_ANDROID)
   int16_t Orientation(mozilla::dom::CallerType aCallerType) const;
 #endif
 
@@ -874,17 +874,17 @@ private:
    *
    * @param argv The arguments to pass to the new window.  The first
    *        three args, if present, will be aUrl, aName, and aOptions.  So this
    *        param only matters if there are more than 3 arguments.
    *
    * @param aExtraArgument Another way to pass arguments in.  This is mutually
    *        exclusive with the argv approach.
    *
-   * @param aLoadState to be passed on along to the windowwatcher.
+   * @param aLoadInfo to be passed on along to the windowwatcher.
    *
    * @param aForceNoOpener if true, will act as if "noopener" were passed in
    *                       aOptions, but without affecting any other window
    *                       features.
    *
    * @param aReturn [out] The window that was opened, if any.  Will be null if
    *                      aForceNoOpener is true of if aOptions contains
    *                      "noopener".
@@ -896,17 +896,17 @@ private:
                         const nsAString& aOptions,
                         bool aDialog,
                         bool aContentModal,
                         bool aCalledNoScript,
                         bool aDoJSFixups,
                         bool aNavigate,
                         nsIArray *argv,
                         nsISupports *aExtraArgument,
-                        nsDocShellLoadState* aLoadState,
+                        nsDocShellLoadInfo* aLoadInfo,
                         bool aForceNoOpener,
                         nsPIDOMWindowOuter **aReturn);
 
   // Checks that the channel was loaded by the URI currently loaded in aDoc
   static bool SameLoadingURI(nsIDocument *aDoc, nsIChannel *aChannel);
 
 public:
   // Helper Functions
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -143,16 +143,17 @@ class ImageLoader;
 class Rule;
 } // namespace css
 
 namespace dom {
 class Animation;
 class AnonymousContent;
 class Attr;
 class BoxObject;
+class XULBroadcastManager;
 class ClientInfo;
 class ClientState;
 class CDATASection;
 class Comment;
 struct CustomElementDefinition;
 class DocGroup;
 class DocumentL10n;
 class DocumentFragment;
@@ -3437,16 +3438,25 @@ public:
                                                        const mozilla::dom::BlockParsingOptions& aOptions,
                                                        mozilla::ErrorResult& aRv);
 
   already_AddRefed<nsIURI> GetMozDocumentURIIfNotForErrorPages();
 
   mozilla::dom::Promise* GetDocumentReadyForIdle(mozilla::ErrorResult& aRv);
 
   nsIDOMXULCommandDispatcher* GetCommandDispatcher();
+  bool HasXULBroadcastManager() const
+  {
+    return mXULBroadcastManager;
+  };
+  void InitializeXULBroadcastManager();
+  mozilla::dom::XULBroadcastManager* GetXULBroadcastManager() const
+  {
+    return mXULBroadcastManager;
+  }
   already_AddRefed<nsINode> GetPopupNode();
   void SetPopupNode(nsINode* aNode);
   nsINode* GetPopupRangeParent(ErrorResult& aRv);
   int32_t GetPopupRangeOffset(ErrorResult& aRv);
   already_AddRefed<nsINode> GetTooltipNode();
   void SetTooltipNode(nsINode* aNode) { /* do nothing */ }
 
   // ParentNode
@@ -4734,16 +4744,18 @@ protected:
   // document.close(), and document.write() when they are invoked by the parser.
   uint32_t mThrowOnDynamicMarkupInsertionCounter;
 
   // Count of unload/beforeunload/pagehide operations in progress.
   uint32_t mIgnoreOpensDuringUnloadCounter;
 
   nsCOMPtr<nsIDOMXULCommandDispatcher> mCommandDispatcher; // [OWNER] of the focus tracker
 
+  RefPtr<mozilla::dom::XULBroadcastManager> mXULBroadcastManager;
+
   // At the moment, trackers might be blocked by Tracking Protection or FastBlock.
   // In order to know the numbers of trackers detected and blocked, we add
   // these two values here and those are shared by TP and FB.
   uint32_t mNumTrackersFound;
   uint32_t mNumTrackersBlocked;
 
   mozilla::EnumSet<mozilla::Telemetry::LABELS_DOCUMENT_ANALYTICS_TRACKER_FASTBLOCKED>
     mTrackerBlockedReasons;
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -26,17 +26,17 @@ class nsDOMOfflineResourceList;
 class nsDOMWindowList;
 class nsGlobalWindowInner;
 class nsGlobalWindowOuter;
 class nsIArray;
 class nsIChannel;
 class nsIContent;
 class nsICSSDeclaration;
 class nsIDocShell;
-class nsDocShellLoadState;
+class nsDocShellLoadInfo;
 class nsIDocument;
 class nsIPrincipal;
 class nsIScriptTimeoutHandler;
 class nsISerialEventTarget;
 class nsIURI;
 class nsPIDOMWindowInner;
 class nsPIDOMWindowOuter;
 class nsPIWindowRoot;
@@ -1116,22 +1116,22 @@ public:
 
   virtual nsresult GetPrompter(nsIPrompt** aPrompt) = 0;
   virtual nsresult GetControllers(nsIControllers** aControllers) = 0;
   virtual already_AddRefed<mozilla::dom::Selection> GetSelection() = 0;
   virtual already_AddRefed<nsPIDOMWindowOuter> GetOpener() = 0;
 
   virtual nsDOMWindowList* GetFrames() = 0;
 
-  // aLoadState will be passed on through to the windowwatcher.
+  // aLoadInfo will be passed on through to the windowwatcher.
   // aForceNoOpener will act just like a "noopener" feature in aOptions except
   //                will not affect any other window features.
   virtual nsresult Open(const nsAString& aUrl, const nsAString& aName,
                         const nsAString& aOptions,
-                        nsDocShellLoadState* aLoadState,
+                        nsDocShellLoadInfo* aLoadInfo,
                         bool aForceNoOpener,
                         nsPIDOMWindowOuter **_retval) = 0;
   virtual nsresult OpenDialog(const nsAString& aUrl, const nsAString& aName,
                               const nsAString& aOptions,
                               nsISupports* aExtraArgument,
                               nsPIDOMWindowOuter** _retval) = 0;
 
   virtual nsresult GetInnerWidth(int32_t* aWidth) = 0;
--- a/dom/clients/manager/ClientNavigateOpChild.cpp
+++ b/dom/clients/manager/ClientNavigateOpChild.cpp
@@ -4,17 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ClientNavigateOpChild.h"
 
 #include "ClientState.h"
 #include "mozilla/Unused.h"
 #include "nsIDocShell.h"
-#include "nsDocShellLoadState.h"
+#include "nsDocShellLoadInfo.h"
 #include "nsIWebNavigation.h"
 #include "nsIWebProgress.h"
 #include "nsIWebProgressListener.h"
 #include "nsNetUtil.h"
 #include "nsPIDOMWindow.h"
 #include "nsURLHelper.h"
 
 namespace mozilla {
@@ -231,26 +231,23 @@ ClientNavigateOpChild::DoNavigate(const 
 
   nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
   nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);
   if (!docShell || !webProgress) {
     ref = ClientOpPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__);
     return ref.forget();
   }
 
-  RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState();
+  RefPtr<nsDocShellLoadInfo> loadInfo = new nsDocShellLoadInfo();
 
-  loadState->SetTriggeringPrincipal(principal);
-  loadState->SetReferrerPolicy(doc->GetReferrerPolicy());
-  loadState->SetLoadType(LOAD_STOP_CONTENT);
-  loadState->SetSourceDocShell(docShell);
-  loadState->SetURI(url);
-  loadState->SetLoadFlags(nsIWebNavigation::LOAD_FLAGS_NONE);
-  loadState->SetFirstParty(true);
-  rv = docShell->LoadURI(loadState);
+  loadInfo->SetTriggeringPrincipal(principal);
+  loadInfo->SetReferrerPolicy(doc->GetReferrerPolicy());
+  loadInfo->SetLoadType(LOAD_STOP_CONTENT);
+  loadInfo->SetSourceDocShell(docShell);
+  rv = docShell->LoadURI(url, loadInfo, nsIWebNavigation::LOAD_FLAGS_NONE, true);
   if (NS_FAILED(rv)) {
     ref = ClientOpPromise::CreateAndReject(rv, __func__);
     return ref.forget();
   }
 
   RefPtr<ClientOpPromise::Private> promise =
     new ClientOpPromise::Private(__func__);
 
--- a/dom/flex/test/chrome/test_flex_items.html
+++ b/dom/flex/test/chrome/test_flex_items.html
@@ -4,57 +4,62 @@
 <meta charset="utf-8">
 <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
 <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
 <style>
   .container {
     display: flex;
     background-color: grey;
     font: 14px sans-serif;
-    width: 800px;
     height: 50px;
   }
+  #flex-sanity {
+    /* This just needs to be large enough so that no shrinking is required. */
+    width: 1600px;
+  }
 
   .base        { align-self: baseline; }
   .lastbase    { align-self: last baseline; }
 
   .offset      { margin-top: 10px;
                  margin-bottom: 3px; }
 
   .lime        { background: lime;   }
   .yellow      { background: yellow; }
   .orange      { background: orange; }
   .pink        { background: pink;   }
+  .tan         { background: tan;    }
   .white       { background: white;  }
 
   .crossMinMax { min-height: 40px;
                  max-height: 120px; }
 
   .mainMinMax  { min-width: 120px;
                  max-width: 500px; }
 
   .flexGrow    { flex-grow: 1; }
+  .spacer150   { width: 150px;
+                 box-sizing: border-box;
+                 height: 10px;
+                 border: 1px solid teal; }
+
 </style>
 
 <script>
 "use strict";
 
 SimpleTest.waitForExplicitFinish();
 
 const TEXT_NODE = Node.TEXT_NODE;
 
 function testItemMatchesExpectedValues(item, values, index) {
   if (typeof(values.node) != "undefined") {
     is(item.node, values.node, "Item index " + index + " has expected node.");
   }
 
-  if (typeof(values.node_todo) != "undefined") {
-    todo_is(item.node, values.node_todo, "Item index " + index + " has expected node.");
-  }
-
   if (typeof(values.mainBaseSize) != "undefined") {
     is(item.mainBaseSize, values.mainBaseSize, "Item index " + index + " has expected mainBaseSize.");
   }
 
   if (typeof(values.mainDeltaSize) != "undefined") {
     is(item.mainDeltaSize, values.mainDeltaSize, "Item index " + index + " has expected mainDeltaSize.");
   }
 
@@ -80,18 +85,19 @@ function testItemMatchesExpectedValues(i
     is(item.crossMaxSize, values.crossMaxSize, "Item index " + index + " has expected crossMaxSize.");
   } else {
     // As above for mainMaxSize, no-expected-value implies we expect +infinity.
     is(item.crossMaxSize, Number.POSITIVE_INFINITY,
        "Item index " + index + " has expected (default) crossMaxSize.");
   }
 }
 
-function runTests() {
-  let container = document.getElementById("wrapper");
+// Test for items in "flex-sanity" flex container:
+function testFlexSanity() {
+  let container = document.getElementById("flex-sanity");
   let flex = container.getAsFlexContainer();
   let lines = flex.getLines();
   is(lines.length, 1, "Container should have expected number of lines.");
 
   let line = lines[0];
   let containerHeight = container.getBoundingClientRect().height;
   is(line.crossSize, containerHeight,
      "Line crossSize should equal the height of the container.");
@@ -113,24 +119,36 @@ function runTests() {
   let lbElemBoundingRect = lbElem.getBoundingClientRect();
   ok(line.lastBaselineOffset > containerHeight - lbElemBoundingRect.bottom &&
      line.lastBaselineOffset < containerHeight - lbElemBoundingRect.top,
      "Line lastBaselineOffset should land somewhere within the element" +
      "that determines it.");
 
   let expectedValues = [
     { crossMinSize: 0 },
-    { mainBaseSize: lbElemBoundingRect.width,
+    { mainBaseSize: 100,
       mainDeltaSize: 0 },
     { crossMinSize: 40,
       crossMaxSize: 120,
       mainDeltaSize: 0 },
     { mainMinSize: 120,
       mainMaxSize: 500,
       mainDeltaSize: 0 },
+    { mainMinSize: 120,
+      mainMaxSize: 500,
+      mainBaseSize: 150,
+      mainDeltaSize: 0 },
+    { mainBaseSize:  10,
+      mainMaxSize:   5,
+      mainDeltaSize: 0 },
+    { mainBaseSize: 10,
+      mainMinSize:  15,
+      mainDeltaSize: 0 },
+    { mainBaseSize: 50,
+      mainMaxSize: 10 },
     { mainDeltaSize: 0 },
     { /* final item is anonymous flex item */ },
   ];
 
   let items = line.getItems();
   is(items.length, expectedValues.length,
      "Line should have expected number of items.");
   is(items.length, container.children.length + 1,
@@ -156,27 +174,132 @@ function runTests() {
     }
     testItemMatchesExpectedValues(item, values, i);
   }
 
   // Check that the delta size of the first item is (roughly) equal to the
   // actual size minus the base size.
   isfuzzy(items[0].mainDeltaSize, firstRect.width - items[0].mainBaseSize, 1e-4,
           "flex-grow item should have expected mainDeltaSize.");
+}
 
+// Test for items in "flex-growing" flex container:
+function testFlexGrowing() {
+  let expectedValues = [
+    { mainBaseSize:  10,
+      mainDeltaSize: 10,
+      mainMinSize:   35 },
+    { mainBaseSize:  20,
+      mainDeltaSize: 5,
+      mainMinSize:   28 },
+    { mainBaseSize:  30,
+      mainDeltaSize: 7  },
+    { mainBaseSize:  0,
+      mainDeltaSize: 48,
+      mainMaxSize:   20 },
+  ];
+
+  let container = document.getElementById("flex-growing");
+  let items = container.getAsFlexContainer().getLines()[0].getItems();
+  is(items.length, container.children.length,
+     "Line should have as many items as the flex container has child elems");
+
+  for (let i = 0; i < items.length; ++i) {
+    let item = items[i];
+    let values = expectedValues[i];
+    testItemMatchesExpectedValues(item, values, i);
+  }
+}
+
+function runTests() {
+  testFlexSanity();
+  testFlexGrowing();
   SimpleTest.finish();
 }
 </script>
 </head>
 
 <body onLoad="runTests();">
-  <div id="wrapper" class="container">
+  <!-- First flex container to be tested: "flex-sanity".
+       We test a few general things, e.g.:
+       - accuracy of reported baselines.
+       - accuracy of reported flex base size, min/max sizes, & trivial deltas.
+       - flex items formation for display:contents subtrees & text nodes.
+   -->
+  <div id="flex-sanity" class="container">
     <div class="lime base flexGrow">one line (first)</div>
     <div class="yellow lastbase" style="width: 100px">one line (last)</div>
     <div class="orange offset lastbase crossMinMax">two<br/>lines and offset (last)</div>
     <div class="pink offset base mainMinMax">offset (first)</div>
+    <!-- Inflexible item w/ content-derived flex base size, which has min/max
+         but doesn't violate them: -->
+    <div class="tan mainMinMax">
+      <div class="spacer150"></div>
+    </div>
+    <!-- Inflexible item that is trivially clamped to smaller max-width: -->
+    <div style="flex: 0 0 10px; max-width: 5px"></div>
+    <!-- Inflexible item that is trivially clamped to larger min-width: -->
+    <div style="flex: 0 0 10px; min-width: 15px"></div>
+    <!-- Item that wants to grow but is trivially clamped to max-width
+         below base size: -->
+    <div style="flex: 1 1 50px; max-width: 10px"></div>
     <div style="display:contents">
       <div class="white">replaced</div>
     </div>
-    anonymous text node
+    anon item for text
+  </div>
+
+  <!-- Second flex container to be tested, with items that grow by specific
+       predictable amounts. This ends up triggering 4 passes of the main
+       flexbox layout algorithm loop - and note that for each item, we only
+       report (via 'mainDeltaSize') the delta that it wanted in the *last pass
+       of the loop before that item was frozen*.
+
+       Here's what goes on in each pass (and the tentative deltas)
+     * PASS 1
+        - Available space = 120 - 10 - 20 - 30 - 0 = 60px.
+        - Sum of flex values = 1+1+2+16 = 20. So 1 "share" is 60px/20 = 3px.
+        - Deltas (space distributed): +3px, +3px, +6px, +48px
+          VIOLATIONS:
+            item0 is now 10+3 = 13px, which under its min
+            item1 is now 20+3 = 23px, which under its min
+            item3 is now 0+48 = 48px, which over its max
+        - the magnitude of max-violations (how far we're out of bounds) exceeds
+          magnitude of min-violations, so we prioritize the max-violations.
+        - So we freeze item3 at its max-width, 20px, leaving its final "desired"
+          mainDeltaSize at +48px from this pass.
+
+     * PASS 2
+        - Available space = 120 - 10 - 20 - 30 - 20 = 40px.
+        - Sum of flex values = 1+1+2 = 4. So 1 "share" is 40px/4 = 10px.
+        - Deltas (space distributed): +10px, +10px, +20px, +0px.
+          VIOLATIONS:
+            item0 is now 10+10 = 20px, which is under its min
+        - So we freeze item0 at its min-width, 35px, leaving its final "desired"
+          mainDeltaSize at +10px from this pass.
+
+     * PASS 3
+        - Available space = 120 - 35 - 20 - 30 - 20 = 15px.
+        - Sum of flex values = 1+2 = 3. So 1 "share" is 15px/3 = 5px.
+        - Deltas (space distributed): +0px, +5px, +10px, +0px.
+          VIOLATIONS:
+            item1 is now 20+5 = 25px, which is under its min
+        - So we freeze item1 at its min-width, 28px, leaving its final "desired"
+          mainDeltaSize at +5px from this pass.
+
+     * PASS 4
+        - Available space = 120 - 35 - 28 - 30 - 20 = 7px.
+        - Sum of flex values = 2. So 1 "share" is 7px/2 = 3.5px
+        - Deltas (space distributed): +0px, +0px, +7px, +0px.
+          VIOLATIONS:
+            None! (So, we'll be done after this pass!)
+        - So we freeze item2 (the only unfrozen item) at its flexed size, 37px,
+          and set its final "desired" mainDeltaSize to +7px from this pass.
+        - And we're done!
+       -->
+  <div id="flex-growing" class="container" style="width: 120px">
+    <div style="flex: 1 10px; min-width: 35px"></div>
+    <div style="flex: 1 20px; min-width: 28px"></div>
+    <div style="flex: 2 30px; min-width: 0"></div>
+    <div style="flex: 16 0px; max-width: 20px"></div>
   </div>
 </body>
 </html>
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -114,17 +114,17 @@
 #include <unistd.h>
 #endif
 #endif
 
 #include "mozilla/Unused.h"
 
 #include "mozInlineSpellChecker.h"
 #include "nsDocShell.h"
-#include "nsDocShellLoadState.h"
+#include "nsDocShellLoadInfo.h"
 #include "nsIConsoleListener.h"
 #include "nsIContentViewer.h"
 #include "nsICycleCollectorListener.h"
 #include "nsIIdlePeriod.h"
 #include "nsIDragService.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIMemoryReporter.h"
 #include "nsIMemoryInfoDumper.h"
@@ -769,29 +769,29 @@ ContentChild::ProvideWindow(mozIDOMWindo
                             uint32_t aChromeFlags,
                             bool aCalledFromJS,
                             bool aPositionSpecified,
                             bool aSizeSpecified,
                             nsIURI* aURI,
                             const nsAString& aName,
                             const nsACString& aFeatures,
                             bool aForceNoOpener,
-                            nsDocShellLoadState* aLoadState,
+                            nsDocShellLoadInfo* aLoadInfo,
                             bool* aWindowIsNew,
                             mozIDOMWindowProxy** aReturn)
 {
   return ProvideWindowCommon(nullptr, aParent, false, aChromeFlags,
                              aCalledFromJS, aPositionSpecified,
                              aSizeSpecified, aURI, aName, aFeatures,
-                             aForceNoOpener, aLoadState, aWindowIsNew, aReturn);
+                             aForceNoOpener, aLoadInfo, aWindowIsNew, aReturn);
 }
 
 static nsresult
 GetCreateWindowParams(mozIDOMWindowProxy* aParent,
-                      nsDocShellLoadState* aLoadState,
+                      nsDocShellLoadInfo* aLoadInfo,
                       nsACString& aBaseURIString, float* aFullZoom,
                       uint32_t* aReferrerPolicy,
                       nsIPrincipal** aTriggeringPrincipal)
 {
   *aFullZoom = 1.0f;
   if (!aTriggeringPrincipal) {
     NS_ERROR("aTriggeringPrincipal is null");
     return NS_ERROR_FAILURE;
@@ -809,21 +809,21 @@ GetCreateWindowParams(mozIDOMWindowProxy
   nsCOMPtr<nsIURI> baseURI = doc->GetDocBaseURI();
   if (!baseURI) {
     NS_ERROR("nsIDocument didn't return a base URI");
     return NS_ERROR_FAILURE;
   }
 
   baseURI->GetSpec(aBaseURIString);
 
-  if (aLoadState) {
-    if (!aLoadState->SendReferrer()) {
+  if (aLoadInfo) {
+    if (!aLoadInfo->SendReferrer()) {
       *aReferrerPolicy = mozilla::net::RP_No_Referrer;
     } else {
-      *aReferrerPolicy = aLoadState->ReferrerPolicy();
+      *aReferrerPolicy = aLoadInfo->ReferrerPolicy();
     }
   }
 
   RefPtr<nsDocShell> openerDocShell =
     static_cast<nsDocShell*>(opener->GetDocShell());
   if (!openerDocShell) {
     return NS_OK;
   }
@@ -844,17 +844,17 @@ ContentChild::ProvideWindowCommon(TabChi
                                   uint32_t aChromeFlags,
                                   bool aCalledFromJS,
                                   bool aPositionSpecified,
                                   bool aSizeSpecified,
                                   nsIURI* aURI,
                                   const nsAString& aName,
                                   const nsACString& aFeatures,
                                   bool aForceNoOpener,
-                                  nsDocShellLoadState* aLoadState,
+                                  nsDocShellLoadInfo* aLoadInfo,
                                   bool* aWindowIsNew,
                                   mozIDOMWindowProxy** aReturn)
 {
   *aReturn = nullptr;
 
   nsAutoPtr<IPCTabContext> ipcContext;
   TabId openerTabId = TabId(0);
   nsAutoCString features(aFeatures);
@@ -891,17 +891,17 @@ ContentChild::ProvideWindowCommon(TabChi
 
   // If we're in a content process and we have noopener set, there's no reason
   // to load in our process, so let's load it elsewhere!
   if (loadInDifferentProcess) {
     nsAutoCString baseURIString;
     float fullZoom;
     nsCOMPtr<nsIPrincipal> triggeringPrincipal;
     uint32_t referrerPolicy = mozilla::net::RP_Unset;
-    rv = GetCreateWindowParams(aParent, aLoadState, baseURIString, &fullZoom,
+    rv = GetCreateWindowParams(aParent, aLoadInfo, baseURIString, &fullZoom,
                                &referrerPolicy,
                                getter_AddRefs(triggeringPrincipal));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     OptionalURIParams uriToLoad;
     SerializeURI(aURI, uriToLoad);
@@ -1105,17 +1105,17 @@ ContentChild::ProvideWindowCommon(TabChi
                                          NS_ConvertUTF8toUTF16(url),
                                          name, NS_ConvertUTF8toUTF16(features),
                                          std::move(resolve), std::move(reject));
   } else {
     nsAutoCString baseURIString;
     float fullZoom;
     nsCOMPtr<nsIPrincipal> triggeringPrincipal;
     uint32_t referrerPolicy = mozilla::net::RP_Unset;
-    rv = GetCreateWindowParams(aParent, aLoadState, baseURIString, &fullZoom,
+    rv = GetCreateWindowParams(aParent, aLoadInfo, baseURIString, &fullZoom,
                                &referrerPolicy,
                                getter_AddRefs(triggeringPrincipal));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     OptionalURIParams uriToLoad;
     if (aURI) {
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -29,17 +29,17 @@
 
 struct ChromePackage;
 class nsIObserver;
 struct SubstitutionMapping;
 struct OverrideMapping;
 class nsIDomainPolicy;
 class nsIURIClassifierCallback;
 struct LookAndFeelInt;
-class nsDocShellLoadState;
+class nsDocShellLoadInfo;
 
 namespace mozilla {
 class RemoteSpellcheckEngineChild;
 class ChildProfilerController;
 
 using mozilla::loader::PScriptCacheChild;
 
 #if !defined(XP_WIN)
@@ -114,17 +114,17 @@ public:
                       uint32_t aChromeFlags,
                       bool aCalledFromJS,
                       bool aPositionSpecified,
                       bool aSizeSpecified,
                       nsIURI* aURI,
                       const nsAString& aName,
                       const nsACString& aFeatures,
                       bool aForceNoOpener,
-                      nsDocShellLoadState* aLoadState,
+                      nsDocShellLoadInfo* aLoadInfo,
                       bool* aWindowIsNew,
                       mozIDOMWindowProxy** aReturn);
 
   bool Init(MessageLoop* aIOLoop,
             base::ProcessId aParentPid,
             const char* aParentBuildID,
             IPC::Channel* aChannel,
             uint64_t aChildID,
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -119,17 +119,17 @@
 #include "nsCommandParams.h"
 #include "nsISHistory.h"
 #include "nsQueryObject.h"
 #include "nsIHttpChannel.h"
 #include "mozilla/dom/DocGroup.h"
 #include "nsString.h"
 #include "nsISupportsPrimitives.h"
 #include "mozilla/Telemetry.h"
-#include "nsDocShellLoadState.h"
+#include "nsDocShellLoadInfo.h"
 #include "nsWebBrowser.h"
 
 #ifdef XP_WIN
 #include "mozilla/plugins/PluginWidgetChild.h"
 #endif
 
 #ifdef NS_PRINTING
 #include "nsIPrintSession.h"
@@ -955,17 +955,17 @@ TabChild::GetInterface(const nsIID & aII
 
 NS_IMETHODIMP
 TabChild::ProvideWindow(mozIDOMWindowProxy* aParent,
                         uint32_t aChromeFlags,
                         bool aCalledFromJS,
                         bool aPositionSpecified, bool aSizeSpecified,
                         nsIURI* aURI, const nsAString& aName,
                         const nsACString& aFeatures, bool aForceNoOpener,
-                        nsDocShellLoadState* aLoadState, bool* aWindowIsNew,
+                        nsDocShellLoadInfo* aLoadInfo, bool* aWindowIsNew,
                         mozIDOMWindowProxy** aReturn)
 {
     *aReturn = nullptr;
 
     // If aParent is inside an <iframe mozbrowser> and this isn't a request to
     // open a modal-type window, we're going to create a new <iframe mozbrowser>
     // and return its window here.
     nsCOMPtr<nsIDocShell> docshell = do_GetInterface(aParent);
@@ -999,17 +999,17 @@ TabChild::ProvideWindow(mozIDOMWindowPro
                                    aChromeFlags,
                                    aCalledFromJS,
                                    aPositionSpecified,
                                    aSizeSpecified,
                                    aURI,
                                    aName,
                                    aFeatures,
                                    aForceNoOpener,
-                                   aLoadState,
+                                   aLoadInfo,
                                    aWindowIsNew,
                                    aReturn);
 }
 
 void
 TabChild::DestroyWindow()
 {
     if (mCoalescedMouseEventFlusher) {
new file mode 100644
--- /dev/null
+++ b/dom/xul/XULBroadcastManager.cpp
@@ -0,0 +1,614 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=4 sw=4 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "XULBroadcastManager.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "mozilla/EventDispatcher.h"
+#include "nsXULElement.h"
+#include "mozilla/Logging.h"
+
+
+struct BroadcastListener {
+  nsWeakPtr mListener;
+  RefPtr<nsAtom> mAttribute;
+};
+
+struct BroadcasterMapEntry : public PLDHashEntryHdr
+{
+  Element* mBroadcaster;  // [WEAK]
+  nsTArray<BroadcastListener*> mListeners;  // [OWNING] of BroadcastListener objects
+};
+
+struct nsAttrNameInfo
+{
+  nsAttrNameInfo(int32_t aNamespaceID, nsAtom* aName, nsAtom* aPrefix) :
+    mNamespaceID(aNamespaceID), mName(aName), mPrefix(aPrefix) {}
+  nsAttrNameInfo(const nsAttrNameInfo& aOther) :
+    mNamespaceID(aOther.mNamespaceID), mName(aOther.mName),
+    mPrefix(aOther.mPrefix) {}
+  int32_t           mNamespaceID;
+  RefPtr<nsAtom> mName;
+  RefPtr<nsAtom> mPrefix;
+};
+
+static void
+ClearBroadcasterMapEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry)
+{
+    BroadcasterMapEntry* entry =
+        static_cast<BroadcasterMapEntry*>(aEntry);
+    for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
+        delete entry->mListeners[i];
+    }
+    entry->mListeners.Clear();
+
+    // N.B. that we need to manually run the dtor because we
+    // constructed the nsTArray object in-place.
+    entry->mListeners.~nsTArray<BroadcastListener*>();
+}
+
+static bool
+CanBroadcast(int32_t aNameSpaceID, nsAtom* aAttribute)
+{
+    // Don't push changes to the |id|, |persist|, |command| or
+    // |observes| attribute.
+    if (aNameSpaceID == kNameSpaceID_None) {
+        if ((aAttribute == nsGkAtoms::id) ||
+            (aAttribute == nsGkAtoms::persist) ||
+            (aAttribute == nsGkAtoms::command) ||
+            (aAttribute == nsGkAtoms::observes)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+namespace mozilla {
+namespace dom {
+static LazyLogModule sXULBroadCastManager("XULBroadcastManager");
+
+/* static */
+bool
+XULBroadcastManager::MayNeedListener(const Element& aElement) {
+    if (aElement.NodeInfo()->Equals(nsGkAtoms::observes, kNameSpaceID_XUL)) {
+        return true;
+    }
+    if (aElement.HasAttr(nsGkAtoms::observes)) {
+        return true;
+    }
+    if (aElement.HasAttr(nsGkAtoms::command) &&
+        !(aElement.NodeInfo()->Equals(nsGkAtoms::menuitem, kNameSpaceID_XUL) ||
+          aElement.NodeInfo()->Equals(nsGkAtoms::key, kNameSpaceID_XUL))) {
+        return true;
+    }
+    return false;
+}
+
+XULBroadcastManager::XULBroadcastManager(nsIDocument* aDocument)
+  : mDocument(aDocument),
+    mBroadcasterMap(nullptr),
+    mHandlingDelayedAttrChange(false),
+    mHandlingDelayedBroadcasters(false)
+{
+}
+
+XULBroadcastManager::~XULBroadcastManager()
+{
+  delete mBroadcasterMap;
+}
+
+void
+XULBroadcastManager::DropDocumentReference(void)
+{
+  mDocument = nullptr;
+}
+
+void
+XULBroadcastManager::SynchronizeBroadcastListener(Element *aBroadcaster,
+                                                  Element *aListener,
+                                                  const nsAString &aAttr)
+{
+    if (!nsContentUtils::IsSafeToRunScript()) {
+        nsDelayedBroadcastUpdate delayedUpdate(aBroadcaster, aListener,
+                                               aAttr);
+        mDelayedBroadcasters.AppendElement(delayedUpdate);
+        MaybeBroadcast();
+        return;
+    }
+    bool notify = mHandlingDelayedBroadcasters;
+
+    if (aAttr.EqualsLiteral("*")) {
+        uint32_t count = aBroadcaster->GetAttrCount();
+        nsTArray<nsAttrNameInfo> attributes(count);
+        for (uint32_t i = 0; i < count; ++i) {
+            const nsAttrName* attrName = aBroadcaster->GetAttrNameAt(i);
+            int32_t nameSpaceID = attrName->NamespaceID();
+            nsAtom* name = attrName->LocalName();
+
+            // _Don't_ push the |id|, |ref|, or |persist| attribute's value!
+            if (! CanBroadcast(nameSpaceID, name))
+                continue;
+
+            attributes.AppendElement(nsAttrNameInfo(nameSpaceID, name,
+                                                    attrName->GetPrefix()));
+        }
+
+        count = attributes.Length();
+        while (count-- > 0) {
+            int32_t nameSpaceID = attributes[count].mNamespaceID;
+            nsAtom* name = attributes[count].mName;
+            nsAutoString value;
+            if (aBroadcaster->GetAttr(nameSpaceID, name, value)) {
+              aListener->SetAttr(nameSpaceID, name, attributes[count].mPrefix,
+                                 value, notify);
+            }
+
+#if 0
+            // XXX we don't fire the |onbroadcast| handler during
+            // initial hookup: doing so would potentially run the
+            // |onbroadcast| handler before the |onload| handler,
+            // which could define JS properties that mask XBL
+            // properties, etc.
+            ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name);
+#endif
+        }
+    }
+    else {
+        // Find out if the attribute is even present at all.
+        RefPtr<nsAtom> name = NS_Atomize(aAttr);
+
+        nsAutoString value;
+        if (aBroadcaster->GetAttr(kNameSpaceID_None, name, value)) {
+            aListener->SetAttr(kNameSpaceID_None, name, value, notify);
+        } else {
+            aListener->UnsetAttr(kNameSpaceID_None, name, notify);
+        }
+
+#if 0
+        // XXX we don't fire the |onbroadcast| handler during initial
+        // hookup: doing so would potentially run the |onbroadcast|
+        // handler before the |onload| handler, which could define JS
+        // properties that mask XBL properties, etc.
+        ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name);
+#endif
+    }
+}
+
+void
+XULBroadcastManager::AddListenerFor(Element& aBroadcaster, Element& aListener,
+                                    const nsAString& aAttr, ErrorResult& aRv)
+{
+    if (!mDocument) {
+        aRv.Throw(NS_ERROR_FAILURE);
+        return;
+    }
+
+    nsresult rv =
+        nsContentUtils::CheckSameOrigin(mDocument, &aBroadcaster);
+
+    if (NS_FAILED(rv)) {
+        aRv.Throw(rv);
+        return;
+    }
+
+    rv = nsContentUtils::CheckSameOrigin(mDocument, &aListener);
+
+    if (NS_FAILED(rv)) {
+        aRv.Throw(rv);
+        return;
+    }
+
+    static const PLDHashTableOps gOps = {
+        PLDHashTable::HashVoidPtrKeyStub,
+        PLDHashTable::MatchEntryStub,
+        PLDHashTable::MoveEntryStub,
+        ClearBroadcasterMapEntry,
+        nullptr
+    };
+
+    if (! mBroadcasterMap) {
+        mBroadcasterMap = new PLDHashTable(&gOps, sizeof(BroadcasterMapEntry));
+    }
+
+    auto entry = static_cast<BroadcasterMapEntry*>
+                            (mBroadcasterMap->Search(&aBroadcaster));
+    if (!entry) {
+        entry = static_cast<BroadcasterMapEntry*>
+                           (mBroadcasterMap->Add(&aBroadcaster, fallible));
+
+        if (! entry) {
+            aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+            return;
+        }
+
+        entry->mBroadcaster = &aBroadcaster;
+
+        // N.B. placement new to construct the nsTArray object in-place
+        new (&entry->mListeners) nsTArray<BroadcastListener*>();
+    }
+
+    // Only add the listener if it's not there already!
+    RefPtr<nsAtom> attr = NS_Atomize(aAttr);
+
+    for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
+        BroadcastListener* bl = entry->mListeners[i];
+        nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener);
+
+        if (blListener == &aListener && bl->mAttribute == attr)
+            return;
+    }
+
+    BroadcastListener* bl = new BroadcastListener;
+    bl->mListener  = do_GetWeakReference(&aListener);
+    bl->mAttribute = attr;
+
+    entry->mListeners.AppendElement(bl);
+
+    SynchronizeBroadcastListener(&aBroadcaster, &aListener, aAttr);
+}
+
+void
+XULBroadcastManager::RemoveListenerFor(Element& aBroadcaster,
+                                       Element& aListener,
+                                       const nsAString& aAttr)
+{
+    // If we haven't added any broadcast listeners, then there sure
+    // aren't any to remove.
+    if (! mBroadcasterMap)
+        return;
+
+    auto entry = static_cast<BroadcasterMapEntry*>
+                            (mBroadcasterMap->Search(&aBroadcaster));
+    if (entry) {
+        RefPtr<nsAtom> attr = NS_Atomize(aAttr);
+        for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
+            BroadcastListener* bl = entry->mListeners[i];
+            nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener);
+
+            if (blListener == &aListener && bl->mAttribute == attr) {
+                entry->mListeners.RemoveElementAt(i);
+                delete bl;
+
+                if (entry->mListeners.IsEmpty())
+                    mBroadcasterMap->RemoveEntry(entry);
+
+                break;
+            }
+        }
+    }
+}
+
+nsresult
+XULBroadcastManager::ExecuteOnBroadcastHandlerFor(Element* aBroadcaster,
+                                                  Element* aListener,
+                                                  nsAtom* aAttr)
+{
+    if (!mDocument) {
+      return NS_OK;
+    }
+    // Now we execute the onchange handler in the context of the
+    // observer. We need to find the observer in order to
+    // execute the handler.
+
+    for (nsIContent* child = aListener->GetFirstChild();
+         child;
+         child = child->GetNextSibling()) {
+
+        // Look for an <observes> element beneath the listener. This
+        // ought to have an |element| attribute that refers to
+        // aBroadcaster, and an |attribute| element that tells us what
+        // attriubtes we're listening for.
+        if (!child->IsXULElement(nsGkAtoms::observes))
+            continue;
+
+        // Is this the element that was listening to us?
+        nsAutoString listeningToID;
+        child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::element, listeningToID);
+
+        nsAutoString broadcasterID;
+        aBroadcaster->GetAttr(kNameSpaceID_None, nsGkAtoms::id, broadcasterID);
+
+        if (listeningToID != broadcasterID)
+            continue;
+
+        // We are observing the broadcaster, but is this the right
+        // attribute?
+        nsAutoString listeningToAttribute;
+        child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute,
+                                    listeningToAttribute);
+
+        if (!aAttr->Equals(listeningToAttribute) &&
+            !listeningToAttribute.EqualsLiteral("*")) {
+            continue;
+        }
+
+        // This is the right <observes> element. Execute the
+        // |onbroadcast| event handler
+        WidgetEvent event(true, eXULBroadcast);
+
+        RefPtr<nsPresContext> presContext = mDocument->GetPresContext();
+        if (presContext) {
+          // Handle the DOM event
+          nsEventStatus status = nsEventStatus_eIgnore;
+          EventDispatcher::Dispatch(child, presContext, &event, nullptr,
+                                    &status);
+        }
+    }
+
+    return NS_OK;
+}
+
+void
+XULBroadcastManager::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
+                                      nsAtom* aAttribute)
+{
+    if (!mDocument) {
+        return;
+    }
+    NS_ASSERTION(aElement->OwnerDoc() == mDocument, "unexpected doc");
+
+    // Synchronize broadcast listeners
+    if (mBroadcasterMap &&
+        CanBroadcast(aNameSpaceID, aAttribute)) {
+        auto entry = static_cast<BroadcasterMapEntry*>
+                                (mBroadcasterMap->Search(aElement));
+
+        if (entry) {
+            // We've got listeners: push the value.
+            nsAutoString value;
+            bool attrSet = aElement->GetAttr(kNameSpaceID_None, aAttribute, value);
+
+            for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
+                BroadcastListener* bl = entry->mListeners[i];
+                if ((bl->mAttribute == aAttribute) ||
+                    (bl->mAttribute == nsGkAtoms::_asterisk)) {
+                    nsCOMPtr<Element> listenerEl
+                        = do_QueryReferent(bl->mListener);
+                    if (listenerEl) {
+                        nsAutoString currentValue;
+                        bool hasAttr = listenerEl->GetAttr(kNameSpaceID_None,
+                                                           aAttribute,
+                                                           currentValue);
+                        // We need to update listener only if we're
+                        // (1) removing an existing attribute,
+                        // (2) adding a new attribute or
+                        // (3) changing the value of an attribute.
+                        bool needsAttrChange =
+                            attrSet != hasAttr || !value.Equals(currentValue);
+                        nsDelayedBroadcastUpdate delayedUpdate(aElement,
+                                                               listenerEl,
+                                                               aAttribute,
+                                                               value,
+                                                               attrSet,
+                                                               needsAttrChange);
+
+                        size_t index =
+                            mDelayedAttrChangeBroadcasts.IndexOf(delayedUpdate,
+                                0, nsDelayedBroadcastUpdate::Comparator());
+                        if (index != mDelayedAttrChangeBroadcasts.NoIndex) {
+                            if (mHandlingDelayedAttrChange) {
+                                NS_WARNING("Broadcasting loop!");
+                                continue;
+                            }
+                            mDelayedAttrChangeBroadcasts.RemoveElementAt(index);
+                        }
+
+                        mDelayedAttrChangeBroadcasts.AppendElement(delayedUpdate);
+                    }
+                }
+            }
+        }
+    }
+}
+
+void
+XULBroadcastManager::MaybeBroadcast()
+{
+    // Only broadcast when not in an update and when safe to run scripts.
+    if (mDocument && mDocument->UpdateNestingLevel() == 0 &&
+        (mDelayedAttrChangeBroadcasts.Length() ||
+         mDelayedBroadcasters.Length())) {
+        if (!nsContentUtils::IsSafeToRunScript()) {
+            if (mDocument) {
+              nsContentUtils::AddScriptRunner(
+                NewRunnableMethod("dom::XULBroadcastManager::MaybeBroadcast",
+                                  this,
+                                  &XULBroadcastManager::MaybeBroadcast));
+            }
+            return;
+        }
+        if (!mHandlingDelayedAttrChange) {
+            mHandlingDelayedAttrChange = true;
+            for (uint32_t i = 0; i < mDelayedAttrChangeBroadcasts.Length(); ++i) {
+                nsAtom* attrName = mDelayedAttrChangeBroadcasts[i].mAttrName;
+                if (mDelayedAttrChangeBroadcasts[i].mNeedsAttrChange) {
+                    nsCOMPtr<Element> listener = mDelayedAttrChangeBroadcasts[i].mListener;
+                    const nsString& value = mDelayedAttrChangeBroadcasts[i].mAttr;
+                    if (mDelayedAttrChangeBroadcasts[i].mSetAttr) {
+                        listener->SetAttr(kNameSpaceID_None, attrName, value,
+                                          true);
+                    } else {
+                        listener->UnsetAttr(kNameSpaceID_None, attrName,
+                                            true);
+                    }
+                }
+                ExecuteOnBroadcastHandlerFor(mDelayedAttrChangeBroadcasts[i].mBroadcaster,
+                                             mDelayedAttrChangeBroadcasts[i].mListener,
+                                             attrName);
+            }
+            mDelayedAttrChangeBroadcasts.Clear();
+            mHandlingDelayedAttrChange = false;
+        }
+
+        uint32_t length = mDelayedBroadcasters.Length();
+        if (length) {
+            bool oldValue = mHandlingDelayedBroadcasters;
+            mHandlingDelayedBroadcasters = true;
+            nsTArray<nsDelayedBroadcastUpdate> delayedBroadcasters;
+            mDelayedBroadcasters.SwapElements(delayedBroadcasters);
+            for (uint32_t i = 0; i < length; ++i) {
+                SynchronizeBroadcastListener(delayedBroadcasters[i].mBroadcaster,
+                                             delayedBroadcasters[i].mListener,
+                                             delayedBroadcasters[i].mAttr);
+            }
+            mHandlingDelayedBroadcasters = oldValue;
+        }
+    }
+}
+
+nsresult
+XULBroadcastManager::FindBroadcaster(Element* aElement,
+                                     Element** aListener,
+                                     nsString& aBroadcasterID,
+                                     nsString& aAttribute,
+                                     Element** aBroadcaster)
+{
+    NodeInfo *ni = aElement->NodeInfo();
+    *aListener = nullptr;
+    *aBroadcaster = nullptr;
+
+    if (ni->Equals(nsGkAtoms::observes, kNameSpaceID_XUL)) {
+        // It's an <observes> element, which means that the actual
+        // listener is the _parent_ node. This element should have an
+        // 'element' attribute that specifies the ID of the
+        // broadcaster element, and an 'attribute' element, which
+        // specifies the name of the attribute to observe.
+        nsIContent* parent = aElement->GetParent();
+        if (!parent) {
+             // <observes> is the root element
+            return NS_FINDBROADCASTER_NOT_FOUND;
+        }
+
+        *aListener = Element::FromNode(parent);
+        NS_IF_ADDREF(*aListener);
+
+        aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::element, aBroadcasterID);
+        if (aBroadcasterID.IsEmpty()) {
+            return NS_FINDBROADCASTER_NOT_FOUND;
+        }
+        aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute, aAttribute);
+    }
+    else {
+        // It's a generic element, which means that we'll use the
+        // value of the 'observes' attribute to determine the ID of
+        // the broadcaster element, and we'll watch _all_ of its
+        // values.
+        aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::observes, aBroadcasterID);
+
+        // Bail if there's no aBroadcasterID
+        if (aBroadcasterID.IsEmpty()) {
+            // Try the command attribute next.
+            aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::command, aBroadcasterID);
+            if (!aBroadcasterID.IsEmpty()) {
+                // We've got something in the command attribute.  We
+                // only treat this as a normal broadcaster if we are
+                // not a menuitem or a key.
+
+                if (ni->Equals(nsGkAtoms::menuitem, kNameSpaceID_XUL) ||
+                    ni->Equals(nsGkAtoms::key, kNameSpaceID_XUL)) {
+                return NS_FINDBROADCASTER_NOT_FOUND;
+              }
+            }
+            else {
+              return NS_FINDBROADCASTER_NOT_FOUND;
+            }
+        }
+
+        *aListener = aElement;
+        NS_ADDREF(*aListener);
+
+        aAttribute.Assign('*');
+    }
+
+    // Make sure we got a valid listener.
+    NS_ENSURE_TRUE(*aListener, NS_ERROR_UNEXPECTED);
+
+    // Try to find the broadcaster element in the document.
+    nsIDocument* doc = aElement->GetComposedDoc();
+    if (doc) {
+      *aBroadcaster = doc->GetElementById(aBroadcasterID);
+    }
+
+    // The broadcaster element is missing.
+    if (! *aBroadcaster) {
+        return NS_FINDBROADCASTER_NOT_FOUND;
+    }
+
+    NS_ADDREF(*aBroadcaster);
+
+    return NS_FINDBROADCASTER_FOUND;
+}
+
+nsresult
+XULBroadcastManager::UpdateListenerHookup(Element* aElement, HookupAction aAction)
+{
+    // Resolve a broadcaster hookup. Look at the element that we're
+    // trying to resolve: it could be an '<observes>' element, or just
+    // a vanilla element with an 'observes' attribute on it.
+    nsresult rv;
+
+    nsCOMPtr<Element> listener;
+    nsAutoString broadcasterID;
+    nsAutoString attribute;
+    nsCOMPtr<Element> broadcaster;
+
+    rv = FindBroadcaster(aElement, getter_AddRefs(listener),
+                         broadcasterID, attribute, getter_AddRefs(broadcaster));
+    switch (rv) {
+        case NS_FINDBROADCASTER_NOT_FOUND:
+            return NS_OK;
+        case NS_FINDBROADCASTER_FOUND:
+            break;
+        default:
+            return rv;
+    }
+
+    NS_ENSURE_ARG(broadcaster && listener);
+    if (aAction == eHookupAdd) {
+        ErrorResult domRv;
+        AddListenerFor(*broadcaster, *listener, attribute, domRv);
+        if (domRv.Failed()) {
+            return domRv.StealNSResult();
+        }
+    } else {
+        RemoveListenerFor(*broadcaster, *listener, attribute);
+    }
+
+    // Tell the world we succeeded
+    if (MOZ_LOG_TEST(sXULBroadCastManager, LogLevel::Debug)) {
+        nsCOMPtr<nsIContent> content = listener;
+        NS_ASSERTION(content != nullptr, "not an nsIContent");
+        if (!content) {
+            return rv;
+        }
+
+        nsAutoCString attributeC,broadcasteridC;
+        LossyCopyUTF16toASCII(attribute, attributeC);
+        LossyCopyUTF16toASCII(broadcasterID, broadcasteridC);
+        MOZ_LOG(sXULBroadCastManager, LogLevel::Debug,
+               ("xul: broadcaster hookup <%s attribute='%s'> to %s",
+                nsAtomCString(content->NodeInfo()->NameAtom()).get(),
+                attributeC.get(),
+                broadcasteridC.get()));
+    }
+
+    return NS_OK;
+}
+
+nsresult
+XULBroadcastManager::AddListener(Element* aElement)
+{
+    return UpdateListenerHookup(aElement, eHookupAdd);
+}
+
+nsresult
+XULBroadcastManager::RemoveListener(Element* aElement)
+{
+    return UpdateListenerHookup(aElement, eHookupRemove);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/xul/XULBroadcastManager.h
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_XULBroadcastManager_h
+#define mozilla_dom_XULBroadcastManager_h
+
+#include "mozilla/dom/Element.h"
+#include "nsAtom.h"
+
+class nsXULElement;
+
+namespace mozilla {
+namespace dom {
+
+class XULBroadcastManager final {
+
+public:
+  typedef mozilla::dom::Element Element;
+
+  explicit XULBroadcastManager(nsIDocument* aDocument);
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(XULBroadcastManager)
+
+  /**
+   * Checks whether an element uses any of the special broadcaster attributes
+   * or is an observes element. This mimics the logic in FindBroadcaster, but
+   * is intended to be a lighter weight check and doesn't actually guarantee
+   * that the element will need a listener.
+   */
+  static bool MayNeedListener(const Element& aElement);
+
+  nsresult AddListener(Element* aElement);
+  nsresult RemoveListener(Element* aElement);
+  void AttributeChanged(Element* aElement, int32_t aNameSpaceID,
+                        nsAtom* aAttribute);
+  void MaybeBroadcast();
+  void DropDocumentReference(); // notification that doc is going away
+protected:
+
+   enum HookupAction {
+    eHookupAdd = 0,
+    eHookupRemove
+  };
+
+  nsresult UpdateListenerHookup(Element* aElement, HookupAction aAction);
+
+  void RemoveListenerFor(Element& aBroadcaster, Element& aListener,
+                         const nsAString& aAttr);
+  void AddListenerFor(Element& aBroadcaster, Element& aListener,
+                      const nsAString& aAttr, ErrorResult& aRv);
+
+  nsresult
+  ExecuteOnBroadcastHandlerFor(Element* aBroadcaster,
+                               Element* aListener,
+                               nsAtom* aAttr);
+  // The out params of FindBroadcaster only have values that make sense when
+  // the method returns NS_FINDBROADCASTER_FOUND.  In all other cases, the
+  // values of the out params should not be relied on (though *aListener and
+  // *aBroadcaster do need to be released if non-null, of course).
+  nsresult
+  FindBroadcaster(Element* aElement,
+                  Element** aListener,
+                  nsString& aBroadcasterID,
+                  nsString& aAttribute,
+                  Element** aBroadcaster);
+
+  void
+  SynchronizeBroadcastListener(Element *aBroadcaster,
+                               Element *aListener,
+                               const nsAString &aAttr);
+
+
+  // This reference is nulled by the Document in it's destructor through
+  // DropDocumentReference().
+  nsIDocument* MOZ_NON_OWNING_REF mDocument;
+
+  /**
+   * A map from a broadcaster element to a list of listener elements.
+   */
+  PLDHashTable* mBroadcasterMap;
+
+  class nsDelayedBroadcastUpdate
+  {
+  public:
+    nsDelayedBroadcastUpdate(Element* aBroadcaster,
+                             Element* aListener,
+                             const nsAString &aAttr)
+    : mBroadcaster(aBroadcaster), mListener(aListener), mAttr(aAttr),
+      mSetAttr(false), mNeedsAttrChange(false) {}
+
+    nsDelayedBroadcastUpdate(Element* aBroadcaster,
+                             Element* aListener,
+                             nsAtom* aAttrName,
+                             const nsAString &aAttr,
+                             bool aSetAttr,
+                             bool aNeedsAttrChange)
+    : mBroadcaster(aBroadcaster), mListener(aListener), mAttr(aAttr),
+      mAttrName(aAttrName), mSetAttr(aSetAttr),
+      mNeedsAttrChange(aNeedsAttrChange) {}
+
+    nsDelayedBroadcastUpdate(const nsDelayedBroadcastUpdate& aOther)
+    : mBroadcaster(aOther.mBroadcaster), mListener(aOther.mListener),
+      mAttr(aOther.mAttr), mAttrName(aOther.mAttrName),
+      mSetAttr(aOther.mSetAttr), mNeedsAttrChange(aOther.mNeedsAttrChange) {}
+
+    nsCOMPtr<Element>       mBroadcaster;
+    nsCOMPtr<Element>       mListener;
+    // Note if mAttrName isn't used, this is the name of the attr, otherwise
+    // this is the value of the attribute.
+    nsString                mAttr;
+    RefPtr<nsAtom>       mAttrName;
+    bool                    mSetAttr;
+    bool                    mNeedsAttrChange;
+
+    class Comparator {
+      public:
+        static bool Equals(const nsDelayedBroadcastUpdate& a, const nsDelayedBroadcastUpdate& b) {
+          return a.mBroadcaster == b.mBroadcaster && a.mListener == b.mListener && a.mAttrName == b.mAttrName;
+        }
+    };
+  };
+  nsTArray<nsDelayedBroadcastUpdate> mDelayedBroadcasters;
+  nsTArray<nsDelayedBroadcastUpdate> mDelayedAttrChangeBroadcasts;
+  bool                               mHandlingDelayedAttrChange;
+  bool                               mHandlingDelayedBroadcasters;
+
+private:
+  ~XULBroadcastManager();
+
+
+};
+
+} // namespace dom
+} // namespace mozilla
+
+
+#endif // mozilla_dom_XULBroadcastManager_h
--- a/dom/xul/XULDocument.cpp
+++ b/dom/xul/XULDocument.cpp
@@ -112,29 +112,16 @@ static NS_DEFINE_CID(kParserCID,        
 // Statics
 //
 
 int32_t XULDocument::gRefCnt = 0;
 
 LazyLogModule XULDocument::gXULLog("XULDocument");
 
 //----------------------------------------------------------------------
-
-struct BroadcastListener {
-    nsWeakPtr mListener;
-    RefPtr<nsAtom> mAttribute;
-};
-
-struct BroadcasterMapEntry : public PLDHashEntryHdr
-{
-    Element* mBroadcaster;  // [WEAK]
-    nsTArray<BroadcastListener*> mListeners;  // [OWNING] of BroadcastListener objects
-};
-
-//----------------------------------------------------------------------
 //
 // ctors & dtors
 //
 
 namespace mozilla {
 namespace dom {
 
 XULDocument::XULDocument(void)
@@ -144,20 +131,17 @@ XULDocument::XULDocument(void)
       mIsWritingFastLoad(false),
       mDocumentLoaded(false),
       mStillWalking(false),
       mPendingSheets(0),
       mCurrentScriptProto(nullptr),
       mOffThreadCompiling(false),
       mOffThreadCompileStringBuf(nullptr),
       mOffThreadCompileStringLength(0),
-      mBroadcasterMap(nullptr),
-      mInitialLayoutComplete(false),
-      mHandlingDelayedAttrChange(false),
-      mHandlingDelayedBroadcasters(false)
+      mInitialLayoutComplete(false)
 {
     // Override the default in nsDocument
     mCharacterSet = UTF_8_ENCODING;
 
     mDefaultElementType = kNameSpaceID_XUL;
     mType = eXUL;
 
     mDelayFrameLoaderInitialization = true;
@@ -165,19 +149,16 @@ XULDocument::XULDocument(void)
     mAllowXULXBL = eTriTrue;
 }
 
 XULDocument::~XULDocument()
 {
     NS_ASSERTION(mNextSrcLoadWaiter == nullptr,
         "unreferenced document still waiting for script source to load?");
 
-    // Destroy our broadcaster map.
-    delete mBroadcasterMap;
-
     Preferences::UnregisterCallback(XULDocument::DirectionChanged,
                                     "intl.uidirection", this);
 
     if (mOffThreadCompileStringBuf) {
       js_free(mOffThreadCompileStringBuf);
     }
 }
 
@@ -437,286 +418,16 @@ XULDocument::OnPrototypeLoadDone(bool aR
     if (NS_FAILED(rv)) return rv;
 
     if (aResumeWalk) {
         rv = ResumeWalk();
     }
     return rv;
 }
 
-static void
-ClearBroadcasterMapEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry)
-{
-    BroadcasterMapEntry* entry =
-        static_cast<BroadcasterMapEntry*>(aEntry);
-    for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
-        delete entry->mListeners[i];
-    }
-    entry->mListeners.Clear();
-
-    // N.B. that we need to manually run the dtor because we
-    // constructed the nsTArray object in-place.
-    entry->mListeners.~nsTArray<BroadcastListener*>();
-}
-
-static bool
-CanBroadcast(int32_t aNameSpaceID, nsAtom* aAttribute)
-{
-    // Don't push changes to the |id|, |persist|, |command| or
-    // |observes| attribute.
-    if (aNameSpaceID == kNameSpaceID_None) {
-        if ((aAttribute == nsGkAtoms::id) ||
-            (aAttribute == nsGkAtoms::persist) ||
-            (aAttribute == nsGkAtoms::command) ||
-            (aAttribute == nsGkAtoms::observes)) {
-            return false;
-        }
-    }
-    return true;
-}
-
-struct nsAttrNameInfo
-{
-  nsAttrNameInfo(int32_t aNamespaceID, nsAtom* aName, nsAtom* aPrefix) :
-    mNamespaceID(aNamespaceID), mName(aName), mPrefix(aPrefix) {}
-  nsAttrNameInfo(const nsAttrNameInfo& aOther) :
-    mNamespaceID(aOther.mNamespaceID), mName(aOther.mName),
-    mPrefix(aOther.mPrefix) {}
-  int32_t           mNamespaceID;
-  RefPtr<nsAtom> mName;
-  RefPtr<nsAtom> mPrefix;
-};
-
-void
-XULDocument::SynchronizeBroadcastListener(Element *aBroadcaster,
-                                          Element *aListener,
-                                          const nsAString &aAttr)
-{
-    if (!nsContentUtils::IsSafeToRunScript()) {
-        nsDelayedBroadcastUpdate delayedUpdate(aBroadcaster, aListener,
-                                               aAttr);
-        mDelayedBroadcasters.AppendElement(delayedUpdate);
-        MaybeBroadcast();
-        return;
-    }
-    bool notify = mDocumentLoaded || mHandlingDelayedBroadcasters;
-
-    if (aAttr.EqualsLiteral("*")) {
-        uint32_t count = aBroadcaster->GetAttrCount();
-        nsTArray<nsAttrNameInfo> attributes(count);
-        for (uint32_t i = 0; i < count; ++i) {
-            const nsAttrName* attrName = aBroadcaster->GetAttrNameAt(i);
-            int32_t nameSpaceID = attrName->NamespaceID();
-            nsAtom* name = attrName->LocalName();
-
-            // _Don't_ push the |id|, |ref|, or |persist| attribute's value!
-            if (! CanBroadcast(nameSpaceID, name))
-                continue;
-
-            attributes.AppendElement(nsAttrNameInfo(nameSpaceID, name,
-                                                    attrName->GetPrefix()));
-        }
-
-        count = attributes.Length();
-        while (count-- > 0) {
-            int32_t nameSpaceID = attributes[count].mNamespaceID;
-            nsAtom* name = attributes[count].mName;
-            nsAutoString value;
-            if (aBroadcaster->GetAttr(nameSpaceID, name, value)) {
-              aListener->SetAttr(nameSpaceID, name, attributes[count].mPrefix,
-                                 value, notify);
-            }
-
-#if 0
-            // XXX we don't fire the |onbroadcast| handler during
-            // initial hookup: doing so would potentially run the
-            // |onbroadcast| handler before the |onload| handler,
-            // which could define JS properties that mask XBL
-            // properties, etc.
-            ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name);
-#endif
-        }
-    }
-    else {
-        // Find out if the attribute is even present at all.
-        RefPtr<nsAtom> name = NS_Atomize(aAttr);
-
-        nsAutoString value;
-        if (aBroadcaster->GetAttr(kNameSpaceID_None, name, value)) {
-            aListener->SetAttr(kNameSpaceID_None, name, value, notify);
-        } else {
-            aListener->UnsetAttr(kNameSpaceID_None, name, notify);
-        }
-
-#if 0
-        // XXX we don't fire the |onbroadcast| handler during initial
-        // hookup: doing so would potentially run the |onbroadcast|
-        // handler before the |onload| handler, which could define JS
-        // properties that mask XBL properties, etc.
-        ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name);
-#endif
-    }
-}
-
-void
-XULDocument::AddBroadcastListenerFor(Element& aBroadcaster, Element& aListener,
-                                     const nsAString& aAttr, ErrorResult& aRv)
-{
-    nsresult rv =
-        nsContentUtils::CheckSameOrigin(this, &aBroadcaster);
-
-    if (NS_FAILED(rv)) {
-        aRv.Throw(rv);
-        return;
-    }
-
-    rv = nsContentUtils::CheckSameOrigin(this, &aListener);
-
-    if (NS_FAILED(rv)) {
-        aRv.Throw(rv);
-        return;
-    }
-
-    static const PLDHashTableOps gOps = {
-        PLDHashTable::HashVoidPtrKeyStub,
-        PLDHashTable::MatchEntryStub,
-        PLDHashTable::MoveEntryStub,
-        ClearBroadcasterMapEntry,
-        nullptr
-    };
-
-    if (! mBroadcasterMap) {
-        mBroadcasterMap = new PLDHashTable(&gOps, sizeof(BroadcasterMapEntry));
-    }
-
-    auto entry = static_cast<BroadcasterMapEntry*>
-                            (mBroadcasterMap->Search(&aBroadcaster));
-    if (!entry) {
-        entry = static_cast<BroadcasterMapEntry*>
-                           (mBroadcasterMap->Add(&aBroadcaster, fallible));
-
-        if (! entry) {
-            aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
-            return;
-        }
-
-        entry->mBroadcaster = &aBroadcaster;
-
-        // N.B. placement new to construct the nsTArray object in-place
-        new (&entry->mListeners) nsTArray<BroadcastListener*>();
-    }
-
-    // Only add the listener if it's not there already!
-    RefPtr<nsAtom> attr = NS_Atomize(aAttr);
-
-    for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
-        BroadcastListener* bl = entry->mListeners[i];
-        nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener);
-
-        if (blListener == &aListener && bl->mAttribute == attr)
-            return;
-    }
-
-    BroadcastListener* bl = new BroadcastListener;
-    bl->mListener  = do_GetWeakReference(&aListener);
-    bl->mAttribute = attr;
-
-    entry->mListeners.AppendElement(bl);
-
-    SynchronizeBroadcastListener(&aBroadcaster, &aListener, aAttr);
-}
-
-void
-XULDocument::RemoveBroadcastListenerFor(Element& aBroadcaster,
-                                        Element& aListener,
-                                        const nsAString& aAttr)
-{
-    // If we haven't added any broadcast listeners, then there sure
-    // aren't any to remove.
-    if (! mBroadcasterMap)
-        return;
-
-    auto entry = static_cast<BroadcasterMapEntry*>
-                            (mBroadcasterMap->Search(&aBroadcaster));
-    if (entry) {
-        RefPtr<nsAtom> attr = NS_Atomize(aAttr);
-        for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
-            BroadcastListener* bl = entry->mListeners[i];
-            nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener);
-
-            if (blListener == &aListener && bl->mAttribute == attr) {
-                entry->mListeners.RemoveElementAt(i);
-                delete bl;
-
-                if (entry->mListeners.IsEmpty())
-                    mBroadcasterMap->RemoveEntry(entry);
-
-                break;
-            }
-        }
-    }
-}
-
-nsresult
-XULDocument::ExecuteOnBroadcastHandlerFor(Element* aBroadcaster,
-                                          Element* aListener,
-                                          nsAtom* aAttr)
-{
-    // Now we execute the onchange handler in the context of the
-    // observer. We need to find the observer in order to
-    // execute the handler.
-
-    for (nsIContent* child = aListener->GetFirstChild();
-         child;
-         child = child->GetNextSibling()) {
-
-        // Look for an <observes> element beneath the listener. This
-        // ought to have an |element| attribute that refers to
-        // aBroadcaster, and an |attribute| element that tells us what
-        // attriubtes we're listening for.
-        if (!child->IsXULElement(nsGkAtoms::observes))
-            continue;
-
-        // Is this the element that was listening to us?
-        nsAutoString listeningToID;
-        child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::element, listeningToID);
-
-        nsAutoString broadcasterID;
-        aBroadcaster->GetAttr(kNameSpaceID_None, nsGkAtoms::id, broadcasterID);
-
-        if (listeningToID != broadcasterID)
-            continue;
-
-        // We are observing the broadcaster, but is this the right
-        // attribute?
-        nsAutoString listeningToAttribute;
-        child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute,
-                                    listeningToAttribute);
-
-        if (!aAttr->Equals(listeningToAttribute) &&
-            !listeningToAttribute.EqualsLiteral("*")) {
-            continue;
-        }
-
-        // This is the right <observes> element. Execute the
-        // |onbroadcast| event handler
-        WidgetEvent event(true, eXULBroadcast);
-
-        RefPtr<nsPresContext> presContext = GetPresContext();
-        if (presContext) {
-          // Handle the DOM event
-          nsEventStatus status = nsEventStatus_eIgnore;
-          EventDispatcher::Dispatch(child, presContext, &event, nullptr,
-                                    &status);
-        }
-    }
-
-    return NS_OK;
-}
-
 static bool
 ShouldPersistAttribute(Element* aElement, nsAtom* aAttribute)
 {
     if (aElement->IsXULElement(nsGkAtoms::window)) {
         // This is not an element of the top document, its owner is
         // not an nsXULWindow. Persist it.
         if (aElement->OwnerDoc()->GetParentDocument()) {
             return true;
@@ -739,72 +450,16 @@ XULDocument::AttributeChanged(Element* a
                               nsAtom* aAttribute, int32_t aModType,
                               const nsAttrValue* aOldValue)
 {
     NS_ASSERTION(aElement->OwnerDoc() == this, "unexpected doc");
 
     // Might not need this, but be safe for now.
     nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
 
-    // Synchronize broadcast listeners
-    if (mBroadcasterMap &&
-        CanBroadcast(aNameSpaceID, aAttribute)) {
-        auto entry = static_cast<BroadcasterMapEntry*>
-                                (mBroadcasterMap->Search(aElement));
-
-        if (entry) {
-            // We've got listeners: push the value.
-            nsAutoString value;
-            bool attrSet = aElement->GetAttr(kNameSpaceID_None, aAttribute, value);
-
-            for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
-                BroadcastListener* bl = entry->mListeners[i];
-                if ((bl->mAttribute == aAttribute) ||
-                    (bl->mAttribute == nsGkAtoms::_asterisk)) {
-                    nsCOMPtr<Element> listenerEl
-                        = do_QueryReferent(bl->mListener);
-                    if (listenerEl) {
-                        nsAutoString currentValue;
-                        bool hasAttr = listenerEl->GetAttr(kNameSpaceID_None,
-                                                           aAttribute,
-                                                           currentValue);
-                        // We need to update listener only if we're
-                        // (1) removing an existing attribute,
-                        // (2) adding a new attribute or
-                        // (3) changing the value of an attribute.
-                        bool needsAttrChange =
-                            attrSet != hasAttr || !value.Equals(currentValue);
-                        nsDelayedBroadcastUpdate delayedUpdate(aElement,
-                                                               listenerEl,
-                                                               aAttribute,
-                                                               value,
-                                                               attrSet,
-                                                               needsAttrChange);
-
-                        size_t index =
-                            mDelayedAttrChangeBroadcasts.IndexOf(delayedUpdate,
-                                0, nsDelayedBroadcastUpdate::Comparator());
-                        if (index != mDelayedAttrChangeBroadcasts.NoIndex) {
-                            if (mHandlingDelayedAttrChange) {
-                                NS_WARNING("Broadcasting loop!");
-                                continue;
-                            }
-                            mDelayedAttrChangeBroadcasts.RemoveElementAt(index);
-                        }
-
-                        mDelayedAttrChangeBroadcasts.AppendElement(delayedUpdate);
-                    }
-                }
-            }
-        }
-    }
-
-    // checks for modifications in broadcasters
-    CheckBroadcasterHookup(aElement);
-
     // See if there is anything we need to persist in the localstore.
     //
     // XXX Namespace handling broken :-(
     nsAutoString persist;
     aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::persist, persist);
     // Persistence of attributes of xul:window is handled in nsXULWindow.
     if (ShouldPersistAttribute(aElement, aAttribute) && !persist.IsEmpty() &&
         // XXXldb This should check that it's a token, not just a substring.
@@ -915,32 +570,26 @@ XULDocument::Persist(Element* aElement, 
     mLocalStore->SetValue(uri, id, attrstr, valuestr);
 }
 
 nsresult
 XULDocument::AddElementToDocumentPre(Element* aElement)
 {
     // Do a bunch of work that's necessary when an element gets added
     // to the XUL Document.
-    nsresult rv;
 
     // 1. Add the element to the id map, since it seems this can be
     // called when creating elements from prototypes.
     nsAtom* id = aElement->GetID();
     if (id) {
         // FIXME: Shouldn't BindToTree take care of this?
         nsAutoScriptBlocker scriptBlocker;
         AddToIdTable(aElement, id);
     }
 
-    // 2. Check for a broadcaster hookup attribute, in which case
-    // we'll hook the node up as a listener on a broadcaster.
-    rv = CheckBroadcasterHookup(aElement);
-    if (NS_FAILED(rv)) return rv;
-
     return NS_OK;
 }
 
 nsresult
 XULDocument::AddElementToDocumentPost(Element* aElement)
 {
     if (aElement == GetRootElement()) {
         ResetDocumentDirection();
@@ -1010,26 +659,16 @@ XULDocument::RemoveSubtreeFromDocument(n
     // AddElementToDocumentPre().
     nsAtom* id = aElement->GetID();
     if (id) {
         // FIXME: Shouldn't UnbindFromTree take care of this?
         nsAutoScriptBlocker scriptBlocker;
         RemoveFromIdTable(aElement, id);
     }
 
-    // Remove the element from our broadcaster map, since it is no longer
-    // in the document.
-    nsCOMPtr<Element> broadcaster, listener;
-    nsAutoString attribute, broadcasterID;
-    rv = FindBroadcaster(aElement, getter_AddRefs(listener),
-                         broadcasterID, attribute, getter_AddRefs(broadcaster));
-    if (rv == NS_FINDBROADCASTER_FOUND) {
-        RemoveBroadcastListenerFor(*broadcaster, *listener, attribute);
-    }
-
     return NS_OK;
 }
 
 //----------------------------------------------------------------------
 //
 // nsINode interface
 //
 
@@ -1797,75 +1436,19 @@ XULDocument::StyleSheetLoaded(StyleSheet
             return DoneWalking();
         }
     }
 
     return NS_OK;
 }
 
 void
-XULDocument::MaybeBroadcast()
-{
-    // Only broadcast when not in an update and when safe to run scripts.
-    if (mUpdateNestLevel == 0 &&
-        (mDelayedAttrChangeBroadcasts.Length() ||
-         mDelayedBroadcasters.Length())) {
-        if (!nsContentUtils::IsSafeToRunScript()) {
-            if (!mInDestructor) {
-              nsContentUtils::AddScriptRunner(
-                NewRunnableMethod("dom::XULDocument::MaybeBroadcast",
-                                  this,
-                                  &XULDocument::MaybeBroadcast));
-            }
-            return;
-        }
-        if (!mHandlingDelayedAttrChange) {
-            mHandlingDelayedAttrChange = true;
-            for (uint32_t i = 0; i < mDelayedAttrChangeBroadcasts.Length(); ++i) {
-                nsAtom* attrName = mDelayedAttrChangeBroadcasts[i].mAttrName;
-                if (mDelayedAttrChangeBroadcasts[i].mNeedsAttrChange) {
-                    nsCOMPtr<Element> listener = mDelayedAttrChangeBroadcasts[i].mListener;
-                    const nsString& value = mDelayedAttrChangeBroadcasts[i].mAttr;
-                    if (mDelayedAttrChangeBroadcasts[i].mSetAttr) {
-                        listener->SetAttr(kNameSpaceID_None, attrName, value,
-                                          true);
-                    } else {
-                        listener->UnsetAttr(kNameSpaceID_None, attrName,
-                                            true);
-                    }
-                }
-                ExecuteOnBroadcastHandlerFor(mDelayedAttrChangeBroadcasts[i].mBroadcaster,
-                                             mDelayedAttrChangeBroadcasts[i].mListener,
-                                             attrName);
-            }
-            mDelayedAttrChangeBroadcasts.Clear();
-            mHandlingDelayedAttrChange = false;
-        }
-
-        uint32_t length = mDelayedBroadcasters.Length();
-        if (length) {
-            bool oldValue = mHandlingDelayedBroadcasters;
-            mHandlingDelayedBroadcasters = true;
-            nsTArray<nsDelayedBroadcastUpdate> delayedBroadcasters;
-            mDelayedBroadcasters.SwapElements(delayedBroadcasters);
-            for (uint32_t i = 0; i < length; ++i) {
-                SynchronizeBroadcastListener(delayedBroadcasters[i].mBroadcaster,
-                                             delayedBroadcasters[i].mListener,
-                                             delayedBroadcasters[i].mAttr);
-            }
-            mHandlingDelayedBroadcasters = oldValue;
-        }
-    }
-}
-
-void
 XULDocument::EndUpdate()
 {
     XMLDocument::EndUpdate();
-    MaybeBroadcast();
 }
 
 nsresult
 XULDocument::LoadScript(nsXULPrototypeScript* aScriptProto, bool* aBlock)
 {
     // Load a transcluded script
     nsresult rv;
 
@@ -2243,151 +1826,16 @@ XULDocument::AddAttributes(nsXULPrototyp
                                valueStr,
                                false);
         if (NS_FAILED(rv)) return rv;
     }
 
     return NS_OK;
 }
 
-
-//----------------------------------------------------------------------
-
-nsresult
-XULDocument::FindBroadcaster(Element* aElement,
-                             Element** aListener,
-                             nsString& aBroadcasterID,
-                             nsString& aAttribute,
-                             Element** aBroadcaster)
-{
-    mozilla::dom::NodeInfo *ni = aElement->NodeInfo();
-    *aListener = nullptr;
-    *aBroadcaster = nullptr;
-
-    if (ni->Equals(nsGkAtoms::observes, kNameSpaceID_XUL)) {
-        // It's an <observes> element, which means that the actual
-        // listener is the _parent_ node. This element should have an
-        // 'element' attribute that specifies the ID of the
-        // broadcaster element, and an 'attribute' element, which
-        // specifies the name of the attribute to observe.
-        nsIContent* parent = aElement->GetParent();
-        if (!parent) {
-             // <observes> is the root element
-            return NS_FINDBROADCASTER_NOT_FOUND;
-        }
-
-        *aListener = Element::FromNode(parent);
-        NS_IF_ADDREF(*aListener);
-
-        aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::element, aBroadcasterID);
-        if (aBroadcasterID.IsEmpty()) {
-            return NS_FINDBROADCASTER_NOT_FOUND;
-        }
-        aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute, aAttribute);
-    }
-    else {
-        // It's a generic element, which means that we'll use the
-        // value of the 'observes' attribute to determine the ID of
-        // the broadcaster element, and we'll watch _all_ of its
-        // values.
-        aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::observes, aBroadcasterID);
-
-        // Bail if there's no aBroadcasterID
-        if (aBroadcasterID.IsEmpty()) {
-            // Try the command attribute next.
-            aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::command, aBroadcasterID);
-            if (!aBroadcasterID.IsEmpty()) {
-                // We've got something in the command attribute.  We
-                // only treat this as a normal broadcaster if we are
-                // not a menuitem or a key.
-
-                if (ni->Equals(nsGkAtoms::menuitem, kNameSpaceID_XUL) ||
-                    ni->Equals(nsGkAtoms::key, kNameSpaceID_XUL)) {
-                return NS_FINDBROADCASTER_NOT_FOUND;
-              }
-            }
-            else {
-              return NS_FINDBROADCASTER_NOT_FOUND;
-            }
-        }
-
-        *aListener = aElement;
-        NS_ADDREF(*aListener);
-
-        aAttribute.Assign('*');
-    }
-
-    // Make sure we got a valid listener.
-    NS_ENSURE_TRUE(*aListener, NS_ERROR_UNEXPECTED);
-
-    // Try to find the broadcaster element in the document.
-    *aBroadcaster = GetElementById(aBroadcasterID);
-
-    // The broadcaster element is missing.
-    if (! *aBroadcaster) {
-        return NS_FINDBROADCASTER_NOT_FOUND;
-    }
-
-    NS_ADDREF(*aBroadcaster);
-
-    return NS_FINDBROADCASTER_FOUND;
-}
-
-nsresult
-XULDocument::CheckBroadcasterHookup(Element* aElement)
-{
-    // Resolve a broadcaster hookup. Look at the element that we're
-    // trying to resolve: it could be an '<observes>' element, or just
-    // a vanilla element with an 'observes' attribute on it.
-    nsresult rv;
-
-    nsCOMPtr<Element> listener;
-    nsAutoString broadcasterID;
-    nsAutoString attribute;
-    nsCOMPtr<Element> broadcaster;
-
-    rv = FindBroadcaster(aElement, getter_AddRefs(listener),
-                         broadcasterID, attribute, getter_AddRefs(broadcaster));
-    switch (rv) {
-        case NS_FINDBROADCASTER_NOT_FOUND:
-            return NS_OK;
-        case NS_FINDBROADCASTER_FOUND:
-            break;
-        default:
-            return rv;
-    }
-
-    NS_ENSURE_ARG(broadcaster && listener);
-    ErrorResult domRv;
-    AddBroadcastListenerFor(*broadcaster, *listener, attribute, domRv);
-    if (domRv.Failed()) {
-        return domRv.StealNSResult();
-    }
-
-    // Tell the world we succeeded
-    if (MOZ_LOG_TEST(gXULLog, LogLevel::Debug)) {
-        nsCOMPtr<nsIContent> content = listener;
-        NS_ASSERTION(content != nullptr, "not an nsIContent");
-        if (!content) {
-            return rv;
-        }
-
-        nsAutoCString attributeC,broadcasteridC;
-        LossyCopyUTF16toASCII(attribute, attributeC);
-        LossyCopyUTF16toASCII(broadcasterID, broadcasteridC);
-        MOZ_LOG(gXULLog, LogLevel::Debug,
-               ("xul: broadcaster hookup <%s attribute='%s'> to %s",
-                nsAtomCString(content->NodeInfo()->NameAtom()).get(),
-                attributeC.get(),
-                broadcasteridC.get()));
-    }
-
-    return NS_OK;
-}
-
 //----------------------------------------------------------------------
 //
 // CachedChromeStreamListener
 //
 
 XULDocument::CachedChromeStreamListener::CachedChromeStreamListener(XULDocument* aDocument, bool aProtoLoaded)
     : mDocument(aDocument),
       mProtoLoaded(aProtoLoaded)
--- a/dom/xul/XULDocument.h
+++ b/dom/xul/XULDocument.h
@@ -123,19 +123,16 @@ public:
     void ResetDocumentDirection();
 
     NS_IMETHOD OnScriptCompileComplete(JSScript* aScript, nsresult aStatus) override;
 
     NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(XULDocument, XMLDocument)
 
     void TraceProtos(JSTracer* aTrc);
 
-    void RemoveBroadcastListenerFor(Element& aBroadcaster, Element& aListener,
-                                    const nsAString& aAttr);
-
 protected:
     virtual ~XULDocument();
 
     // Implementation methods
     friend nsresult
     (::NS_NewXULDocument(nsIDocument** aResult));
 
     nsresult Init(void) override;
@@ -159,24 +156,16 @@ protected:
                                                  nsCOMArray<Element>& aElements);
 
     nsresult
     AddElementToDocumentPre(Element* aElement);
 
     nsresult
     AddElementToDocumentPost(Element* aElement);
 
-    void AddBroadcastListenerFor(Element& aBroadcaster, Element& aListener,
-                                 const nsAString& aAttr, ErrorResult& aRv);
-
-    nsresult
-    ExecuteOnBroadcastHandlerFor(Element* aBroadcaster,
-                                 Element* aListener,
-                                 nsAtom* aAttr);
-
     static void DirectionChanged(const char* aPrefName, XULDocument* aData);
 
     // pseudo constants
     static int32_t gRefCnt;
 
     static LazyLogModule gXULLog;
 
     void
@@ -287,35 +276,16 @@ protected:
      * If the current transcluded script is being compiled off thread, the
      * source for that script.
      */
     char16_t* mOffThreadCompileStringBuf;
     size_t mOffThreadCompileStringLength;
 
 
 protected:
-    // The out params of FindBroadcaster only have values that make sense when
-    // the method returns NS_FINDBROADCASTER_FOUND.  In all other cases, the
-    // values of the out params should not be relied on (though *aListener and
-    // *aBroadcaster do need to be released if non-null, of course).
-    nsresult
-    FindBroadcaster(Element* aElement,
-                    Element** aListener,
-                    nsString& aBroadcasterID,
-                    nsString& aAttribute,
-                    Element** aBroadcaster);
-
-    nsresult
-    CheckBroadcasterHookup(Element* aElement);
-
-    void
-    SynchronizeBroadcastListener(Element *aBroadcaster,
-                                 Element *aListener,
-                                 const nsAString &aAttr);
-
     /**
      * The current prototype that we are walking to construct the
      * content model.
      */
     RefPtr<nsXULPrototypeDocument> mCurrentPrototype;
 
     /**
      * Owning references to all of the prototype documents that were
@@ -377,70 +347,18 @@ protected:
 
         NS_DECL_ISUPPORTS
         NS_DECL_NSIREQUESTOBSERVER
         NS_DECL_NSISTREAMLISTENER
     };
 
     friend class CachedChromeStreamListener;
 
-    /**
-     * A map from a broadcaster element to a list of listener elements.
-     */
-    PLDHashTable* mBroadcasterMap;
-
     bool mInitialLayoutComplete;
 
-    class nsDelayedBroadcastUpdate
-    {
-    public:
-      nsDelayedBroadcastUpdate(Element* aBroadcaster,
-                               Element* aListener,
-                               const nsAString &aAttr)
-      : mBroadcaster(aBroadcaster), mListener(aListener), mAttr(aAttr),
-        mSetAttr(false), mNeedsAttrChange(false) {}
-
-      nsDelayedBroadcastUpdate(Element* aBroadcaster,
-                               Element* aListener,
-                               nsAtom* aAttrName,
-                               const nsAString &aAttr,
-                               bool aSetAttr,
-                               bool aNeedsAttrChange)
-      : mBroadcaster(aBroadcaster), mListener(aListener), mAttr(aAttr),
-        mAttrName(aAttrName), mSetAttr(aSetAttr),
-        mNeedsAttrChange(aNeedsAttrChange) {}
-
-      nsDelayedBroadcastUpdate(const nsDelayedBroadcastUpdate& aOther)
-      : mBroadcaster(aOther.mBroadcaster), mListener(aOther.mListener),
-        mAttr(aOther.mAttr), mAttrName(aOther.mAttrName),
-        mSetAttr(aOther.mSetAttr), mNeedsAttrChange(aOther.mNeedsAttrChange) {}
-
-      nsCOMPtr<Element>       mBroadcaster;
-      nsCOMPtr<Element>       mListener;
-      // Note if mAttrName isn't used, this is the name of the attr, otherwise
-      // this is the value of the attribute.
-      nsString                mAttr;
-      RefPtr<nsAtom>       mAttrName;
-      bool                    mSetAttr;
-      bool                    mNeedsAttrChange;
-
-      class Comparator {
-        public:
-          static bool Equals(const nsDelayedBroadcastUpdate& a, const nsDelayedBroadcastUpdate& b) {
-            return a.mBroadcaster == b.mBroadcaster && a.mListener == b.mListener && a.mAttrName == b.mAttrName;
-          }
-      };
-    };
-
-    nsTArray<nsDelayedBroadcastUpdate> mDelayedBroadcasters;
-    nsTArray<nsDelayedBroadcastUpdate> mDelayedAttrChangeBroadcasts;
-    bool                               mHandlingDelayedAttrChange;
-    bool                               mHandlingDelayedBroadcasters;
-
-    void MaybeBroadcast();
 private:
     // helpers
 
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/xul/moz.build
+++ b/dom/xul/moz.build
@@ -17,16 +17,17 @@ MOCHITEST_CHROME_MANIFESTS += ['test/chr
 if CONFIG['MOZ_XUL']:
     EXPORTS += [
         'nsXULCommandDispatcher.h',
         'nsXULElement.h',
         'nsXULSortService.h',
     ]
 
     EXPORTS.mozilla.dom += [
+        'XULBroadcastManager.h',
         'XULFrameElement.h',
         'XULMenuElement.h',
         'XULPopupElement.h',
         'XULScrollElement.h',
         'XULTextElement.h',
         'XULTooltipElement.h',
     ]
 
@@ -34,16 +35,17 @@ if CONFIG['MOZ_XUL']:
         'nsXULCommandDispatcher.cpp',
         'nsXULContentSink.cpp',
         'nsXULContentUtils.cpp',
         'nsXULElement.cpp',
         'nsXULPopupListener.cpp',
         'nsXULPrototypeCache.cpp',
         'nsXULPrototypeDocument.cpp',
         'nsXULSortService.cpp',
+        'XULBroadcastManager.cpp',
         'XULDocument.cpp',
         'XULFrameElement.cpp',
         'XULMenuElement.cpp',
         'XULPopupElement.cpp',
         'XULScrollElement.cpp',
         'XULTextElement.cpp',
         'XULTooltipElement.cpp',
     ]
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -76,16 +76,17 @@
 #include "nsLayoutUtils.h"
 #include "XULFrameElement.h"
 #include "XULMenuElement.h"
 #include "XULPopupElement.h"
 #include "XULScrollElement.h"
 
 #include "mozilla/dom/XULElementBinding.h"
 #include "mozilla/dom/BoxObject.h"
+#include "mozilla/dom/XULBroadcastManager.h"
 #include "mozilla/dom/MouseEventBinding.h"
 #include "mozilla/dom/MutationEventBinding.h"
 #include "mozilla/dom/XULCommandEvent.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 #ifdef XUL_PROTOTYPE_ATTRIBUTE_METERING
@@ -749,30 +750,45 @@ nsXULElement::BindToTree(nsIDocument* aD
     // Create our XUL key listener and hook it up.
     nsXBLService::AttachGlobalKeyHandler(this);
   }
 
   if (doc && NeedTooltipSupport(*this)) {
       AddTooltipSupport();
   }
 
+  if (doc && XULBroadcastManager::MayNeedListener(*this)) {
+    if (!doc->HasXULBroadcastManager()) {
+      doc->InitializeXULBroadcastManager();
+    }
+    XULBroadcastManager* broadcastManager = doc->GetXULBroadcastManager();
+    broadcastManager->AddListener(this);
+  }
+
   return rv;
 }
 
 void
 nsXULElement::UnbindFromTree(bool aDeep, bool aNullParent)
 {
     if (NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) {
         nsXBLService::DetachGlobalKeyHandler(this);
     }
 
     if (NeedTooltipSupport(*this)) {
         RemoveTooltipSupport();
     }
 
+    nsIDocument* doc = GetComposedDoc();
+    if (doc && doc->HasXULBroadcastManager() &&
+        XULBroadcastManager::MayNeedListener(*this)) {
+        RefPtr<XULBroadcastManager> broadcastManager = doc->GetXULBroadcastManager();
+        broadcastManager->RemoveListener(this);
+    }
+
     // mControllers can own objects that are implemented
     // in JavaScript (such as some implementations of
     // nsIControllers.  These objects prevent their global
     // object's script object from being garbage collected,
     // which means JS continues to hold an owning reference
     // to the nsGlobalWindow, which owns the document,
     // which owns this content.  That's a cycle, so we break
     // it here.  (It might be better to break this by releasing
@@ -825,24 +841,28 @@ nsXULElement::BeforeSetAttr(int32_t aNam
         nsAutoString oldValue;
         if (GetAttr(aNamespaceID, aName, oldValue)) {
             UnregisterAccessKey(oldValue);
         }
     } else if (aNamespaceID == kNameSpaceID_None &&
                (aName == nsGkAtoms::command || aName == nsGkAtoms::observes) &&
                IsInUncomposedDoc()) {
 //         XXX sXBL/XBL2 issue! Owner or current document?
+        // XXX Why does this not also remove broadcast listeners if the
+        // "element" attribute was changed on an <observer>?
         nsAutoString oldValue;
         GetAttr(kNameSpaceID_None, nsGkAtoms::observes, oldValue);
         if (oldValue.IsEmpty()) {
           GetAttr(kNameSpaceID_None, nsGkAtoms::command, oldValue);
         }
 
-        if (!oldValue.IsEmpty()) {
-          RemoveBroadcaster(oldValue);
+        nsIDocument* doc = GetUncomposedDoc();
+        if (!oldValue.IsEmpty() && doc->HasXULBroadcastManager()) {
+            RefPtr<XULBroadcastManager> broadcastManager = doc->GetXULBroadcastManager();
+            broadcastManager->RemoveListener(this);
         }
     } else if (aNamespaceID == kNameSpaceID_None &&
                aValue &&
                mNodeInfo->Equals(nsGkAtoms::window) &&
                aName == nsGkAtoms::chromemargin) {
       nsAttrValue attrValue;
       // Make sure the margin format is valid first
       if (!attrValue.ParseIntMarginValue(aValue->String())) {
@@ -960,16 +980,29 @@ nsXULElement::AfterSetAttr(int32_t aName
                 !NodeInfo()->Equals(nsGkAtoms::treechildren)) {
                 if (aValue) {
                     AddTooltipSupport();
                 } else {
                     RemoveTooltipSupport();
                 }
             }
         }
+        nsIDocument* doc = GetComposedDoc();
+        if (doc && doc->HasXULBroadcastManager()) {
+            RefPtr<XULBroadcastManager> broadcastManager = doc->GetXULBroadcastManager();
+            broadcastManager->AttributeChanged(this, aNamespaceID, aName);
+        }
+        if (doc && XULBroadcastManager::MayNeedListener(*this)) {
+            if (!doc->HasXULBroadcastManager()) {
+                doc->InitializeXULBroadcastManager();
+            }
+            XULBroadcastManager* broadcastManager = doc->GetXULBroadcastManager();
+            broadcastManager->AddListener(this);
+        }
+
         // XXX need to check if they're changing an event handler: if
         // so, then we need to unhook the old one.  Or something.
     }
 
     return nsStyledElement::AfterSetAttr(aNamespaceID, aName,
                                          aValue, aOldValue, aSubjectPrincipal, aNotify);
 }
 
@@ -1008,29 +1041,16 @@ nsXULElement::ParseAttribute(int32_t aNa
         // Fall back to parsing as atom for short values
         aResult.ParseStringOrAtom(aValue);
     }
 
     return true;
 }
 
 void
-nsXULElement::RemoveBroadcaster(const nsAString & broadcasterId)
-{
-    nsIDocument* doc = OwnerDoc();
-    if (!doc->IsXULDocument()) {
-      return;
-    }
-    if (Element* broadcaster = doc->GetElementById(broadcasterId)) {
-        doc->AsXULDocument()->RemoveBroadcastListenerFor(
-           *broadcaster, *this, NS_LITERAL_STRING("*"));
-    }
-}
-
-void
 nsXULElement::DestroyContent()
 {
     nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
     if (slots) {
         slots->mControllers = nullptr;
     }
 
     nsStyledElement::DestroyContent();
--- a/dom/xul/nsXULElement.h
+++ b/dom/xul/nsXULElement.h
@@ -688,18 +688,16 @@ protected:
     nsresult HideWindowChrome(bool aShouldHide);
     void SetChromeMargins(const nsAttrValue* aValue);
     void ResetChromeMargins();
 
     void SetDrawsInTitlebar(bool aState);
     void SetDrawsTitle(bool aState);
     void UpdateBrightTitlebarForeground(nsIDocument* aDocument);
 
-    void RemoveBroadcaster(const nsAString & broadcasterId);
-
 protected:
     void AddTooltipSupport();
     void RemoveTooltipSupport();
 
     // Internal accessor. This shadows the 'Slots', and returns
     // appropriate value.
     nsIControllers *Controllers() {
       nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
--- a/dom/xul/test/test_bug445177.xul
+++ b/dom/xul/test/test_bug445177.xul
@@ -11,18 +11,18 @@ https://bugzilla.mozilla.org/show_bug.cg
   xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
 
 <body id="body" xmlns="http://www.w3.org/1999/xhtml">
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=445177">Mozilla Bug 445177</a>
 
 
-<hbox id="b1" value="foo"/>
-<hbox id="o1" observes="b1"/>
+<xul:hbox id="b1" value="foo"/>
+<xul:hbox id="o1" observes="b1"/>
 
 <pre id="test">
   <script class="testbody" type="text/javascript">
 <![CDATA[
   SimpleTest.waitForExplicitFinish();
   function do_test() {
     var b1 = document.getElementById("b1");
     var o1 = document.getElementById("o1");
--- a/layout/generic/nsFlexContainerFrame.cpp
+++ b/layout/generic/nsFlexContainerFrame.cpp
@@ -1074,17 +1074,18 @@ public:
 
   void PositionItemsInCrossAxis(nscoord aLineStartPosition,
                                 const FlexboxAxisTracker& aAxisTracker);
 
   friend class AutoFlexLineListClearer; // (needs access to mItems)
 
 private:
   // Helpers for ResolveFlexibleLengths():
-  void FreezeItemsEarly(bool aIsUsingFlexGrow);
+  void FreezeItemsEarly(bool aIsUsingFlexGrow,
+                        ComputedFlexLineInfo* aLineInfo);
 
   void FreezeOrRestoreEachFlexibleSize(const nscoord aTotalViolation,
                                        bool aIsFinalIteration);
 
   LinkedList<FlexItem> mItems; // Linked list of this line's flex items.
 
   uint32_t mNumItems; // Number of FlexItems in this line (in |mItems|).
                       // (Shouldn't change after GenerateFlexLines finishes
@@ -2498,17 +2499,18 @@ nsFlexContainerFrame::BuildDisplayList(n
   for (; !iter.AtEnd(); iter.Next()) {
     nsIFrame* childFrame = *iter;
     BuildDisplayListForChild(aBuilder, childFrame, childLists,
                              GetDisplayFlagsForFlexItem(childFrame));
   }
 }
 
 void
-FlexLine::FreezeItemsEarly(bool aIsUsingFlexGrow)
+FlexLine::FreezeItemsEarly(bool aIsUsingFlexGrow,
+                           ComputedFlexLineInfo* aLineInfo)
 {
   // After we've established the type of flexing we're doing (growing vs.
   // shrinking), and before we try to flex any items, we freeze items that
   // obviously *can't* flex.
   //
   // Quoting the spec:
   //  # Freeze, setting its target main size to its hypothetical main size...
   //  #  - any item that has a flex factor of zero
@@ -2527,17 +2529,23 @@ FlexLine::FreezeItemsEarly(bool aIsUsing
   uint32_t numUnfrozenItemsToBeSeen = mNumItems - mNumFrozenItems;
   for (FlexItem* item = mItems.getFirst();
        numUnfrozenItemsToBeSeen > 0; item = item->getNext()) {
     MOZ_ASSERT(item, "numUnfrozenItemsToBeSeen says items remain to be seen");
 
     if (!item->IsFrozen()) {
       numUnfrozenItemsToBeSeen--;
       bool shouldFreeze = (0.0f == item->GetFlexFactor(aIsUsingFlexGrow));
-      if (!shouldFreeze) {
+      // NOTE: We skip the "could flex but base size out of range"
+      // early-freezing if flex devtools are active, so that we can let the
+      // first run of the main flex layout loop compute how much this item
+      // wants to flex. (This skipping shouldn't impact results, because
+      // any affected items will just immediately be caught & frozen as min/max
+      // violations in that first loop, and that'll trigger another loop.)
+      if (!shouldFreeze && !aLineInfo) {
         if (aIsUsingFlexGrow) {
           if (item->GetFlexBaseSize() > item->GetMainSize()) {
             shouldFreeze = true;
           }
         } else { // using flex-shrink
           if (item->GetFlexBaseSize() < item->GetMainSize()) {
             shouldFreeze = true;
           }
@@ -2613,23 +2621,36 @@ FlexLine::FreezeOrRestoreEachFlexibleSiz
 }
 
 void
 FlexLine::ResolveFlexibleLengths(nscoord aFlexContainerMainSize,
                                  ComputedFlexLineInfo* aLineInfo)
 {
   MOZ_LOG(gFlexContainerLog, LogLevel::Debug, ("ResolveFlexibleLengths\n"));
 
+  // Before we start resolving sizes: if we have an aLineInfo structure to fill
+  // out, we inform it of each item's base size, and we initialize the "delta"
+  // for each item to 0. (And if the flex algorithm wants to grow or shrink the
+  // item, we'll update this delta further down.)
+  if (aLineInfo) {
+    uint32_t itemIndex = 0;
+    for (FlexItem* item = mItems.getFirst(); item; item = item->getNext(),
+                                                   ++itemIndex) {
+      aLineInfo->mItems[itemIndex].mMainBaseSize = item->GetFlexBaseSize();
+      aLineInfo->mItems[itemIndex].mMainDeltaSize = 0;
+    }
+  }
+
   // Determine whether we're going to be growing or shrinking items.
   const bool isUsingFlexGrow =
     (mTotalOuterHypotheticalMainSize < aFlexContainerMainSize);
 
   // Do an "early freeze" for flex items that obviously can't flex in the
   // direction we've chosen:
-  FreezeItemsEarly(isUsingFlexGrow);
+  FreezeItemsEarly(isUsingFlexGrow, aLineInfo);
 
   if ((mNumFrozenItems == mNumItems) && !aLineInfo) {
     // All our items are frozen, so we have no flexible lengths to resolve,
     // and we aren't being asked to generate computed line info.
     return;
   }
   MOZ_ASSERT(!IsEmpty() || aLineInfo,
              "empty lines should take the early-return above");
@@ -2658,31 +2679,16 @@ FlexLine::ResolveFlexibleLengths(nscoord
     nscoord availableFreeSpace = spaceAvailableForFlexItemsContentBoxes;
     for (FlexItem* item = mItems.getFirst(); item; item = item->getNext()) {
       if (!item->IsFrozen()) {
         item->SetMainSize(item->GetFlexBaseSize());
       }
       availableFreeSpace -= item->GetMainSize();
     }
 
-    // If we have an aLineInfo structure to fill out, and this is the
-    // first time through the loop, capture these sizes as mainBaseSizes.
-    // We only care about the first iteration, because additional
-    // iterations will only reset item base sizes to these values.
-    // We also set a 0 mainDeltaSize. This will be modified later if
-    // the item is stretched or shrunk.
-    if (aLineInfo && (iterationCounter == 0)) {
-      uint32_t itemIndex = 0;
-      for (FlexItem* item = mItems.getFirst(); item; item = item->getNext(),
-                                                     ++itemIndex) {
-        aLineInfo->mItems[itemIndex].mMainBaseSize = item->GetMainSize();
-        aLineInfo->mItems[itemIndex].mMainDeltaSize = 0;
-      }
-    }
-
     MOZ_LOG(gFlexContainerLog, LogLevel::Debug,
            (" available free space = %d\n", availableFreeSpace));
 
 
     // The sign of our free space should agree with the type of flexing
     // (grow/shrink) that we're doing (except if we've had integer overflow;
     // then, all bets are off). Any disagreement should've made us use the
     // other type of flexing, or should've been resolved in FreezeItemsEarly.
@@ -2847,47 +2853,54 @@ FlexLine::ResolveFlexibleLengths(nscoord
         // If we have an aLineInfo structure to fill out, capture any
         // size changes that may have occurred in the previous loop.
         // We don't do this inside the previous loop, because we don't
         // want to burden layout when aLineInfo is null.
         if (aLineInfo) {
           uint32_t itemIndex = 0;
           for (FlexItem* item = mItems.getFirst(); item; item = item->getNext(),
                                                          ++itemIndex) {
-            // Calculate a deltaSize that represents how much the
-            // flex sizing algorithm "wants" to stretch or shrink this
-            // item during this pass through the algorithm. Later
-            // passes through the algorithm may overwrite this value.
-            // Also, this value may not reflect how much the size of
-            // the item is actually changed, since the size of the
-            // item will be clamped to min and max values later in
-            // this pass. That's intentional, since we want to report
-            // the value that the sizing algorithm tried to stretch
-            // or shrink the item.
-            nscoord deltaSize = item->GetMainSize() -
-              aLineInfo->mItems[itemIndex].mMainBaseSize;
-
-            aLineInfo->mItems[itemIndex].mMainDeltaSize = deltaSize;
-            // If any item on the line is growing, mark the aLineInfo
-            // structure; likewise if any item is shrinking. Items in
-            // a line can't be both growing and shrinking.
-            if (deltaSize > 0) {
-              MOZ_ASSERT(item->IsFrozen() || isUsingFlexGrow,
-                "Unfrozen items shouldn't grow without isUsingFlexGrow.");
-              MOZ_ASSERT(aLineInfo->mGrowthState !=
-                         ComputedFlexLineInfo::GrowthState::SHRINKING);
-              aLineInfo->mGrowthState =
-                ComputedFlexLineInfo::GrowthState::GROWING;
-            } else if (deltaSize < 0) {
-              MOZ_ASSERT(item->IsFrozen() || !isUsingFlexGrow,
-               "Unfrozen items shouldn't shrink with isUsingFlexGrow.");
-              MOZ_ASSERT(aLineInfo->mGrowthState !=
-                         ComputedFlexLineInfo::GrowthState::GROWING);
-              aLineInfo->mGrowthState =
-                ComputedFlexLineInfo::GrowthState::SHRINKING;
+            if (!item->IsFrozen()) {
+              // Calculate a deltaSize that represents how much the flex sizing
+              // algorithm "wants" to stretch or shrink this item during this
+              // pass through the algorithm. Later passes through the algorithm
+              // may overwrite this, until this item is frozen. Note that this
+              // value may not reflect how much the size of the item is
+              // actually changed, since the size of the item will be clamped
+              // to min and max values later in this pass. That's intentional,
+              // since we want to report the value that the sizing algorithm
+              // tried to stretch or shrink the item.
+              nscoord deltaSize = item->GetMainSize() -
+                aLineInfo->mItems[itemIndex].mMainBaseSize;
+
+              aLineInfo->mItems[itemIndex].mMainDeltaSize = deltaSize;
+              // If any (unfrozen) item on the line is growing, we mark the
+              // aLineInfo structure; likewise if any item is shrinking.
+              // (Note: a line can't contain a mix of items that are growing
+              // and shrinking. Also, the sign of any delta should match the
+              // type of flex factor we're using [grow vs shrink].)
+              if (deltaSize > 0) {
+                MOZ_ASSERT(isUsingFlexGrow,
+                           "Unfrozen items can only grow if we're "
+                           "distributing (positive) space with flex-grow");
+                MOZ_ASSERT(aLineInfo->mGrowthState !=
+                           ComputedFlexLineInfo::GrowthState::SHRINKING,
+                           "shouldn't flip flop from shrinking to growing");
+                aLineInfo->mGrowthState =
+                  ComputedFlexLineInfo::GrowthState::GROWING;
+              } else if (deltaSize < 0) {
+                MOZ_ASSERT(!isUsingFlexGrow,
+                           "Unfrozen items can only shrink if we're "
+                           "distributing (negative) space with flex-shrink");
+                MOZ_ASSERT(aLineInfo->mGrowthState !=
+                           ComputedFlexLineInfo::GrowthState::GROWING,
+                           "shouldn't flip flop from growing to shrinking");
+                aLineInfo->mGrowthState =
+                  ComputedFlexLineInfo::GrowthState::SHRINKING;
+              }
             }
           }
         }
       }
     }
 
     // Fix min/max violations:
     nscoord totalViolation = 0; // keeps track of adjustments for min/max
--- a/layout/style/test/test_transitions_per_property.html
+++ b/layout/style/test/test_transitions_per_property.html
@@ -453,21 +453,21 @@ var transformTests = [
     round_error_ok: true },
   { start: 'none', end: 'rotate(90deg) translate(20%, 20%) rotate(-90deg)',
     expected_uncomputed: 'rotate(22.5deg) translate(5%, 5%) rotate(-22.5deg)',
     round_error_ok: true },
   { start: 'none', end: 'rotate(-90deg) translate(20%, 20%) rotate(90deg)',
     expected_uncomputed: 'rotate(-22.5deg) translate(5%, 5%) rotate(22.5deg)',
     round_error_ok: true },
   // test percent translation using matrix decomposition
-  { start: 'rotate(45deg) rotate(-45deg)',
+  { start: 'matrix(1, 0, 0, 1, 0, 0)',
     end: 'rotate(90deg) translate(20%, 20%) rotate(-90deg)',
     expected: 'matrix(1, 0, 0, 1, -2.5, 15)',
     round_error_ok: true },
-  { start: 'rotate(45deg) rotate(-45deg)',
+  { start: 'matrix(1, 0, 0, 1, 0, 0)',
     end: 'rotate(-90deg) translate(20%, 20%) rotate(90deg)',
     expected: 'matrix(1, 0, 0, 1, 2.5, -15)',
     round_error_ok: true },
   // test calc() in translate
   // Note that font-size: is 20px, and that percentages are relative
   // to 300px (width) and 50px (height) per the prerequisites in
   // property_database.js
   { start: 'translateX(20%)', /* 60px */
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5908,21 +5908,17 @@ pref("general.document_open_conversion_d
 // documentElement and document.body are passive by default.
 pref("dom.event.default_to_passive_touch_listeners", true);
 
 // The amount of time (ms) since navigation start after which all
 // tracker connections will be cancelled.
 pref("browser.fastblock.timeout", 5000);
 // The amount of time (ms) since navigation start after which
 // we'll stop blocking tracker connections (0 = no limit).
-#ifdef NIGHTLY_BUILD
 pref("browser.fastblock.limit", 20000);
-#else
-pref("browser.fastblock.limit", 0);
-#endif
 
 // Enable clipboard readText() and writeText() by default
 pref("dom.events.asyncClipboard", true);
 // Disable clipboard read() and write() by default
 pref("dom.events.asyncClipboard.dataTransfer", false);
 // Should only be enabled in tests
 pref("dom.events.testing.asyncClipboard", false);
 
--- a/servo/components/style/properties/helpers/animated_properties.mako.rs
+++ b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -1351,21 +1351,21 @@ fn is_matched_operation(first: &Computed
         (&TransformOperation::RotateX(..),
          &TransformOperation::RotateX(..)) |
         (&TransformOperation::RotateY(..),
          &TransformOperation::RotateY(..)) |
         (&TransformOperation::RotateZ(..),
          &TransformOperation::RotateZ(..)) |
         (&TransformOperation::Perspective(..),
          &TransformOperation::Perspective(..)) => true,
-        // we animate scale and translate operations against each other
+        // Match functions that have the same primitive transform function
         (a, b) if a.is_translate() && b.is_translate() => true,
         (a, b) if a.is_scale() && b.is_scale() => true,
         (a, b) if a.is_rotate() && b.is_rotate() => true,
-        // InterpolateMatrix and AccumulateMatrix are for mismatched transform.
+        // InterpolateMatrix and AccumulateMatrix are for mismatched transforms
         _ => false
     }
 }
 
 /// A 2d matrix for interpolation.
 #[derive(Clone, ComputeSquaredDistance, Copy, Debug)]
 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
 #[allow(missing_docs)]
@@ -2463,89 +2463,122 @@ impl Animate for ComputedTransform {
     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
         use std::borrow::Cow;
 
         if procedure == Procedure::Add {
             let result = self.0.iter().chain(&other.0).cloned().collect::<Vec<_>>();
             return Ok(Transform(result));
         }
 
-        // https://drafts.csswg.org/css-transforms-1/#transform-transform-neutral-extend-animation
-        fn match_operations_if_possible<'a>(
-            this: &mut Cow<'a, Vec<ComputedTransformOperation>>,
-            other: &mut Cow<'a, Vec<ComputedTransformOperation>>,
-        ) -> bool {
-            if !this.iter().zip(other.iter()).all(|(this, other)| is_matched_operation(this, other)) {
-                return false;
-            }
+        let this = Cow::Borrowed(&self.0);
+        let other = Cow::Borrowed(&other.0);
+
+        // Interpolate the common prefix
+        let mut result = this
+            .iter()
+            .zip(other.iter())
+            .take_while(|(this, other)| is_matched_operation(this, other))
+            .map(|(this, other)| this.animate(other, procedure))
+            .collect::<Result<Vec<_>, _>>()?;
 
-            if this.len() == other.len() {
-                return true;
-            }
-
-            let (shorter, longer) =
-                if this.len() < other.len() {
-                    (this.to_mut(), other)
-                } else {
-                    (other.to_mut(), this)
-                };
+        // Deal with the remainders
+        let this_remainder = if this.len() > result.len() {
+            Some(&this[result.len()..])
+        } else {
+            None
+        };
+        let other_remainder = if other.len() > result.len() {
+            Some(&other[result.len()..])
+        } else {
+            None
+        };
 
-            shorter.reserve(longer.len());
-            for op in longer.iter().skip(shorter.len()) {
-                shorter.push(op.to_animated_zero().unwrap());
-            }
+        match (this_remainder, other_remainder) {
+            // If there is a remainder from *both* lists we must have had mismatched functions.
+            // => Add the remainders to a suitable ___Matrix function.
+            (Some(this_remainder), Some(other_remainder)) => match procedure {
+                Procedure::Add => {
+                    debug_assert!(false, "Should have already dealt with add by the point");
+                    return Err(());
+                }
+                Procedure::Interpolate { progress } => {
+                    result.push(TransformOperation::InterpolateMatrix {
+                        from_list: Transform(this_remainder.to_vec()),
+                        to_list: Transform(other_remainder.to_vec()),
+                        progress: Percentage(progress as f32),
+                    });
+                }
+                Procedure::Accumulate { count } => {
+                    result.push(TransformOperation::AccumulateMatrix {
+                        from_list: Transform(this_remainder.to_vec()),
+                        to_list: Transform(other_remainder.to_vec()),
+                        count: cmp::min(count, i32::max_value() as u64) as i32,
+                    });
+                }
+            },
+            // If there is a remainder from just one list, then one list must be shorter but
+            // completely match the type of the corresponding functions in the longer list.
+            // => Interpolate the remainder with identity transforms.
+            (Some(remainder), None) | (None, Some(remainder)) => {
+                let fill_right = this_remainder.is_some();
+                result.append(
+                    &mut remainder
+                        .iter()
+                        .map(|transform| {
+                            let identity = transform.to_animated_zero().unwrap();
 
-            // The resulting operations won't be matched regardless if the
-            // extended component is already InterpolateMatrix /
-            // AccumulateMatrix.
-            //
-            // Otherwise they should be matching operations all the time.
-            let already_mismatched = matches!(
-                longer[0],
-                TransformOperation::InterpolateMatrix { .. } |
-                TransformOperation::AccumulateMatrix { .. }
-            );
+                            match transform {
+                                // We can't interpolate/accumulate ___Matrix types directly with a
+                                // matrix. Instead we need to wrap it in another ___Matrix type.
+                                TransformOperation::AccumulateMatrix { .. }
+                                | TransformOperation::InterpolateMatrix { .. } => {
+                                    let transform_list = Transform(vec![transform.clone()]);
+                                    let identity_list = Transform(vec![identity]);
+                                    let (from_list, to_list) = if fill_right {
+                                        (transform_list, identity_list)
+                                    } else {
+                                        (identity_list, transform_list)
+                                    };
 
-            debug_assert_eq!(
-                !already_mismatched,
-                longer.iter().zip(shorter.iter()).all(|(this, other)| is_matched_operation(this, other)),
-                "ToAnimatedZero should generate matched operations"
-            );
-
-            !already_mismatched
+                                    match procedure {
+                                        Procedure::Add => Err(()),
+                                        Procedure::Interpolate { progress } => {
+                                            Ok(TransformOperation::InterpolateMatrix {
+                                                from_list,
+                                                to_list,
+                                                progress: Percentage(progress as f32),
+                                            })
+                                        }
+                                        Procedure::Accumulate { count } => {
+                                            Ok(TransformOperation::AccumulateMatrix {
+                                                from_list,
+                                                to_list,
+                                                count: cmp::min(count, i32::max_value() as u64)
+                                                    as i32,
+                                            })
+                                        }
+                                    }
+                                }
+                                _ => {
+                                    let (lhs, rhs) = if fill_right {
+                                        (transform, &identity)
+                                    } else {
+                                        (&identity, transform)
+                                    };
+                                    lhs.animate(rhs, procedure)
+                                }
+                            }
+                        })
+                        .collect::<Result<Vec<_>, _>>()?,
+                );
+            }
+            (None, None) => {}
         }
 
-        let mut this = Cow::Borrowed(&self.0);
-        let mut other = Cow::Borrowed(&other.0);
-
-        if match_operations_if_possible(&mut this, &mut other) {
-            return Ok(Transform(
-                this.iter().zip(other.iter())
-                    .map(|(this, other)| this.animate(other, procedure))
-                    .collect::<Result<Vec<_>, _>>()?
-            ));
-        }
-
-        match procedure {
-            Procedure::Add => Err(()),
-            Procedure::Interpolate { progress } => {
-                Ok(Transform(vec![TransformOperation::InterpolateMatrix {
-                    from_list: Transform(this.into_owned()),
-                    to_list: Transform(other.into_owned()),
-                    progress: Percentage(progress as f32),
-                }]))
-            },
-            Procedure::Accumulate { count } => {
-                Ok(Transform(vec![TransformOperation::AccumulateMatrix {
-                    from_list: Transform(this.into_owned()),
-                    to_list: Transform(other.into_owned()),
-                    count: cmp::min(count, i32::max_value() as u64) as i32,
-                }]))
-            },
-        }
+        Ok(Transform(result))
     }
 }
 
 // This might not be the most useful definition of distance. It might be better, for example,
 // to trace the distance travelled by a point as its transform is interpolated between the two
 // lists. That, however, proves to be quite complicated so we take a simple approach for now.
 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1318591#c0.
 impl ComputeSquaredDistance for ComputedTransformOperation {
--- a/testing/mozharness/configs/l10n_bumper/jamun.py
+++ b/testing/mozharness/configs/l10n_bumper/jamun.py
@@ -18,16 +18,17 @@ config = {
     "gecko_push_url": "ssh://hg.mozilla.org/{}".format(MULTI_REPO),
 
     "hg_user": "L10n Bumper Bot <release+l10nbumper@mozilla.com>",
     "ssh_key": "~/.ssh/ffxbld_rsa",
     "ssh_user": "ffxbld",
 
     "vcs_share_base": "/builds/hg-shared",
     "version_path": "browser/config/version.txt",
+    "status_path": ".l10n_bumper_status",
 
     "bump_configs": [{
         "path": "mobile/locales/l10n-changesets.json",
         "format": "json",
         "name": "Fennec l10n changesets",
         "revision_url": "https://l10n.mozilla.org/shipping/l10n-changesets?av=fennec%(MAJOR_VERSION)s",
         "platform_configs": [{
             "platforms": ["android-api-16", "android"],
--- a/testing/mozharness/configs/l10n_bumper/mozilla-beta.py
+++ b/testing/mozharness/configs/l10n_bumper/mozilla-beta.py
@@ -17,16 +17,17 @@ config = {
     "gecko_push_url": "ssh://hg.mozilla.org/{}".format(MULTI_REPO),
 
     "hg_user": "L10n Bumper Bot <release+l10nbumper@mozilla.com>",
     "ssh_key": "~/.ssh/ffxbld_rsa",
     "ssh_user": "ffxbld",
 
     "vcs_share_base": "/builds/hg-shared",
     "version_path": "browser/config/version.txt",
+    "status_path": ".l10n_bumper_status",
 
     "bump_configs": [{
         "path": "mobile/locales/l10n-changesets.json",
         "format": "json",
         "name": "Fennec l10n changesets",
         "revision_url": "https://l10n.mozilla.org/shipping/l10n-changesets?av=fennec%(MAJOR_VERSION)s",
         "platform_configs": [{
             "platforms": ["android-multilocale"],
--- a/testing/mozharness/configs/l10n_bumper/mozilla-central.py
+++ b/testing/mozharness/configs/l10n_bumper/mozilla-central.py
@@ -17,16 +17,17 @@ config = {
     "gecko_push_url": "ssh://hg.mozilla.org/{}".format(MULTI_REPO),
 
     "hg_user": "L10n Bumper Bot <release+l10nbumper@mozilla.com>",
     "ssh_key": "~/.ssh/ffxbld_rsa",
     "ssh_user": "ffxbld",
 
     "vcs_share_base": "/builds/hg-shared",
     "version_path": "browser/config/version.txt",
+    "status_path": ".l10n_bumper_status",
 
     "bump_configs": [{
         "path": "mobile/locales/l10n-changesets.json",
         "format": "json",
         "name": "Fennec l10n changesets",
         "platform_configs": [{
             "platforms": ["android-api-16", "android"],
             "path": "mobile/android/locales/all-locales"
--- a/testing/mozharness/scripts/l10n_bumper.py
+++ b/testing/mozharness/scripts/l10n_bumper.py
@@ -329,13 +329,18 @@ class L10nBumper(VCSScript):
             self.device_manifests = {}
 
             # Sleep before trying again
             self.info("Sleeping 60 before trying again")
             time.sleep(60)
         else:
             self.fatal("Didn't complete successfully (hit max_retries)")
 
+        # touch status file for nagios
+        dirs = self.query_abs_dirs()
+        status_path = os.path.join(dirs['base_work_dir'], self.config['status_path'])
+        self._touch_file(status_path)
+
 
 # __main__ {{{1
 if __name__ == '__main__':
     bumper = L10nBumper()
     bumper.run_and_exit()
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-transforms/animation/list-interpolation.html
@@ -0,0 +1,120 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Transform list interpolation</title>
+<link rel="help" href="https://drafts.csswg.org/css-transforms-1/#interpolation-of-transforms">
+<meta name="assert" content="Interpolation of transform function lists is performed as follows">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/interpolation-testcommon.js"></script>
+</head>
+<body>
+<script>
+// none -> none
+test_interpolation(
+  {
+    property: 'transform',
+    from: 'none',
+    to: 'none',
+  },
+  [{ at: 0.25, expect: 'none' }]
+);
+
+// none -> something
+test_interpolation(
+  {
+    property: 'transform',
+    from: 'none',
+    to: 'translate(200px) rotate(720deg)',
+  },
+  [{ at: 0.25, expect: 'translate(50px) rotate(180deg)' }]
+);
+
+// something -> none
+test_interpolation(
+  {
+    property: 'transform',
+    from: 'translate(200px) rotate(720deg)',
+    to: 'none',
+  },
+  [{ at: 0.25, expect: 'translate(150px) rotate(540deg)' }]
+);
+
+// Mismatched lengths (from is shorter), common part matches
+test_interpolation(
+  {
+    property: 'transform',
+    from: 'translate(100px)',
+    to: 'translate(200px) rotate(720deg)',
+  },
+  [{ at: 0.25, expect: 'translate(125px) rotate(180deg)' }]
+);
+
+// Mismatched lengths (to is shorter), common part matches
+test_interpolation(
+  {
+    property: 'transform',
+    from: 'translate(100px) rotate(720deg)',
+    to: 'translate(200px)',
+  },
+  [{ at: 0.25, expect: 'translate(125px) rotate(540deg)' }]
+);
+
+// Perfect match
+test_interpolation(
+  {
+    property: 'transform',
+    from: 'scale(2) rotate(360deg) translate(100px) matrix(1, 0, 0, 1, 100, 0) skew(0deg)',
+    to: 'scale(3) rotate(1080deg) translate(200px) matrix(1, 0, 0, 1, 0, 200) skew(720deg)',
+  },
+  [
+    {
+      at: 0.25,
+      expect: 'scale(2.25) rotate(540deg) translate(125px) matrix(1, 0, 0, 1, 75, 50) skew(180deg)',
+    },
+  ]
+);
+
+// Matches on primitives
+test_interpolation(
+  {
+    property: 'transform',
+    from: 'translateX(100px) scaleX(3) translate(500px) scale(2)',
+    to: 'translateY(200px) scale(5) translateX(100px) scaleY(3)',
+  },
+  [{ at: 0.25, expect: 'translate(75px, 50px) scale(3.5, 2) translate(400px, 0px) scale(1.75, 2.25)' }]
+);
+
+// Common prefix
+test_interpolation(
+  {
+    property: 'transform',
+    from: 'rotate(0deg) translate(100px)',
+    to: 'rotate(720deg) scale(2) translate(200px)',
+  },
+  [{ at: 0.25, expect: 'rotate(180deg) matrix(1.25, 0, 0, 1.25, 175, 0)' }]
+);
+
+// Complete mismatch (except length)
+test_interpolation(
+  {
+    property: 'transform',
+    from: 'scale(2) rotate(0deg) translate(100px)',
+    to: 'rotate(720deg) scale(2) translate(200px)',
+  },
+  [{ at: 0.25, expect: 'matrix(2, 0, 0, 2, 250, 0)' }]
+);
+
+// Complete mismatch including length
+test_interpolation(
+  {
+    property: 'transform',
+    from: 'scale(2) rotate(0deg)',
+    to: 'rotate(720deg) scale(2) translate(200px)',
+  },
+  [{ at: 0.25, expect: 'matrix(2, 0, 0, 2, 100, 0)' }]
+);
+</script>
+</body>
+</html>
--- a/testing/web-platform/tests/web-animations/animation-model/animation-types/property-types.js
+++ b/testing/web-platform/tests/web-animations/animation-model/animation-types/property-types.js
@@ -1468,17 +1468,17 @@ const transformListType = {
                                    100 * Math.cos(Math.PI / 2),
                                    100 * Math.sin(Math.PI / 2) ] },
          { time: 1000, expected: [ Math.cos(Math.PI / 2),
                                    Math.sin(Math.PI / 2),
                                   -Math.sin(Math.PI / 2),
                                    Math.cos(Math.PI / 2),
                                    100 * Math.cos(Math.PI / 2),
                                    100 * Math.sin(Math.PI / 2) ] }]);
-    }, `${property}: rotate on roate and translate`);
+    }, `${property}: rotate on rotate and translate`);
 
     test(t => {
       const idlName = propertyToIDL(property);
       const target = createTestElement(t, setup);
       target.style[idlName] = 'matrix(0, 1, -1, 0, 0, 0)';
       const animation =                 // Same matrices as above.
         target.animate({ [idlName]: [ 'matrix(1, 0, 0, 1, 100, 0)',
                                       'matrix(1, 0, 0, 1, 200, 0)' ] },
--- a/toolkit/components/windowcreator/nsIWindowProvider.idl
+++ b/toolkit/components/windowcreator/nsIWindowProvider.idl
@@ -9,22 +9,22 @@
  * wish to provide a custom "new window" of their own (for example by returning
  * a new tab, an existing window, etc) instead of just having a real new
  * toplevel window open.
  */
 
 #include "nsISupports.idl"
 
 %{ C++
-class nsDocShellLoadState;
+class nsDocShellLoadInfo;
 %}
 
 interface mozIDOMWindowProxy;
 interface nsIURI;
-native nsDocShellLoadStatePtr(nsDocShellLoadState*);
+native nsDocShellLoadInfoPtr(nsDocShellLoadInfo*);
 
 /**
  * The nsIWindowProvider interface exists so that the window watcher's default
  * behavior of opening a new window can be easly modified.  When the window
  * watcher needs to open a new window, it will first check with the
  * nsIWindowProvider it gets from the parent window.  If there is no provider
  * or the provider does not provide a window, the window watcher will proceed
  * to actually open a new window.
@@ -71,17 +71,17 @@ interface nsIWindowProvider : nsISupport
    *        is provided solely to help the nsIWindowProvider implementation
    *        make decisions.
    *
    * @param aFeatures The feature string for the window being opened.  This may
    *        be empty.  The nsIWindowProvider implementation is allowed to apply
    *        the feature string to the window it returns in any way it sees fit.
    *        See the nsIWindowWatcher interface for details on feature strings.
    *
-   * @param aLoadState Specify setup information of the load in the new window
+   * @param aLoadInfo Specify setup information of the load in the new window
    *
    * @param aWindowIsNew [out] Whether the window being returned was just
    *        created by the window provider implementation.  This can be used by
    *        callers to keep track of which windows were opened by the user as
    *        opposed to being opened programmatically.  This should be set to
    *        false if the window being returned existed before the
    *        provideWindow() call.  The value of this out parameter is
    *        meaningless if provideWindow() returns null.
@@ -101,11 +101,11 @@ interface nsIWindowProvider : nsISupport
                                    in unsigned long aChromeFlags,
                                    in boolean aCalledFromJS,
                                    in boolean aPositionSpecified,
                                    in boolean aSizeSpecified,
                                    in nsIURI aURI,
                                    in AString aName,
                                    in AUTF8String aFeatures,
                                    in boolean aForceNoOpener,
-                                   in nsDocShellLoadStatePtr aLoadState,
+                                   in nsDocShellLoadInfoPtr aLoadInfo,
                                    out boolean aWindowIsNew);
 };
--- a/toolkit/components/windowwatcher/nsPIWindowWatcher.idl
+++ b/toolkit/components/windowwatcher/nsPIWindowWatcher.idl
@@ -6,27 +6,27 @@
 
 /* Private "control" methods on the Window Watcher. These are annoying
    bookkeeping methods, not part of the public (embedding) interface.
 */
 
 #include "nsISupports.idl"
 
 %{ C++
-class nsDocShellLoadState;
+class nsDocShellLoadInfo;
 %}
 
 interface mozIDOMWindowProxy;
 interface nsIDOMWindow;
 interface nsISimpleEnumerator;
 interface nsIWebBrowserChrome;
 interface nsIDocShellTreeItem;
 interface nsIArray;
 interface nsITabParent;
-native nsDocShellLoadStatePtr(nsDocShellLoadState*);
+native nsDocShellLoadInfoPtr(nsDocShellLoadInfo*);
 
 [uuid(d162f9c4-19d5-4723-931f-f1e51bfa9f68)]
 
 interface nsPIWindowWatcher : nsISupports
 {
   /** A window has been created. Add it to our list.
       @param aWindow the window to add
       @param aChrome the corresponding chrome window. The DOM window
@@ -61,18 +61,18 @@ interface nsPIWindowWatcher : nsISupport
              specified URL.
       @param aArgs Window argument
       @param aIsPopupSpam true if the window is a popup spam window; used for
                           popup blocker internals.
       @param aForceNoOpener If true, force noopener behavior.  This means not
                             looking for existing windows with the given name,
                             not setting an opener on the newly opened window,
                             and returning null from this method.
-      @param aLoadState if aNavigate is true, this allows the caller to pass in
-                        an nsIDocShellLoadState to use for the navigation.
+      @param aLoadInfo if aNavigate is true, this allows the caller to pass in
+                       an nsIDocShellLoadInfo to use for the navigation.
                        Callers can pass in null if they want the windowwatcher
                        to just construct a loadinfo itself.  If aNavigate is
                        false, this argument is ignored.
 
       @return the new window
 
       @note This method may examine the JS context stack for purposes of
             determining the security context to use for the search for a given
@@ -85,17 +85,17 @@ interface nsPIWindowWatcher : nsISupport
   mozIDOMWindowProxy openWindow2(in mozIDOMWindowProxy aParent, in string aUrl,
                                  in string aName, in string aFeatures,
                                  in boolean aCalledFromScript,
                                  in boolean aDialog,
                                  in boolean aNavigate,
                                  in nsISupports aArgs,
                                  in boolean aIsPopupSpam,
                                  in boolean aForceNoOpener,
-                                 in nsDocShellLoadStatePtr aLoadState);
+                                 in nsDocShellLoadInfoPtr aLoadInfo);
 
   /**
    * Opens a new window so that the window that aOpeningTab belongs to
    * is set as the parent window. The newly opened window will also
    * inherit load context information from aOpeningTab.
    *
    * @param aOpeningTab
    *        The nsITabParent that is requesting the new window be opened.
--- a/toolkit/components/windowwatcher/nsWindowWatcher.cpp
+++ b/toolkit/components/windowwatcher/nsWindowWatcher.cpp
@@ -17,17 +17,17 @@
 #include "plstr.h"
 
 #include "nsDocShell.h"
 #include "nsGlobalWindow.h"
 #include "nsHashPropertyBag.h"
 #include "nsIBaseWindow.h"
 #include "nsIBrowserDOMWindow.h"
 #include "nsIDocShell.h"
-#include "nsDocShellLoadState.h"
+#include "nsDocShellLoadInfo.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsIDocShellTreeOwner.h"
 #include "nsIDocumentLoader.h"
 #include "nsIDocument.h"
 #include "nsIDOMWindow.h"
 #include "nsIDOMChromeWindow.h"
 #include "nsIPrompt.h"
 #include "nsIScriptObjectPrincipal.h"
@@ -320,17 +320,17 @@ nsWindowWatcher::OpenWindow(mozIDOMWindo
   }
   bool dialog = (argc != 0);
 
   return OpenWindowInternal(aParent, aUrl, aName, aFeatures,
                             /* calledFromJS = */ false, dialog,
                             /* navigate = */ true, argv,
                             /* aIsPopupSpam = */ false,
                             /* aForceNoOpener = */ false,
-                            /* aLoadState = */ nullptr,
+                            /* aLoadInfo = */ nullptr,
                             aResult);
 }
 
 struct SizeSpec
 {
   SizeSpec()
     : mLeft(0)
     , mTop(0)
@@ -386,17 +386,17 @@ nsWindowWatcher::OpenWindow2(mozIDOMWind
                              const char* aName,
                              const char* aFeatures,
                              bool aCalledFromScript,
                              bool aDialog,
                              bool aNavigate,
                              nsISupports* aArguments,
                              bool aIsPopupSpam,
                              bool aForceNoOpener,
-                             nsDocShellLoadState* aLoadState,
+                             nsDocShellLoadInfo* aLoadInfo,
                              mozIDOMWindowProxy** aResult)
 {
   nsCOMPtr<nsIArray> argv = ConvertArgsToArray(aArguments);
 
   uint32_t argc = 0;
   if (argv) {
     argv->GetLength(&argc);
   }
@@ -407,17 +407,17 @@ nsWindowWatcher::OpenWindow2(mozIDOMWind
   bool dialog = aDialog;
   if (!aCalledFromScript) {
     dialog = argc > 0;
   }
 
   return OpenWindowInternal(aParent, aUrl, aName, aFeatures,
                             aCalledFromScript, dialog,
                             aNavigate, argv, aIsPopupSpam,
-                            aForceNoOpener, aLoadState, aResult);
+                            aForceNoOpener, aLoadInfo, aResult);
 }
 
 // This static function checks if the aDocShell uses an UserContextId equal to
 // the userContextId of subjectPrincipal, if not null.
 static bool
 CheckUserContextCompatibility(nsIDocShell* aDocShell)
 {
   MOZ_ASSERT(aDocShell);
@@ -632,17 +632,17 @@ nsWindowWatcher::OpenWindowInternal(mozI
                                     const char* aName,
                                     const char* aFeatures,
                                     bool aCalledFromJS,
                                     bool aDialog,
                                     bool aNavigate,
                                     nsIArray* aArgv,
                                     bool aIsPopupSpam,
                                     bool aForceNoOpener,
-                                    nsDocShellLoadState* aLoadState,
+                                    nsDocShellLoadInfo* aLoadInfo,
                                     mozIDOMWindowProxy** aResult)
 {
   nsresult rv = NS_OK;
   bool isNewToplevelWindow = false;
   bool windowIsNew = false;
   bool windowNeedsName = false;
   bool windowIsModal = false;
   bool uriToLoadIsChrome = false;
@@ -816,17 +816,17 @@ nsWindowWatcher::OpenWindowInternal(mozI
       }
 
       if (provider) {
         nsCOMPtr<mozIDOMWindowProxy> newWindow;
         rv = provider->ProvideWindow(aParent, chromeFlags, aCalledFromJS,
                                      sizeSpec.PositionSpecified(),
                                      sizeSpec.SizeSpecified(),
                                      uriToLoad, name, features, aForceNoOpener,
-                                     aLoadState, &windowIsNew,
+                                     aLoadInfo, &windowIsNew,
                                      getter_AddRefs(newWindow));
 
         if (NS_SUCCEEDED(rv)) {
           GetWindowTreeItem(newWindow, getter_AddRefs(newDocShellItem));
           if (windowIsNew && newDocShellItem) {
             // Make sure to stop any loads happening in this window that the
             // window provider might have started.  Otherwise if our caller
             // manipulates the window it just opened and then the load
@@ -1113,38 +1113,38 @@ nsWindowWatcher::OpenWindowInternal(mozI
   } else if (windowIsNew) {
     nsCOMPtr<nsILoadContext> childContext = do_QueryInterface(newDocShellItem);
     if (childContext) {
       childContext->SetPrivateBrowsing(isPrivateBrowsingWindow);
       childContext->SetRemoteTabs(isRemoteWindow);
     }
   }
 
-  RefPtr<nsDocShellLoadState> loadState = aLoadState;
-  if (uriToLoad && aNavigate && !loadState) {
-    loadState = new nsDocShellLoadState();
+  RefPtr<nsDocShellLoadInfo> loadInfo = aLoadInfo;
+  if (uriToLoad && aNavigate && !loadInfo) {
+    loadInfo = new nsDocShellLoadInfo();
 
     if (subjectPrincipal) {
-      loadState->SetTriggeringPrincipal(subjectPrincipal);
+      loadInfo->SetTriggeringPrincipal(subjectPrincipal);
     }
 
     /* use the URL from the *extant* document, if any. The usual accessor
        GetDocument will synchronously create an about:blank document if
        it has no better answer, and we only care about a real document.
        Also using GetDocument to force document creation seems to
        screw up focus in the hidden window; see bug 36016.
     */
     nsCOMPtr<nsIDocument> doc = GetEntryDocument();
     if (!doc && parentWindow) {
       doc = parentWindow->GetExtantDoc();
     }
     if (doc) {
       // Set the referrer
-      loadState->SetReferrer(doc->GetDocumentURI());
-      loadState->SetReferrerPolicy(doc->GetReferrerPolicy());
+      loadInfo->SetReferrer(doc->GetDocumentURI());
+      loadInfo->SetReferrerPolicy(doc->GetReferrerPolicy());
     }
   }
 
   if (isNewToplevelWindow) {
     // Notify observers that the window is open and ready.
     // The window has not yet started to load a document.
     nsCOMPtr<nsIObserverService> obsSvc =
       mozilla::services::GetObserverService();
@@ -1176,23 +1176,23 @@ nsWindowWatcher::OpenWindowInternal(mozI
       props->SetPropertyAsInterface(NS_LITERAL_STRING("createdTabDocShell"), newDocShellItem);
 
       obsSvc->NotifyObservers(static_cast<nsIPropertyBag2*>(props),
                               "webNavigation-createdNavigationTarget-from-js", nullptr);
     }
   }
 
   if (uriToLoad && aNavigate) {
-    loadState->SetURI(uriToLoad);
-    loadState->SetLoadFlags(windowIsNew ?
+    newDocShell->LoadURI(
+      uriToLoad,
+      loadInfo,
+      windowIsNew ?
         static_cast<uint32_t>(nsIWebNavigation::LOAD_FLAGS_FIRST_LOAD) :
-                            static_cast<uint32_t>(nsIWebNavigation::LOAD_FLAGS_NONE));
-    loadState->SetFirstParty(true);
-    // Should this pay attention to errors returned by LoadURI?
-    newDocShell->LoadURI(loadState);
+        static_cast<uint32_t>(nsIWebNavigation::LOAD_FLAGS_NONE),
+      true);
   }
 
   // Copy the current session storage for the current domain. Don't perform the
   // copy if we're forcing noopener, however.
   if (!aForceNoOpener && subjectPrincipal && parentDocShell) {
     nsCOMPtr<nsIDOMStorageManager> parentStorageManager =
       do_QueryInterface(parentDocShell);
     nsCOMPtr<nsIDOMStorageManager> newStorageManager =
--- a/toolkit/components/windowwatcher/nsWindowWatcher.h
+++ b/toolkit/components/windowwatcher/nsWindowWatcher.h
@@ -83,17 +83,17 @@ protected:
                               const char* aName,
                               const char* aFeatures,
                               bool aCalledFromJS,
                               bool aDialog,
                               bool aNavigate,
                               nsIArray* aArgv,
                               bool aIsPopupSpam,
                               bool aForceNoOpener,
-                              nsDocShellLoadState* aLoadState,
+                              nsDocShellLoadInfo* aLoadInfo,
                               mozIDOMWindowProxy** aResult);
 
   static nsresult URIfromURL(const char* aURL,
                              mozIDOMWindowProxy* aParent,
                              nsIURI** aURI);
 
   static uint32_t CalculateChromeFlagsForChild(const nsACString& aFeaturesStr);
 
--- a/xpcom/string/nsTStringRepr.h
+++ b/xpcom/string/nsTStringRepr.h
@@ -252,21 +252,24 @@ public:
   // for wide strings. Call this version when you know the
   // length of 'data'.
   bool NS_FASTCALL EqualsASCII(const char* aData, size_type aLen) const;
   // An efficient comparison with ASCII that can be used even
   // for wide strings. Call this version when 'data' is
   // null-terminated.
   bool NS_FASTCALL EqualsASCII(const char* aData) const;
 
-  // EqualsLiteral must ONLY be applied to an actual literal string, or
-  // a char array *constant* declared without an explicit size.
-  // Do not attempt to use it with a regular char* pointer, or with a
-  // non-constant char array variable. Use EqualsASCII for them.
-  // The template trick to acquire the array length at compile time without
+  // EqualsLiteral must ONLY be called with an actual literal string, or
+  // a char array *constant* declared without an explicit size and with an
+  // initializer that is a string literal or is otherwise null-terminated.
+  // Use EqualsASCII for other char array variables.
+  // (Although this method may happen to produce expected results for other
+  // char arrays that have bound one greater than the sequence of interest,
+  // such use is discouraged for reasons of readability and maintainability.)
+  // The template trick to acquire the array bound at compile time without
   // using a macro is due to Corey Kosak, with much thanks.
   template<int N>
   inline bool EqualsLiteral(const char (&aStr)[N]) const
   {
     return EqualsASCII(aStr, N - 1);
   }
 
   // The LowerCaseEquals methods compare the ASCII-lowercase version of
@@ -274,21 +277,23 @@ public:
   // ASCII/Literal string. The ASCII string is *not* lowercased for
   // you. If you compare to an ASCII or literal string that contains an
   // uppercase character, it is guaranteed to return false. We will
   // throw assertions too.
   bool NS_FASTCALL LowerCaseEqualsASCII(const char* aData,
                                         size_type aLen) const;
   bool NS_FASTCALL LowerCaseEqualsASCII(const char* aData) const;
 
-  // LowerCaseEqualsLiteral must ONLY be applied to an actual
-  // literal string, or a char array *constant* declared without an
-  // explicit size.  Do not attempt to use it with a regular char*
-  // pointer, or with a non-constant char array variable. Use
-  // LowerCaseEqualsASCII for them.
+  // LowerCaseEqualsLiteral must ONLY be called with an actual literal string,
+  // or a char array *constant* declared without an explicit size and with an
+  // initializer that is a string literal or is otherwise null-terminated.
+  // Use LowerCaseEqualsASCII for other char array variables.
+  // (Although this method may happen to produce expected results for other
+  // char arrays that have bound one greater than the sequence of interest,
+  // such use is discouraged for reasons of readability and maintainability.)
   template<int N>
   bool LowerCaseEqualsLiteral(const char (&aStr)[N]) const
   {
     return LowerCaseEqualsASCII(aStr, N - 1);
   }
 
   // Returns true if this string overlaps with the given string fragment.
   bool IsDependentOn(const char_type* aStart, const char_type* aEnd) const
--- a/xpcom/string/nsTSubstring.h
+++ b/xpcom/string/nsTSubstring.h
@@ -468,36 +468,37 @@ public:
   MOZ_MUST_USE bool NS_FASTCALL AssignASCII(const char* aData,
                                             const fallible_t& aFallible)
   {
     return AssignASCII(aData,
                        mozilla::AssertedCast<size_type, size_t>(strlen(aData)),
                        aFallible);
   }
 
-  // AssignLiteral must ONLY be applied to an actual literal string, or
-  // a character array *constant* declared without an explicit size.
-  // Do not attempt to use it with a regular character pointer, or with a
-  // non-constant chararacter array variable. Use AssignASCII for those.
+  // AssignLiteral must ONLY be called with an actual literal string, or
+  // a character array *constant* of static storage duration declared
+  // without an explicit size and with an initializer that is a string
+  // literal or is otherwise null-terminated.
+  // Use Assign or AssignASCII for other character array variables.
   //
   // This method does not need a fallible version, because it uses the
   // POD buffer of the literal as the string's buffer without allocating.
   // The literal does not need to be ASCII. If this a 16-bit string, this
   // method takes a u"" literal. (The overload on 16-bit strings that takes
   // a "" literal takes only ASCII.)
   template<int N>
   void AssignLiteral(const char_type (&aStr)[N])
   {
     AssignLiteral(aStr, N - 1);
   }
 
-  // AssignLiteral must ONLY be applied to an actual literal string, or
-  // a character array *constant* declared without an explicit size.
-  // Do not attempt to use it with a regular character pointer, or with a
-  // non-constant chararacter array variable. Use AssignASCII for those.
+  // AssignLiteral must ONLY be called with an actual literal string, or
+  // a char array *constant* declared without an explicit size and with an
+  // initializer that is a string literal or is otherwise null-terminated.
+  // Use AssignASCII for other char array variables.
   //
   // This method takes an 8-bit (ASCII-only!) string that is expanded
   // into a 16-bit string at run time causing a run-time allocation.
   // To avoid the run-time allocation (at the cost of the literal
   // taking twice the size in the binary), use the above overload that
   // takes a u"" string instead. Using the overload that takes a u""
   // literal is generally preferred when working with 16-bit strings.
   //
@@ -587,19 +588,21 @@ public:
                                 const char* aData,
                                 size_type aLength = size_type(-1));
 
   MOZ_MUST_USE bool NS_FASTCALL ReplaceASCII(index_type aCutStart, size_type aCutLength,
                                              const char* aData,
                                              size_type aLength,
                                              const fallible_t&);
 
-  // ReplaceLiteral must ONLY be applied to an actual literal string.
-  // Do not attempt to use it with a regular char* pointer, or with a char
-  // array variable. Use Replace or ReplaceASCII for those.
+  // ReplaceLiteral must ONLY be called with an actual literal string, or
+  // a character array *constant* of static storage duration declared
+  // without an explicit size and with an initializer that is a string
+  // literal or is otherwise null-terminated.
+  // Use Replace or ReplaceASCII for other character array variables.
   template<int N>
   void ReplaceLiteral(index_type aCutStart, size_type aCutLength,
                       const char_type (&aStr)[N])
   {
     ReplaceLiteral(aCutStart, aCutLength, aStr, N - 1);
   }
 
   void Append(char_type aChar);
@@ -635,19 +638,21 @@ public:
 
   MOZ_MUST_USE bool AppendASCII(const char* aData,
                                 size_type aLength,
                                 const fallible_t& aFallible);
 
   // Appends a literal string ("" literal in the 8-bit case and u"" literal
   // in the 16-bit case) to the string.
   //
-  // AppendLiteral must ONLY be applied to an actual literal string.
-  // Do not attempt to use it with a regular character pointer, or with a
-  // character array variable. Use Append or AppendASCII for those.
+  // AppendLiteral must ONLY be called with an actual literal string, or
+  // a character array *constant* of static storage duration declared
+  // without an explicit size and with an initializer that is a string
+  // literal or is otherwise null-terminated.
+  // Use Append or AppendASCII for other character array variables.
   template<int N>
   void AppendLiteral(const char_type (&aStr)[N])
   {
     // The case where base_string_type::mLength is zero is intentionally
     // left unoptimized (could be optimized as call to AssignLiteral),
     // because it's rare/nonexistent. If you add that optimization,
     // please be sure to also check that
     // !(base_string_type::mDataFlags & DataFlags::REFCOUNTED)
@@ -796,19 +801,21 @@ public:
   {
     Replace(aPos, 0, aStr);
   }
   void Insert(const substring_tuple_type& aTuple, index_type aPos)
   {
     Replace(aPos, 0, aTuple);
   }
 
-  // InsertLiteral must ONLY be applied to an actual literal string.
-  // Do not attempt to use it with a regular char* pointer, or with a char
-  // array variable. Use Insert for those.
+  // InsertLiteral must ONLY be called with an actual literal string, or
+  // a character array *constant* of static storage duration declared
+  // without an explicit size and with an initializer that is a string
+  // literal or is otherwise null-terminated.
+  // Use Insert for other character array variables.
   template<int N>
   void InsertLiteral(const char_type (&aStr)[N], index_type aPos)
   {
     ReplaceLiteral(aPos, 0, aStr, N - 1);
   }
 
   void Cut(index_type aCutStart, size_type aCutLength)
   {
--- a/xpfe/appshell/nsContentTreeOwner.cpp
+++ b/xpfe/appshell/nsContentTreeOwner.cpp
@@ -28,17 +28,17 @@
 #include "nsIWebNavigation.h"
 #include "nsDocShellCID.h"
 #include "nsIExternalURLHandlerService.h"
 #include "nsIMIMEInfo.h"
 #include "nsIWidget.h"
 #include "nsWindowWatcher.h"
 #include "mozilla/BrowserElementParent.h"
 #include "mozilla/NullPrincipal.h"
-#include "nsDocShellLoadState.h"
+#include "nsDocShellLoadInfo.h"
 
 #include "nsIScriptObjectPrincipal.h"
 #include "nsIURI.h"
 #include "nsIDocument.h"
 #if defined(XP_MACOSX)
 #include "nsThreadUtils.h"
 #endif
 
@@ -761,17 +761,17 @@ nsContentTreeOwner::ProvideWindow(mozIDO
                                   uint32_t aChromeFlags,
                                   bool aCalledFromJS,
                                   bool aPositionSpecified,
                                   bool aSizeSpecified,
                                   nsIURI* aURI,
                                   const nsAString& aName,
                                   const nsACString& aFeatures,
                                   bool aForceNoOpener,
-                                  nsDocShellLoadState* aLoadState,
+                                  nsDocShellLoadInfo* aLoadInfo,
                                   bool* aWindowIsNew,
                                   mozIDOMWindowProxy** aReturn)
 {
   NS_ENSURE_ARG_POINTER(aParent);
 
   auto* parent = nsPIDOMWindowOuter::From(aParent);
 
   *aReturn = nullptr;