Merge autoland to mozilla-central. a=merge
authorMihai Alexandru Michis <malexandru@mozilla.com>
Fri, 08 May 2020 12:34:44 +0300
changeset 528768 19e273db80195cc5de59647fcaf16bafad9bbcce
parent 528716 50244579252a229131ae04a1d2c60682611e5702 (current diff)
parent 528767 67b2b97539bd1a13a68a84d8aee7b3f613a5985b (diff)
child 528799 002363d72d30c80d89edf36059de0ef5c31c9759
push id37395
push usermalexandru@mozilla.com
push dateFri, 08 May 2020 09:35:43 +0000
treeherdermozilla-central@19e273db8019 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone78.0a1
first release with
nightly linux32
19e273db8019 / 78.0a1 / 20200508093543 / files
nightly linux64
19e273db8019 / 78.0a1 / 20200508093543 / files
nightly mac
19e273db8019 / 78.0a1 / 20200508093543 / files
nightly win32
19e273db8019 / 78.0a1 / 20200508093543 / files
nightly win64
19e273db8019 / 78.0a1 / 20200508093543 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to mozilla-central. a=merge
dom/ipc/PBrowserOrId.ipdlh
dom/tests/mochitest/chrome/file_bug799299.xhtml
dom/tests/mochitest/chrome/test_bug799299.xhtml
--- a/browser/base/content/test/tabs/browser_tabswitch_window_focus.js
+++ b/browser/base/content/test/tabs/browser_tabswitch_window_focus.js
@@ -6,34 +6,73 @@ SpecialPowers.pushPrefEnv({ set: [["dom.
 const FILE = getRootDirectory(gTestPath) + "open_window_in_new_tab.html";
 
 add_task(async function() {
   info("Opening first tab: " + FILE);
   let firstTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, FILE);
 
   let promiseTabOpened = BrowserTestUtils.waitForNewTab(
     gBrowser,
-    FILE + "?opened",
+    FILE + "?open-click",
     true
   );
   info("Opening second tab using a click");
-  await SpecialPowers.spawn(firstTab.linkedBrowser, [""], async function() {
-    content.document.querySelector("#open").click();
-  });
+  await BrowserTestUtils.synthesizeMouseAtCenter(
+    "#open-click",
+    {},
+    firstTab.linkedBrowser
+  );
 
   info("Waiting for the second tab to be opened");
   let secondTab = await promiseTabOpened;
 
   info("Going back to the first tab");
   await BrowserTestUtils.switchTab(gBrowser, firstTab);
 
   info("Focusing second tab by clicking on the first tab");
   await BrowserTestUtils.switchTab(gBrowser, async function() {
     await SpecialPowers.spawn(firstTab.linkedBrowser, [""], async function() {
       content.document.querySelector("#focus").click();
     });
   });
 
   is(gBrowser.selectedTab, secondTab, "Should've switched tabs");
 
-  BrowserTestUtils.removeTab(firstTab);
-  BrowserTestUtils.removeTab(secondTab);
+  await BrowserTestUtils.removeTab(firstTab);
+  await BrowserTestUtils.removeTab(secondTab);
 });
+
+add_task(async function() {
+  info("Opening first tab: " + FILE);
+  let firstTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, FILE);
+
+  let promiseTabOpened = BrowserTestUtils.waitForNewTab(
+    gBrowser,
+    FILE + "?open-mousedown",
+    true
+  );
+  info("Opening second tab using a click");
+  await BrowserTestUtils.synthesizeMouseAtCenter(
+    "#open-mousedown",
+    { type: "mousedown" },
+    firstTab.linkedBrowser
+  );
+
+  info("Waiting for the second tab to be opened");
+  let secondTab = await promiseTabOpened;
+
+  is(gBrowser.selectedTab, secondTab, "Should've switched tabs");
+
+  info("Ensuring we don't switch back");
+  await new Promise(resolve => {
+    // We need to wait for something _not_ happening, so we need to use an arbitrary setTimeout.
+    //
+    // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+    setTimeout(function() {
+      is(gBrowser.selectedTab, secondTab, "Should've remained in original tab");
+      resolve();
+    }, 500);
+  });
+
+  info("cleanup");
+  await BrowserTestUtils.removeTab(firstTab);
+  await BrowserTestUtils.removeTab(secondTab);
+});
--- a/browser/base/content/test/tabs/open_window_in_new_tab.html
+++ b/browser/base/content/test/tabs/open_window_in_new_tab.html
@@ -1,8 +1,15 @@
 <!doctype html>
 <script>
-function openWindow() {
-  window.childWindow = window.open(location.href + "?opened", "", "");
+function openWindow(id) {
+  window.childWindow = window.open(location.href + "?" + id, "", "");
 }
 </script>
-<button id="open" onclick="openWindow()">Open window</button>
+<button id="open-click" onclick="openWindow('open-click')">Open window</button>
 <button id="focus" onclick="window.childWindow.focus()">Focus window</button>
+<button id="open-mousedown">Open window</button>
+<script>
+document.getElementById("open-mousedown").addEventListener("mousedown", function(e) {
+  openWindow(this.id);
+  e.preventDefault();
+});
+</script>
--- a/browser/components/extensions/test/browser/browser_ext_tabs_update_url.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_update_url.js
@@ -143,28 +143,34 @@ add_task(async function() {
   info("done");
 });
 
 add_task(async function test_update_reload() {
   const URL = "https://example.com/";
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
-      permissions: ["tabs", "history"],
+      permissions: ["tabs"],
     },
 
     background() {
       browser.test.onMessage.addListener(async (cmd, ...args) => {
         const result = await browser.tabs[cmd](...args);
         browser.test.sendMessage("result", result);
       });
 
-      browser.history.onVisited.addListener(data => {
-        browser.test.sendMessage("historyAdded");
-      });
+      const filter = {
+        properties: ["status"],
+      };
+
+      browser.tabs.onUpdated.addListener((tabId, changeInfo) => {
+        if (changeInfo.status === "complete") {
+          browser.test.sendMessage("historyAdded");
+        }
+      }, filter);
     },
   });
 
   let win = await BrowserTestUtils.openNewBrowserWindow();
   let tabBrowser = win.gBrowser.selectedBrowser;
   await BrowserTestUtils.loadURI(tabBrowser, URL);
   await BrowserTestUtils.browserLoaded(tabBrowser, false, URL);
   let tab = win.gBrowser.selectedTab;
--- a/devtools/client/aboutdebugging/test/browser/browser.ini
+++ b/devtools/client/aboutdebugging/test/browser/browser.ini
@@ -68,16 +68,17 @@ skip-if = verify || ccov || (os == 'linu
 [browser_aboutdebugging_devtoolstoolbox_shortcuts.js]
 skip-if = ccov || (os == 'linux' && bits == 64) # Bug 1521349, Bug 1548015, Bug 1544828
 [browser_aboutdebugging_devtoolstoolbox_splitconsole_key.js]
 [browser_aboutdebugging_devtoolstoolbox_target_destroyed.js]
 skip-if = debug || asan # This test leaks. See bug 1529005
 [browser_aboutdebugging_devtoolstoolbox_tooltip_markupview.js]
 [browser_aboutdebugging_fenix_runtime_display.js]
 [browser_aboutdebugging_message_close.js]
+[browser_aboutdebugging_navigate_to_url.js]
 [browser_aboutdebugging_navigate.js]
 [browser_aboutdebugging_persist_connection.js]
 [browser_aboutdebugging_process_category.js]
 [browser_aboutdebugging_process_main.js]
 [browser_aboutdebugging_process_main_local.js]
 skip-if = debug
 [browser_aboutdebugging_profiler_dialog.js]
 [browser_aboutdebugging_real_usb_runtime_page_runtime_info.js]
--- a/devtools/client/aboutdebugging/test/browser/browser_aboutdebugging_devtoolstoolbox_target_destroyed.js
+++ b/devtools/client/aboutdebugging/test/browser/browser_aboutdebugging_devtoolstoolbox_target_destroyed.js
@@ -12,21 +12,19 @@ add_task(async function() {
   // go to This Firefox and inspect the new tab
   info("Inspecting a new tab in This Firefox");
   await selectThisFirefoxPage(document, window.AboutDebugging.store);
   const {
     devtoolsDocument,
     devtoolsTab,
     devtoolsWindow,
   } = await openAboutDevtoolsToolbox(document, tab, window, "about:home");
-  const targetInfoHeader = devtoolsDocument.querySelector(
-    ".qa-debug-target-info"
-  );
+  const targetUrl = devtoolsDocument.querySelector(".devtools-textinput");
   ok(
-    targetInfoHeader.textContent.includes("about:home"),
+    targetUrl.value.includes("about:home"),
     "about:devtools-toolbox is open for the target"
   );
 
   // close the inspected tab and check that error page is shown
   info("removing the inspected tab");
   await removeTab(targetTab);
   await waitUntil(() =>
     devtoolsWindow.document.querySelector(".qa-error-page")
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser/browser_aboutdebugging_navigate_to_url.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const NEW_TAB_TITLE = "PAGE 2";
+const TAB_URL = "data:text/html,<title>PAGE</title>";
+const NEW_TAB_URL = `data:text/html,<title>${NEW_TAB_TITLE}</title>`;
+
+/**
+ * This test file ensures that the URL input for DebugTargetInfo navigates the target to
+ * the specified URL.
+ */
+add_task(async function() {
+  const { document, tab, window } = await openAboutDebugging();
+
+  info("Open a new background tab.");
+  const debug_tab = await addTab(TAB_URL, { background: true });
+
+  await selectThisFirefoxPage(document, window.AboutDebugging.store);
+  const devToolsToolbox = await openAboutDevtoolsToolbox(
+    document,
+    tab,
+    window,
+    "PAGE"
+  );
+  const { devtoolsDocument, devtoolsTab, devtoolsWindow } = devToolsToolbox;
+
+  const urlInput = devtoolsDocument.querySelector(".devtools-textinput");
+  await synthesizeUrlKeyInput(devToolsToolbox, urlInput, NEW_TAB_URL);
+
+  info("Test that the debug target navigated to the specified URL.");
+  const toolbox = getToolbox(devtoolsWindow);
+  await waitUntil(
+    () =>
+      toolbox.target.url === NEW_TAB_URL &&
+      debug_tab.linkedBrowser.currentURI.spec === NEW_TAB_URL
+  );
+  ok(true, "Target navigated.");
+  ok(toolbox.target.title.includes(NEW_TAB_TITLE), "Target's title updated.");
+  is(urlInput.value, NEW_TAB_URL, "Input url updated.");
+
+  info("Remove the about:debugging tab.");
+  await removeTab(tab);
+
+  info("Remove the about:devtools-toolbox tab.");
+  await removeTab(devtoolsTab);
+
+  info("Remove the background tab");
+  await removeTab(debug_tab);
+});
+
+/**
+ * Synthesizes key input inside the DebugTargetInfo's URL component.
+ *
+ * @param {DevToolsToolbox} toolbox
+ *        The DevToolsToolbox debugging the target.
+ * @param {HTMLElement} inputEl
+ *        The <input> element to submit the URL with.
+ * @param {String}  url
+ *        The URL to navigate to.
+ */
+async function synthesizeUrlKeyInput(toolbox, inputEl, url) {
+  const { devtoolsDocument, devtoolsWindow } = toolbox;
+
+  info("Wait for URL input to be focused.");
+  const onInputFocused = waitUntil(
+    () => devtoolsDocument.activeElement === inputEl
+  );
+  inputEl.focus();
+  await onInputFocused;
+
+  info("Synthesize entering URL into text field");
+  const onInputChange = waitUntil(() => inputEl.value === url);
+  for (const key of NEW_TAB_URL.split("")) {
+    EventUtils.synthesizeKey(key, {}, devtoolsWindow);
+  }
+  await onInputChange;
+
+  info("Submit URL to navigate to");
+  EventUtils.synthesizeKey("KEY_Enter");
+}
--- a/devtools/client/accessibility/test/browser/head.js
+++ b/devtools/client/accessibility/test/browser/head.js
@@ -628,19 +628,24 @@ async function toggleMenuItem(doc, toolb
   const menuEl = toolboxDoc.getElementById(menuId);
   const menuItem = menuEl.querySelectorAll(".command")[menuItemIndex];
   ok(menuItem, "Expected menu item");
 
   const expected =
     menuItem.getAttribute("aria-checked") === "true" ? null : "true";
 
   // Make the menu visible first.
+  const onPopupShown = new Promise(r =>
+    toolboxDoc.addEventListener("popupshown", r, { once: true })
+  );
   EventUtils.synthesizeMouseAtCenter(menuButton, {}, panelWin);
-  await BrowserTestUtils.waitForCondition(
-    () => !!menuItem.offsetParent,
+  await onPopupShown;
+  const boundingRect = menuItem.getBoundingClientRect();
+  ok(
+    boundingRect.width > 0 && boundingRect.height > 0,
     "Menu item is visible."
   );
 
   EventUtils.synthesizeMouseAtCenter(menuItem, {}, toolboxWin);
   await BrowserTestUtils.waitForCondition(
     () => expected === menuItem.getAttribute("aria-checked"),
     "Menu item updated."
   );
--- a/devtools/client/accessibility/test/node/setup.js
+++ b/devtools/client/accessibility/test/node/setup.js
@@ -22,8 +22,14 @@ global.loader = {
   },
 };
 
 global.define = function(fn) {
   fn(null, global, { exports: global });
 };
 
 global.requestIdleCallback = function() {};
+
+// Used for the HTMLTooltip component.
+// And set "isSystemPrincipal: false" because can't support XUL element in node.
+global.document.nodePrincipal = {
+  isSystemPrincipal: false,
+};
--- a/devtools/client/framework/components/DebugTargetInfo.js
+++ b/devtools/client/framework/components/DebugTargetInfo.js
@@ -1,13 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
+const Services = require("Services");
 const { PureComponent } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const {
   CONNECTION_TYPES,
   DEBUG_TARGET_TYPES,
 } = require("devtools/client/shared/remote-debugging/constants");
 
@@ -30,16 +31,26 @@ class DebugTargetInfo extends PureCompon
         targetType: PropTypes.oneOf(Object.values(DEBUG_TARGET_TYPES))
           .isRequired,
       }).isRequired,
       L10N: PropTypes.object.isRequired,
       toolbox: PropTypes.object.isRequired,
     };
   }
 
+  constructor(props) {
+    super(props);
+
+    this.state = { urlValue: props.toolbox.target.url };
+
+    this.onChange = this.onChange.bind(this);
+    this.onFocus = this.onFocus.bind(this);
+    this.onSubmit = this.onSubmit.bind(this);
+  }
+
   componentDidMount() {
     this.updateTitle();
   }
 
   updateTitle() {
     const { L10N, debugTargetData, toolbox } = this.props;
     const title = toolbox.target.name;
     const targetTypeStr = L10N.getStr(
@@ -131,16 +142,46 @@ class DebugTargetInfo extends PureCompon
           image: "chrome://devtools/skin/images/debugging-workers.svg",
           l10nId: "toolbox.debugTargetInfo.targetType.worker",
         };
       default:
         return {};
     }
   }
 
+  onChange({ target }) {
+    this.setState({ urlValue: target.value });
+  }
+
+  onFocus({ target }) {
+    target.select();
+  }
+
+  onSubmit(event) {
+    event.preventDefault();
+    let url = this.state.urlValue;
+
+    if (!url || !url.length) {
+      return;
+    }
+
+    try {
+      // Get the URL from the fixup service:
+      const flags = Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS;
+      const uriInfo = Services.uriFixup.getFixupURIInfo(url, flags);
+      url = uriInfo.fixedURI.spec;
+    } catch (ex) {
+      // The getFixupURIInfo service will throw an error if a malformed URI is
+      // produced from the input.
+      console.error(ex);
+    }
+
+    this.props.toolbox.target.navigateTo({ url });
+  }
+
   shallRenderConnection() {
     const { connectionType } = this.props.debugTargetData;
     const renderableTypes = [CONNECTION_TYPES.USB, CONNECTION_TYPES.NETWORK];
 
     return renderableTypes.includes(connectionType);
   }
 
   renderConnection() {
@@ -171,39 +212,69 @@ class DebugTargetInfo extends PureCompon
         className: "iconized-label qa-runtime-info",
       },
       dom.img({ src: icon, className: "channel-icon qa-runtime-icon" }),
       dom.b({ className: "devtools-ellipsis-text" }, this.getRuntimeText()),
       dom.span({ className: "devtools-ellipsis-text" }, deviceName)
     );
   }
 
-  renderTarget() {
+  renderTargetTitle() {
     const title = this.props.toolbox.target.name;
-    const url = this.props.toolbox.target.url;
 
     const { image, l10nId } = this.getAssetsForDebugTargetType();
 
     return dom.span(
       {
-        className: "iconized-label",
+        className: "iconized-label debug-target-title",
       },
       dom.img({ src: image, alt: this.props.L10N.getStr(l10nId) }),
       title
         ? dom.b({ className: "devtools-ellipsis-text qa-target-title" }, title)
-        : null,
-      dom.span({ className: "devtools-ellipsis-text" }, url)
+        : null
+    );
+  }
+
+  renderTargetURI() {
+    const url = this.props.toolbox.target.url;
+    const { targetType } = this.props.debugTargetData;
+    const isURLEditable = targetType === DEBUG_TARGET_TYPES.TAB;
+
+    return dom.span(
+      {
+        key: url,
+        className: "debug-target-url",
+      },
+      isURLEditable
+        ? this.renderTargetInput(url)
+        : dom.span({ className: "devtools-ellipsis-text" }, url)
+    );
+  }
+
+  renderTargetInput(url) {
+    return dom.form(
+      {
+        className: "debug-target-url-form",
+        onSubmit: this.onSubmit,
+      },
+      dom.input({
+        className: "devtools-textinput debug-target-url-input",
+        onChange: this.onChange,
+        onFocus: this.onFocus,
+        defaultValue: url,
+      })
     );
   }
 
   render() {
     return dom.header(
       {
         className: "debug-target-info qa-debug-target-info",
       },
       this.shallRenderConnection() ? this.renderConnection() : null,
       this.renderRuntime(),
-      this.renderTarget()
+      this.renderTargetTitle(),
+      this.renderTargetURI()
     );
   }
 }
 
 module.exports = DebugTargetInfo;
--- a/devtools/client/framework/components/ToolboxController.js
+++ b/devtools/client/framework/components/ToolboxController.js
@@ -54,16 +54,17 @@ class ToolboxController extends Componen
     this.setDockOptionsEnabled = this.setDockOptionsEnabled.bind(this);
     this.setCanCloseToolbox = this.setCanCloseToolbox.bind(this);
     this.setIsSplitConsoleActive = this.setIsSplitConsoleActive.bind(this);
     this.setDisableAutohide = this.setDisableAutohide.bind(this);
     this.setCanRender = this.setCanRender.bind(this);
     this.setPanelDefinitions = this.setPanelDefinitions.bind(this);
     this.updateButtonIds = this.updateButtonIds.bind(this);
     this.updateFocusedButton = this.updateFocusedButton.bind(this);
+    this.setDebugTargetData = this.setDebugTargetData.bind(this);
   }
 
   shouldComponentUpdate() {
     return this.state.canRender;
   }
 
   componentWillUnmount() {
     this.state.toolboxButtons.forEach(button => {
@@ -188,14 +189,18 @@ class ToolboxController extends Componen
     ).length;
 
     this.setState(
       { toolboxButtons, visibleToolboxButtonCount },
       this.updateButtonIds
     );
   }
 
+  setDebugTargetData(data) {
+    this.setState({ debugTargetData: data });
+  }
+
   render() {
     return ToolboxToolbar(Object.assign({}, this.props, this.state));
   }
 }
 
 module.exports = ToolboxController;
--- a/devtools/client/framework/test/node/components/__snapshots__/debug-target-info.test.js.snap
+++ b/devtools/client/framework/test/node/components/__snapshots__/debug-target-info.test.js.snap
@@ -27,32 +27,42 @@ exports[`DebugTargetInfo component Conne
     </b>
     <span
       className="devtools-ellipsis-text"
     >
       usbDeviceName
     </span>
   </span>
   <span
-    className="iconized-label"
+    className="iconized-label debug-target-title"
   >
     <img
       alt="toolbox.debugTargetInfo.targetType.tab"
       src="chrome://devtools/skin/images/globe.svg"
     />
     <b
       className="devtools-ellipsis-text qa-target-title"
     >
       Test Tab Name
     </b>
-    <span
-      className="devtools-ellipsis-text"
+  </span>
+  <span
+    className="debug-target-url"
+  >
+    <form
+      className="debug-target-url-form"
+      onSubmit={[Function]}
     >
-      http://some.target/url
-    </span>
+      <input
+        className="devtools-textinput debug-target-url-input"
+        defaultValue="http://some.target/url"
+        onChange={[Function]}
+        onFocus={[Function]}
+      />
+    </form>
   </span>
 </header>
 `;
 
 exports[`DebugTargetInfo component Target icon renders the expected snapshot for a process target 1`] = `
 <header
   className="debug-target-info qa-debug-target-info"
 >
@@ -79,27 +89,31 @@ exports[`DebugTargetInfo component Targe
     </b>
     <span
       className="devtools-ellipsis-text"
     >
       usbDeviceName
     </span>
   </span>
   <span
-    className="iconized-label"
+    className="iconized-label debug-target-title"
   >
     <img
       alt="toolbox.debugTargetInfo.targetType.process"
       src="chrome://devtools/skin/images/settings.svg"
     />
     <b
       className="devtools-ellipsis-text qa-target-title"
     >
       Test Tab Name
     </b>
+  </span>
+  <span
+    className="debug-target-url"
+  >
     <span
       className="devtools-ellipsis-text"
     >
       http://some.target/url
     </span>
   </span>
 </header>
 `;
@@ -131,32 +145,42 @@ exports[`DebugTargetInfo component Targe
     </b>
     <span
       className="devtools-ellipsis-text"
     >
       usbDeviceName
     </span>
   </span>
   <span
-    className="iconized-label"
+    className="iconized-label debug-target-title"
   >
     <img
       alt="toolbox.debugTargetInfo.targetType.tab"
       src="chrome://devtools/skin/images/globe.svg"
     />
     <b
       className="devtools-ellipsis-text qa-target-title"
     >
       Test Tab Name
     </b>
-    <span
-      className="devtools-ellipsis-text"
+  </span>
+  <span
+    className="debug-target-url"
+  >
+    <form
+      className="debug-target-url-form"
+      onSubmit={[Function]}
     >
-      http://some.target/url
-    </span>
+      <input
+        className="devtools-textinput debug-target-url-input"
+        defaultValue="http://some.target/url"
+        onChange={[Function]}
+        onFocus={[Function]}
+      />
+    </form>
   </span>
 </header>
 `;
 
 exports[`DebugTargetInfo component Target icon renders the expected snapshot for a worker target 1`] = `
 <header
   className="debug-target-info qa-debug-target-info"
 >
@@ -183,27 +207,31 @@ exports[`DebugTargetInfo component Targe
     </b>
     <span
       className="devtools-ellipsis-text"
     >
       usbDeviceName
     </span>
   </span>
   <span
-    className="iconized-label"
+    className="iconized-label debug-target-title"
   >
     <img
       alt="toolbox.debugTargetInfo.targetType.worker"
       src="chrome://devtools/skin/images/debugging-workers.svg"
     />
     <b
       className="devtools-ellipsis-text qa-target-title"
     >
       Test Tab Name
     </b>
+  </span>
+  <span
+    className="debug-target-url"
+  >
     <span
       className="devtools-ellipsis-text"
     >
       http://some.target/url
     </span>
   </span>
 </header>
 `;
@@ -235,27 +263,31 @@ exports[`DebugTargetInfo component Targe
     </b>
     <span
       className="devtools-ellipsis-text"
     >
       usbDeviceName
     </span>
   </span>
   <span
-    className="iconized-label"
+    className="iconized-label debug-target-title"
   >
     <img
       alt="toolbox.debugTargetInfo.targetType.extension"
       src="chrome://devtools/skin/images/debugging-addons.svg"
     />
     <b
       className="devtools-ellipsis-text qa-target-title"
     >
       Test Tab Name
     </b>
+  </span>
+  <span
+    className="debug-target-url"
+  >
     <span
       className="devtools-ellipsis-text"
     >
       http://some.target/url
     </span>
   </span>
 </header>
 `;
@@ -276,32 +308,42 @@ exports[`DebugTargetInfo component Targe
     >
       toolbox.debugTargetInfo.runtimeLabel.thisFirefox-1.0.0
     </b>
     <span
       className="devtools-ellipsis-text"
     />
   </span>
   <span
-    className="iconized-label"
+    className="iconized-label debug-target-title"
   >
     <img
       alt="toolbox.debugTargetInfo.targetType.tab"
       src="chrome://devtools/skin/images/globe.svg"
     />
     <b
       className="devtools-ellipsis-text qa-target-title"
     >
       Test Tab Name
     </b>
-    <span
-      className="devtools-ellipsis-text"
+  </span>
+  <span
+    className="debug-target-url"
+  >
+    <form
+      className="debug-target-url-form"
+      onSubmit={[Function]}
     >
-      http://some.target/url
-    </span>
+      <input
+        className="devtools-textinput debug-target-url-input"
+        defaultValue="http://some.target/url"
+        onChange={[Function]}
+        onFocus={[Function]}
+      />
+    </form>
   </span>
 </header>
 `;
 
 exports[`DebugTargetInfo component Target title renders the expected snapshot for a Toolbox with an unnamed target 1`] = `
 <header
   className="debug-target-info qa-debug-target-info"
 >
@@ -317,22 +359,32 @@ exports[`DebugTargetInfo component Targe
     >
       toolbox.debugTargetInfo.runtimeLabel.thisFirefox-1.0.0
     </b>
     <span
       className="devtools-ellipsis-text"
     />
   </span>
   <span
-    className="iconized-label"
+    className="iconized-label debug-target-title"
   >
     <img
       alt="toolbox.debugTargetInfo.targetType.tab"
       src="chrome://devtools/skin/images/globe.svg"
     />
-    <span
-      className="devtools-ellipsis-text"
+  </span>
+  <span
+    className="debug-target-url"
+  >
+    <form
+      className="debug-target-url-form"
+      onSubmit={[Function]}
     >
-      http://some.target/without/a/name
-    </span>
+      <input
+        className="devtools-textinput debug-target-url-input"
+        defaultValue="http://some.target/without/a/name"
+        onChange={[Function]}
+        onFocus={[Function]}
+      />
+    </form>
   </span>
 </header>
 `;
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -350,16 +350,17 @@ function Toolbox(
   this.toggleSplitConsole = this.toggleSplitConsole.bind(this);
   this.toggleOptions = this.toggleOptions.bind(this);
   this.togglePaintFlashing = this.togglePaintFlashing.bind(this);
   this.toggleDragging = this.toggleDragging.bind(this);
   this._onPausedState = this._onPausedState.bind(this);
   this._onResumedState = this._onResumedState.bind(this);
   this._onTargetAvailable = this._onTargetAvailable.bind(this);
   this._onTargetDestroyed = this._onTargetDestroyed.bind(this);
+  this._onNavigate = this._onNavigate.bind(this);
 
   this.isPaintFlashing = false;
 
   if (!selectedTool) {
     selectedTool = Services.prefs.getCharPref(this._prefs.LAST_TOOL);
   }
   this._defaultToolId = selectedTool;
 
@@ -714,17 +715,17 @@ Toolbox.prototype = {
    * This method will be called for the top-level target, as well as any potential
    * additional targets we may care about.
    */
   async _onTargetAvailable({ type, targetFront, isTopLevel }) {
     if (isTopLevel) {
       // Attach to a new top-level target.
       // For now, register these event listeners only on the top level target
       targetFront.on("will-navigate", this._onWillNavigate);
-      targetFront.on("navigate", this._refreshHostTitle);
+      targetFront.on("navigate", this._onNavigate);
       targetFront.on("frame-update", this._updateFrames);
       targetFront.on("inspect-object", this._onInspectObject);
 
       targetFront.watchFronts("inspector", async inspectorFront => {
         registerWalkerListeners(this.store, inspectorFront.walker);
       });
     }
 
@@ -814,23 +815,16 @@ Toolbox.prototype = {
   open: function() {
     return async function() {
       const isToolboxURL = this.win.location.href.startsWith(this._URL);
       if (isToolboxURL) {
         // Update the URL so that onceDOMReady watch for the right url.
         this._URL = this.win.location.href;
       }
 
-      if (this.hostType === Toolbox.HostType.PAGE) {
-        // Displays DebugTargetInfo which shows the basic information of debug target,
-        // if `about:devtools-toolbox` URL opens directly.
-        // DebugTargetInfo requires this._debugTargetData to be populated
-        this._debugTargetData = this._getDebugTargetData();
-      }
-
       const domReady = new Promise(resolve => {
         DOMHelpers.onceDOMReady(
           this.win,
           () => {
             resolve();
           },
           this._URL
         );
@@ -873,25 +867,28 @@ Toolbox.prototype = {
       this._buildDockOptions();
       this._buildTabs();
       this._applyCacheSettings();
       this._applyServiceWorkersTestingSettings();
       this._addWindowListeners();
       this._addChromeEventHandlerEvents();
       this._registerOverlays();
 
-      this._componentMount.addEventListener(
-        "keypress",
-        this._onToolbarArrowKeypress
-      );
+      // Get the tab bar of the ToolboxController to attach the "keypress" event listener to.
+      this._tabBar = this.doc.querySelector(".devtools-tabbar");
+      this._tabBar.addEventListener("keypress", this._onToolbarArrowKeypress);
+
       this._componentMount.setAttribute(
         "aria-label",
         L10N.getStr("toolbox.label")
       );
 
+      // Set debug target data on the ToolboxController component.
+      this._setDebugTargetData();
+
       this.webconsolePanel = this.doc.querySelector(
         "#toolbox-panel-webconsole"
       );
       this.webconsolePanel.height = Services.prefs.getIntPref(
         SPLITCONSOLE_HEIGHT_PREF
       );
       this.webconsolePanel.addEventListener(
         "resize",
@@ -988,17 +985,17 @@ Toolbox.prototype = {
         // passing `e` to console.error, it is not on the stdout, so print it via dump.
         dump(e.stack + "\n");
       });
   },
 
   detachTarget() {
     this.target.off("inspect-object", this._onInspectObject);
     this.target.off("will-navigate", this._onWillNavigate);
-    this.target.off("navigate", this._refreshHostTitle);
+    this.target.off("navigate", this._onNavigate);
     this.target.off("frame-update", this._updateFrames);
 
     // Detach the thread
     this._threadFront = null;
   },
 
   /**
    * Retrieve the ChromeEventHandler associated to the toolbox frame.
@@ -1821,17 +1818,16 @@ Toolbox.prototype = {
       currentToolId: this.currentToolId,
       selectTool: this.selectTool,
       toggleOptions: this.toggleOptions,
       toggleSplitConsole: this.toggleSplitConsole,
       toggleNoAutohide: this.toggleNoAutohide,
       closeToolbox: this.closeToolbox,
       focusButton: this._onToolbarFocus,
       toolbox: this,
-      debugTargetData: this._debugTargetData,
       onTabsOrderUpdated: this._onTabsOrderUpdated,
     });
 
     this.component = this.ReactDOM.render(element, this._componentMount);
   },
 
   /**
    * Reset tabindex attributes across all focusable elements inside the toolbar.
@@ -1855,17 +1851,17 @@ Toolbox.prototype = {
     const { key, target, ctrlKey, shiftKey, altKey, metaKey } = event;
 
     // If any of the modifier keys are pressed do not attempt navigation as it
     // might conflict with global shortcuts (Bug 1327972).
     if (ctrlKey || shiftKey || altKey || metaKey) {
       return;
     }
 
-    const buttons = [...this._componentMount.querySelectorAll("button")];
+    const buttons = [...this._tabBar.querySelectorAll("button")];
     const curIndex = buttons.indexOf(target);
 
     if (curIndex === -1) {
       console.warn(
         target +
           " is not found among Developer Tools tab bar " +
           "focusable elements."
       );
@@ -3699,22 +3695,23 @@ Toolbox.prototype = {
       this._saveSplitConsoleHeight();
       this.webconsolePanel.removeEventListener(
         "resize",
         this._saveSplitConsoleHeight
       );
       this.webconsolePanel = null;
     }
     if (this._componentMount) {
-      this._componentMount.removeEventListener(
+      this._tabBar.removeEventListener(
         "keypress",
         this._onToolbarArrowKeypress
       );
       this.ReactDOM.unmountComponentAtNode(this._componentMount);
       this._componentMount = null;
+      this._tabBar = null;
     }
 
     const outstanding = [];
     for (const [id, panel] of this._toolPanels) {
       try {
         gDevTools.emit(id + "-destroy", this, panel);
         this.emit(id + "-destroy", panel);
 
@@ -4200,9 +4197,29 @@ Toolbox.prototype = {
     }
 
     if (!this._toolNames.has(id)) {
       return "other";
     }
 
     return id;
   },
+
+  /**
+   * Fired when the user navigates to another page.
+   */
+  _onNavigate: function() {
+    this._refreshHostTitle();
+    this._setDebugTargetData();
+  },
+
+  /**
+   * Sets basic information on the DebugTargetInfo component
+   */
+  _setDebugTargetData() {
+    if (this.hostType === Toolbox.HostType.PAGE) {
+      // Displays DebugTargetInfo which shows the basic information of debug target,
+      // if `about:devtools-toolbox` URL opens directly.
+      // DebugTargetInfo requires this._debugTargetData to be populated
+      this.component.setDebugTargetData(this._getDebugTargetData());
+    }
+  },
 };
--- a/devtools/client/performance-new/components/Settings.js
+++ b/devtools/client/performance-new/components/Settings.js
@@ -190,17 +190,17 @@ class Settings extends PureComponent {
     this._handleRemoveObjdir = this._handleRemoveObjdir.bind(this);
     this._setThreadTextFromInput = this._setThreadTextFromInput.bind(this);
     this._handleThreadTextCleanup = this._handleThreadTextCleanup.bind(this);
     this._renderThreadsColumns = this._renderThreadsColumns.bind(this);
 
     this._intervalExponentialScale = makeExponentialScale(0.01, 100);
     this._entriesExponentialScale = makePowerOf2Scale(
       128 * 1024,
-      128 * 1024 * 1024
+      256 * 1024 * 1024
     );
   }
 
   /**
    * Handle the checkbox change.
    * @param {React.ChangeEvent<HTMLInputElement>} event
    */
   _handleThreadCheckboxChange(event) {
--- a/devtools/client/performance-new/popup/background.jsm.js
+++ b/devtools/client/performance-new/popup/background.jsm.js
@@ -80,44 +80,44 @@ const lazy = createLazyLoaders({
 });
 
 /** @type {Presets} */
 const presets = {
   "web-developer": {
     label: "Web Developer",
     description:
       "Recommended preset for most web app debugging, with low overhead.",
-    entries: 16 * 1024 * 1024,
+    entries: 128 * 1024 * 1024,
     interval: 1,
     features: ["screenshots", "js"],
     threads: ["GeckoMain", "Compositor", "Renderer", "DOM Worker"],
     duration: 0,
   },
   "firefox-platform": {
     label: "Firefox Platform",
     description: "Recommended preset for internal Firefox platform debugging.",
-    entries: 16 * 1024 * 1024,
+    entries: 128 * 1024 * 1024,
     interval: 1,
     features: ["screenshots", "js", "leaf", "stackwalk", "java"],
     threads: ["GeckoMain", "Compositor", "Renderer"],
     duration: 0,
   },
   "firefox-front-end": {
     label: "Firefox Front-End",
     description: "Recommended preset for internal Firefox front-end debugging.",
-    entries: 16 * 1024 * 1024,
+    entries: 128 * 1024 * 1024,
     interval: 1,
     features: ["screenshots", "js", "leaf", "stackwalk", "java"],
     threads: ["GeckoMain", "Compositor", "Renderer", "DOM Worker"],
     duration: 0,
   },
   media: {
     label: "Media",
     description: "Recommended preset for diagnosing audio and video problems.",
-    entries: 16 * 1024 * 1024,
+    entries: 128 * 1024 * 1024,
     interval: 1,
     features: ["js", "leaf", "stackwalk"],
     threads: [
       "GeckoMain",
       "Compositor",
       "Renderer",
       "RenderBackend",
       "AudioIPC",
--- a/devtools/client/performance-new/test/browser/browser_aboutprofiling-entries.js
+++ b/devtools/client/performance-new/test/browser/browser_aboutprofiling-entries.js
@@ -5,24 +5,24 @@
 "use strict";
 
 add_task(async function test() {
   info("Test that about:profiling can modify the sampling interval.");
 
   await withAboutProfiling(async document => {
     is(
       getActiveConfiguration().capacity,
-      Math.pow(2, 24),
+      128 * 1024 * 1024,
       "The active configuration is set to a specific number initially. If this" +
         " test fails here, then the magic numbers here may need to be adjusted."
     );
 
     info("Change the buffer input to an arbitrarily smaller value.");
     const bufferInput = await getNearestInputFromText(document, "Buffer size:");
     setReactFriendlyInputValue(bufferInput, Number(bufferInput.value) * 0.1);
 
     is(
       getActiveConfiguration().capacity,
-      Math.pow(2, 18),
+      256 * 1024,
       "The capacity changed to a smaller value."
     );
   });
 });
--- a/devtools/client/performance-new/test/browser/head.js
+++ b/devtools/client/performance-new/test/browser/head.js
@@ -11,16 +11,36 @@ registerCleanupFunction(() => {
   BackgroundJSM.revertRecordingPreferences();
 });
 
 /**
  * Allow tests to use "require".
  */
 const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
 
+{
+  const {
+    getEnvironmentVariable,
+  } = require("devtools/client/performance-new/browser");
+
+  if (getEnvironmentVariable("MOZ_PROFILER_SHUTDOWN")) {
+    throw new Error(
+      "These tests cannot be run with shutdown profiling as they rely on manipulating " +
+        "the state of the profiler."
+    );
+  }
+
+  if (getEnvironmentVariable("MOZ_PROFILER_STARTUP")) {
+    throw new Error(
+      "These tests cannot be run with startup profiling as they rely on manipulating " +
+        "the state of the profiler."
+    );
+  }
+}
+
 /**
  * Wait for a single requestAnimationFrame tick.
  */
 function tick() {
   return new Promise(resolve => requestAnimationFrame(resolve));
 }
 
 /**
--- a/devtools/client/responsive/components/App.js
+++ b/devtools/client/responsive/components/App.js
@@ -101,19 +101,16 @@ class App extends PureComponent {
     this.onToggleLeftAlignment = this.onToggleLeftAlignment.bind(this);
     this.onToggleReloadOnTouchSimulation = this.onToggleReloadOnTouchSimulation.bind(
       this
     );
     this.onToggleReloadOnUserAgent = this.onToggleReloadOnUserAgent.bind(this);
     this.onToggleUserAgentInput = this.onToggleUserAgentInput.bind(this);
     this.onUpdateDeviceDisplayed = this.onUpdateDeviceDisplayed.bind(this);
     this.onUpdateDeviceModal = this.onUpdateDeviceModal.bind(this);
-    this.onUpdateDeviceSelectorMenu = this.onUpdateDeviceSelectorMenu.bind(
-      this
-    );
   }
 
   componentWillUnmount() {
     this.browser.removeEventListener("contextmenu", this.onContextMenu);
     this.browser = null;
   }
 
   onAddCustomDevice(device) {
@@ -398,33 +395,16 @@ class App extends PureComponent {
   onUpdateDeviceModal(isOpen, modalOpenedFromViewport) {
     this.props.dispatch(updateDeviceModal(isOpen, modalOpenedFromViewport));
 
     if (Services.prefs.getBoolPref("devtools.responsive.browserUI.enabled")) {
       window.postMessage({ type: "update-device-modal", isOpen }, "*");
     }
   }
 
-  onUpdateDeviceSelectorMenu(isOpen) {
-    if (Services.prefs.getBoolPref("devtools.responsive.browserUI.enabled")) {
-      const rdmToolbar = window.parent.document.querySelector(".rdm-toolbar");
-      const browserStackEl = rdmToolbar.parentNode;
-
-      // Guarantee a fixed height for the HTMLTooltip to render inside.
-      const style = window.getComputedStyle(browserStackEl);
-      rdmToolbar.style.height = style.height;
-
-      if (isOpen) {
-        browserStackEl.classList.add("device-selector-menu-opened");
-      }
-
-      window.postMessage({ type: "update-device-toolbar-height", isOpen }, "*");
-    }
-  }
-
   render() {
     const { devices, networkThrottling, screenshot, viewports } = this.props;
 
     const {
       onAddCustomDevice,
       onBrowserMounted,
       onChangeDevice,
       onChangeNetworkThrottling,
@@ -442,17 +422,16 @@ class App extends PureComponent {
       onRotateViewport,
       onScreenshot,
       onToggleLeftAlignment,
       onToggleReloadOnTouchSimulation,
       onToggleReloadOnUserAgent,
       onToggleUserAgentInput,
       onUpdateDeviceDisplayed,
       onUpdateDeviceModal,
-      onUpdateDeviceSelectorMenu,
     } = this;
 
     if (!viewports.length) {
       return null;
     }
 
     const selectedDevice = viewports[0].device;
     const selectedPixelRatio = viewports[0].pixelRatio;
@@ -481,17 +460,16 @@ class App extends PureComponent {
         doResizeViewport,
         onRotateViewport,
         onScreenshot,
         onToggleLeftAlignment,
         onToggleReloadOnTouchSimulation,
         onToggleReloadOnUserAgent,
         onToggleUserAgentInput,
         onUpdateDeviceModal,
-        onUpdateDeviceSelectorMenu,
       }),
       !Services.prefs.getBoolPref("devtools.responsive.browserUI.enabled")
         ? Viewports({
             screenshot,
             viewports,
             onBrowserMounted,
             onContentResize,
             onRemoveDeviceAssociation,
--- a/devtools/client/responsive/components/DeviceModal.js
+++ b/devtools/client/responsive/components/DeviceModal.js
@@ -103,22 +103,17 @@ class DeviceModal extends PureComponent 
   onDeviceFormHide() {
     this.setState({
       deviceFormType: "",
       editingDevice: null,
     });
   }
 
   onDeviceModalSubmit() {
-    const {
-      devices,
-      onDeviceListUpdate,
-      onUpdateDeviceDisplayed,
-      onUpdateDeviceModal,
-    } = this.props;
+    const { devices, onDeviceListUpdate, onUpdateDeviceDisplayed } = this.props;
 
     const preferredDevices = {
       added: new Set(),
       removed: new Set(),
     };
 
     for (const type of devices.types) {
       for (const device of devices[type]) {
@@ -132,17 +127,16 @@ class DeviceModal extends PureComponent 
 
         if (this.state[device.name] != device.displayed) {
           onUpdateDeviceDisplayed(device, type, this.state[device.name]);
         }
       }
     }
 
     onDeviceListUpdate(preferredDevices);
-    onUpdateDeviceModal(false);
   }
 
   onEditCustomDevice(newDevice) {
     this.props.onEditCustomDevice(this.state.editingDevice, newDevice);
 
     // We want to remove the original device name from state after editing, so create a
     // new state setting the old key to null and the new one to true.
     this.setState({
--- a/devtools/client/responsive/components/DeviceSelector.js
+++ b/devtools/client/responsive/components/DeviceSelector.js
@@ -35,17 +35,16 @@ loader.lazyGetter(this, "MenuList", () =
 });
 
 class DeviceSelector extends PureComponent {
   static get propTypes() {
     return {
       devices: PropTypes.shape(Types.devices).isRequired,
       onChangeDevice: PropTypes.func.isRequired,
       onUpdateDeviceModal: PropTypes.func.isRequired,
-      onUpdateDeviceSelectorMenu: PropTypes.func.isRequired,
       selectedDevice: PropTypes.string.isRequired,
       viewportId: PropTypes.number.isRequired,
     };
   }
 
   getMenuProps(device) {
     if (!device) {
       return { icon: null, label: null, tooltip: null };
@@ -134,17 +133,17 @@ class DeviceSelector extends PureCompone
         onClick: () => onUpdateDeviceModal(true, viewportId),
       })
     );
 
     return MenuList({}, menuItems);
   }
 
   render() {
-    const { devices, onUpdateDeviceSelectorMenu } = this.props;
+    const { devices } = this.props;
     const selectedDevice = this.getSelectedDevice();
     let { icon, label, tooltip } = this.getMenuProps(selectedDevice);
 
     if (!selectedDevice) {
       label = getStr("responsive.responsiveMode");
     }
 
     // MenuButton is expected to be used in the toolbox document usually,
@@ -170,17 +169,15 @@ class DeviceSelector extends PureCompone
         id: "device-selector",
         menuId: "device-selector-menu",
         toolboxDoc,
         className: "devtools-button devtools-dropdown-button",
         label,
         icon,
         title: tooltip,
         disabled: devices.listState !== Types.loadableState.LOADED,
-        onClick: () => onUpdateDeviceSelectorMenu(true),
-        onCloseButton: () => onUpdateDeviceSelectorMenu(false),
       },
       () => this.renderMenuList()
     );
   }
 }
 
 module.exports = DeviceSelector;
--- a/devtools/client/responsive/components/Toolbar.js
+++ b/devtools/client/responsive/components/Toolbar.js
@@ -56,17 +56,16 @@ class Toolbar extends PureComponent {
       doResizeViewport: PropTypes.func.isRequired,
       onRotateViewport: PropTypes.func.isRequired,
       onScreenshot: PropTypes.func.isRequired,
       onToggleLeftAlignment: PropTypes.func.isRequired,
       onToggleReloadOnTouchSimulation: PropTypes.func.isRequired,
       onToggleReloadOnUserAgent: PropTypes.func.isRequired,
       onToggleUserAgentInput: PropTypes.func.isRequired,
       onUpdateDeviceModal: PropTypes.func.isRequired,
-      onUpdateDeviceSelectorMenu: PropTypes.func.isRequired,
       screenshot: PropTypes.shape(Types.screenshot).isRequired,
       selectedDevice: PropTypes.string.isRequired,
       selectedPixelRatio: PropTypes.number.isRequired,
       showUserAgentInput: PropTypes.bool.isRequired,
       touchSimulationEnabled: PropTypes.bool.isRequired,
       viewport: PropTypes.shape(Types.viewport).isRequired,
     };
   }
@@ -103,17 +102,16 @@ class Toolbar extends PureComponent {
       doResizeViewport,
       onRotateViewport,
       onScreenshot,
       onToggleLeftAlignment,
       onToggleReloadOnTouchSimulation,
       onToggleReloadOnUserAgent,
       onToggleUserAgentInput,
       onUpdateDeviceModal,
-      onUpdateDeviceSelectorMenu,
       screenshot,
       selectedDevice,
       selectedPixelRatio,
       showUserAgentInput,
       touchSimulationEnabled,
       viewport,
     } = this.props;
 
@@ -128,17 +126,16 @@ class Toolbar extends PureComponent {
           .trim(),
       },
       dom.div(
         { id: "toolbar-center-controls" },
         DeviceSelector({
           devices,
           onChangeDevice,
           onUpdateDeviceModal,
-          onUpdateDeviceSelectorMenu,
           selectedDevice,
           viewportId: viewport.id,
         }),
         dom.div({ className: "devtools-separator" }),
         ViewportDimension({
           onRemoveDeviceAssociation,
           doResizeViewport,
           viewport,
--- a/devtools/client/responsive/test/browser/head.js
+++ b/devtools/client/responsive/test/browser/head.js
@@ -493,49 +493,48 @@ async function selectMenuItem({ toolWind
  *         A window reference.
  * @param  {Element} button
  *         The button that will show a context menu when clicked.
  * @param  {Function} testFn
  *         A test function that will be ran with the found menu item in the context menu
  *         as an argument.
  */
 async function testMenuItems(toolWindow, button, testFn) {
-  if (button.id === "device-selector") {
-    // device-selector uses a DevTools MenuButton instead of a XUL menu
-    button.click();
-    // Wait for appearance the menu items..
-    await waitUntil(() =>
-      toolWindow.document.querySelector("#device-selector-menu .menuitem")
-    );
-    const tooltip = toolWindow.document.querySelector("#device-selector-menu");
-    const items = tooltip.querySelectorAll(".menuitem > .command");
-    testFn([...items]);
-
-    if (tooltip.classList.contains("tooltip-visible")) {
-      // Close the tooltip explicitly.
-      button.click();
-      await waitUntil(() => !tooltip.classList.contains("tooltip-visible"));
-    }
-    return;
-  }
-
   // The context menu appears only in the top level window, which is different from
   // the inner toolWindow.
   const win = getTopLevelWindow(toolWindow);
 
   await new Promise(resolve => {
     win.document.addEventListener(
       "popupshown",
-      () => {
-        const popup = win.document.querySelector('menupopup[menu-api="true"]');
-        const menuItems = [...popup.children];
+      async () => {
+        if (button.id === "device-selector") {
+          const popup = toolWindow.document.querySelector(
+            "#device-selector-menu"
+          );
+          const menuItems = [...popup.querySelectorAll(".menuitem > .command")];
+
+          testFn(menuItems);
 
-        testFn(menuItems);
+          if (popup.classList.contains("tooltip-visible")) {
+            // Close the tooltip explicitly.
+            button.click();
+            await waitUntil(() => !popup.classList.contains("tooltip-visible"));
+          }
+        } else {
+          const popup = win.document.querySelector(
+            'menupopup[menu-api="true"]'
+          );
+          const menuItems = [...popup.children];
 
-        popup.hidePopup();
+          testFn(menuItems);
+
+          popup.hidePopup();
+        }
+
         resolve();
       },
       { once: true }
     );
 
     button.click();
   });
 }
--- a/devtools/client/responsive/ui.js
+++ b/devtools/client/responsive/ui.js
@@ -299,21 +299,17 @@ class ResponsiveUI {
       entries => {
         for (const entry of entries) {
           const { width } = entry.contentRect;
 
           this.rdmFrame.style.setProperty("width", `${width}px`);
 
           // If the device modal/selector is opened, resize the toolbar height to
           // the size of the stack.
-          if (
-            this.browserStackEl.classList.contains(
-              "device-selector-menu-opened"
-            )
-          ) {
+          if (this.browserStackEl.classList.contains("device-modal-opened")) {
             const style = this.browserWindow.getComputedStyle(
               this.browserStackEl
             );
             this.rdmFrame.style.height = style.height;
           } else {
             // If the toolbar needs extra space for the UA input, then set a class that
             // will accomodate its height. We should also make sure to keep the width
             // value we're toggling against in sync with the media-query in
@@ -581,18 +577,16 @@ class ResponsiveUI {
         this.onScreenshot();
         break;
       case "toggle-left-alignment":
         this.onToggleLeftAlignment(event);
         break;
       case "update-device-modal":
         this.onUpdateDeviceModal(event);
         break;
-      case "update-device-toolbar-height":
-        this.onUpdateToolbarHeight(event);
     }
   }
 
   async onChangeDevice(event) {
     const { pixelRatio, touch, userAgent } = event.data.device;
     let reloadNeeded = false;
     await this.updateDPPX(pixelRatio);
 
@@ -802,60 +796,34 @@ class ResponsiveUI {
     }
   }
 
   onToggleLeftAlignment(event) {
     this.updateUIAlignment(event.data.leftAlignmentEnabled);
   }
 
   onUpdateDeviceModal(event) {
-    // Restore the toolbar height if closing
-    if (!event.data.isOpen) {
-      this.restoreToolbarHeight();
-    }
-  }
-
-  /**
-   * Handles setting the height of the toolbar when it's closed. This can happen when
-   * an event occurs outside of the device selector menu component, such as opening the
-   * device modal.
-   */
-  onUpdateToolbarHeight(event) {
-    if (!event.data.isOpen) {
-      const {
-        isModalOpen,
-      } = this.rdmFrame.contentWindow.store.getState().devices;
-
-      // Don't remove the device-selector-menu-opened class if it was closed because
-      // the device modal was opened. We still want to preserve the current height of
-      // toolbar.
-      if (isModalOpen) {
-        return;
-      }
-
-      this.restoreToolbarHeight();
+    if (event.data.isOpen) {
+      this.browserStackEl.classList.add("device-modal-opened");
+      const style = this.browserWindow.getComputedStyle(this.browserStackEl);
+      this.rdmFrame.style.height = style.height;
+    } else {
+      this.rdmFrame.style.removeProperty("height");
+      this.browserStackEl.classList.remove("device-modal-opened");
     }
   }
 
   async hasDeviceState() {
     const deviceState = await asyncStorage.getItem(
       "devtools.responsive.deviceState"
     );
     return !!deviceState;
   }
 
   /**
-   * Restores the toolbar's height to it's original class styling.
-   */
-  restoreToolbarHeight() {
-    this.rdmFrame.style.removeProperty("height");
-    this.browserStackEl.classList.remove("device-selector-menu-opened");
-  }
-
-  /**
    * Restores the previous UI state.
    */
   async restoreUIState() {
     // Restore UI alignment.
     if (this.isBrowserUIEnabled) {
       const leftAlignmentEnabled = Services.prefs.getBoolPref(
         "devtools.responsive.leftAlignViewport.enabled",
         false
--- a/devtools/client/shared/widgets/tooltip/HTMLTooltip.js
+++ b/devtools/client/shared/widgets/tooltip/HTMLTooltip.js
@@ -369,17 +369,17 @@ function HTMLTooltip(
   this.doc = toolboxDoc;
   this.id = id;
   this.className = className;
   this.type = type;
   this.noAutoHide = noAutoHide;
   // consumeOutsideClicks cannot be used if the tooltip is not closed on click
   this.consumeOutsideClicks = this.noAutoHide ? false : consumeOutsideClicks;
   this.isMenuTooltip = isMenuTooltip;
-  this.useXulWrapper = this._isXUL() && useXulWrapper;
+  this.useXulWrapper = this._isXULPopupAvailable() && useXulWrapper;
   this.preferredWidth = "auto";
   this.preferredHeight = "auto";
 
   // The top window is used to attach click event listeners to close the tooltip if the
   // user clicks on the content page.
   this.topWindow = this._getTopWindow();
 
   this._position = null;
@@ -399,17 +399,17 @@ function HTMLTooltip(
     //     <div> <! the actual tooltip.container element -->
     this.xulPanelWrapper = this._createXulPanelWrapper();
     const inner = this.doc.createElementNS(XHTML_NS, "div");
     inner.classList.add("tooltip-xul-wrapper-inner");
 
     this.doc.documentElement.appendChild(this.xulPanelWrapper);
     this.xulPanelWrapper.appendChild(inner);
     inner.appendChild(this.container);
-  } else if (this._isXUL()) {
+  } else if (this._hasXULRootElement()) {
     this.doc.documentElement.appendChild(this.container);
   } else {
     // In non-XUL context the container is ready to use as is.
     this.doc.body.appendChild(this.container);
   }
 }
 
 module.exports.HTMLTooltip = HTMLTooltip;
@@ -974,22 +974,26 @@ HTMLTooltip.prototype = {
     return focusableElements.length !== 0;
   },
 
   _getTopWindow: function() {
     return DevToolsUtils.getTopWindow(this.doc.defaultView);
   },
 
   /**
-   * Check if the tooltip's owner document is a XUL document.
+   * Check if the tooltip's owner document has XUL root element.
    */
-  _isXUL: function() {
+  _hasXULRootElement: function() {
     return this.doc.documentElement.namespaceURI === XUL_NS;
   },
 
+  _isXULPopupAvailable: function() {
+    return this.doc.nodePrincipal.isSystemPrincipal;
+  },
+
   _createXulPanelWrapper: function() {
     const panel = this.doc.createXULElement("panel");
 
     // XUL panel is only a way to display DOM elements outside of the document viewport,
     // so disable all features that impact the behavior.
     panel.setAttribute("animate", false);
     panel.setAttribute("consumeoutsideclicks", false);
     panel.setAttribute("incontentshell", false);
--- a/devtools/client/themes/toolbox.css
+++ b/devtools/client/themes/toolbox.css
@@ -4,54 +4,83 @@
 
 /*
  *  Debug Target Info layout
  *  +------------+--------------+------------------------+
  *  | connection | runtime info | target info icon + text |
  *  +------------+--------------+------------------------+
  */
 .debug-target-info {
+  --border-inline-end-width: 1px;
+  --padding-inline-end-size: 24px;
   display: flex;
   background: var(--theme-tab-toolbar-background);
   border-bottom: 1px solid var(--theme-splitter-color);
   padding: 4px 0;
-  font-size: 1.2em;
+  font-size: 1.35em;
   color: var(--theme-toolbar-color);
 }
 
 /*
  *  Debug Target labels with icon layout
  *  +------+------------------------+---------------+
  *  | icon | label text (bold)      | optional text |
  *  | 20px |     max-content        | max-content   |
  *  +------+------------------------+---------------+
  */
 .debug-target-info .iconized-label {
   display: grid;
   grid-template-columns: 20px auto auto;
   grid-column-gap: 8px;
   align-items: center;
-  padding: 0 24px;
+  padding: 0 var(--padding-inline-end-size);
   white-space: nowrap;
 }
 
 .debug-target-info .iconized-label:not(:last-child) {
-  border-inline-end: 1px solid var(--theme-splitter-color);
+  border-inline-end: var(--border-inline-end-width) solid var(--theme-splitter-color);
 }
 
 .debug-target-info .iconized-label img {
   width: 20px;
   height: 20px;
 }
 
 .debug-target-info img {
   -moz-context-properties: fill;
   fill: var(--theme-toolbar-color);
 }
 
+
+/* DebugTargetInfo's renderTargetTitle() component should look like it's in the same
+  section as renderTargetURI() */
+.debug-target-info .debug-target-title {
+  --border-inline-end-width: 0;
+  padding-inline-end: 0;
+}
+
+.debug-target-info .debug-target-url {
+  display: flex;
+  flex-grow: 1;
+  padding-inline-end: var(--padding-inline-end-size);
+  align-self: center;
+}
+
+.debug-target-info .debug-target-url-input {
+  border: 1px solid var(--theme-toolbarbutton-active-background);
+  border-radius: 2px;
+  height: 20px;
+  padding-inline-start: 10px;
+}
+
+.debug-target-info .debug-target-url-input,
+.debug-target-info .debug-target-url-form {
+  width: 100%;
+}
+
 /* Toolbox tabbar */
 
 .devtools-tabbar {
   -moz-appearance: none;
   /* For narrow devtool width, we define the each column width of tabbar.
     Defined layout is as follow:
 
     -------------------------------------------------
--- a/devtools/client/themes/tooltips.css
+++ b/devtools/client/themes/tooltips.css
@@ -128,17 +128,23 @@ strong {
 .tooltip-xul-wrapper {
   -moz-appearance: none;
   background: transparent;
   overflow: visible;
   border-style: none;
 }
 
 .tooltip-xul-wrapper .tooltip-container {
-  position: absolute;
+  /**
+   * As the width/height of xul:panel (.tooltip-xul-wrapper) is dependent on the
+   * width/height of the inner content. Thus, if "absolute" position here, as the
+   * width/height that are conveyed to the panel will be zero, the tooltip will be
+   * invisible.
+   */
+  position: static;
 }
 
 .tooltip-top {
   flex-direction: column;
 }
 
 .tooltip-bottom {
   flex-direction: column-reverse;
--- a/devtools/client/webconsole/test/node/mocha-test-setup.js
+++ b/devtools/client/webconsole/test/node/mocha-test-setup.js
@@ -91,16 +91,22 @@ if (!global.ResizeObserver) {
 // Mock ChromeUtils.
 global.ChromeUtils = {
   import: () => {},
   defineModuleGetter: () => {},
 };
 
 global.define = function() {};
 
+// Used for the HTMLTooltip component.
+// And set "isSystemPrincipal: false" because can't support XUL element in node.
+global.document.nodePrincipal = {
+  isSystemPrincipal: false,
+};
+
 // Point to vendored-in files and mocks when needed.
 const requireHacker = require("require-hacker");
 requireHacker.global_hook("default", (path, module) => {
   const paths = {
     // For Enzyme
     "react-dom": () => getModule("devtools/client/shared/vendor/react-dom"),
     "react-dom/server": () =>
       getModule("devtools/client/shared/vendor/react-dom-server"),
--- a/docshell/base/BrowsingContext.cpp
+++ b/docshell/base/BrowsingContext.cpp
@@ -302,33 +302,32 @@ already_AddRefed<BrowsingContext> Browsi
 
   const auto defaultLoadFlags =
       inherit ? inherit->GetDefaultLoadFlags() : nsIRequest::LOAD_NORMAL;
   context->mFields.SetWithoutSyncing<IDX_DefaultLoadFlags>(defaultLoadFlags);
 
   context->mFields.SetWithoutSyncing<IDX_OrientationLock>(
       mozilla::hal::eScreenOrientation_None);
 
+  const bool useGlobalHistory =
+      inherit ? inherit->GetUseGlobalHistory() : false;
+  context->mFields.SetWithoutSyncing<IDX_UseGlobalHistory>(useGlobalHistory);
+
   return context.forget();
 }
 
 already_AddRefed<BrowsingContext> BrowsingContext::CreateIndependent(
     Type aType) {
   RefPtr<BrowsingContext> bc(
       CreateDetached(nullptr, nullptr, EmptyString(), aType));
   bc->mWindowless = bc->IsContent();
   bc->EnsureAttached();
   return bc.forget();
 }
 
-void BrowsingContext::SetWindowless() {
-  MOZ_DIAGNOSTIC_ASSERT(!mEverAttached);
-  mWindowless = true;
-}
-
 void BrowsingContext::EnsureAttached() {
   if (!mEverAttached) {
     Register(this);
 
     // Attach the browsing context to the tree.
     Attach(/* aFromIPC */ false, /* aOriginProcess */ nullptr);
   }
 }
@@ -472,16 +471,20 @@ void BrowsingContext::SetEmbedderElement
     }
     if (XRE_IsParentProcess() && IsTopContent()) {
       nsAutoString messageManagerGroup;
       if (aEmbedder->IsXULElement()) {
         aEmbedder->GetAttr(kNameSpaceID_None, nsGkAtoms::messagemanagergroup,
                            messageManagerGroup);
       }
       txn.SetMessageManagerGroup(messageManagerGroup);
+
+      bool useGlobalHistory = !aEmbedder->HasAttr(
+          kNameSpaceID_None, nsGkAtoms::disableglobalhistory);
+      txn.SetUseGlobalHistory(useGlobalHistory);
     }
     txn.Commit(this);
   }
 
   mEmbedderElement = aEmbedder;
 }
 
 void BrowsingContext::Embed() {
@@ -503,16 +506,18 @@ void BrowsingContext::Attach(bool aFromI
              XRE_IsParentProcess() ? "Parent" : "Child", Id(),
              GetParent() ? GetParent()->Id() : 0, (int)mPrivateBrowsingId,
              (int)mUseRemoteTabs, (int)mUseRemoteSubframes, suffix.get()));
   }
 
   MOZ_DIAGNOSTIC_ASSERT(mGroup);
   MOZ_DIAGNOSTIC_ASSERT(!mIsDiscarded);
 
+  AssertCoherentLoadContext();
+
   // Add ourselves either to our parent or BrowsingContextGroup's child list.
   if (mParentWindow) {
     mParentWindow->AppendChildBrowsingContext(this);
   } else {
     mGroup->Toplevels().AppendElement(this);
   }
 
   if (GetIsPopupSpam()) {
@@ -1312,16 +1317,48 @@ nsresult BrowsingContext::SetOriginAttri
         nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID;
   }
   SetPrivateBrowsing(isPrivate);
   AssertOriginAttributesMatchPrivateBrowsing();
 
   return NS_OK;
 }
 
+void BrowsingContext::AssertCoherentLoadContext() {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+  // LoadContext should generally match our opener or parent.
+  if (RefPtr<BrowsingContext> opener = GetOpener()) {
+    MOZ_DIAGNOSTIC_ASSERT(opener->mType == mType);
+    MOZ_DIAGNOSTIC_ASSERT(opener->mGroup == mGroup);
+    MOZ_DIAGNOSTIC_ASSERT(opener->mUseRemoteTabs == mUseRemoteTabs);
+    MOZ_DIAGNOSTIC_ASSERT(opener->mUseRemoteSubframes == mUseRemoteSubframes);
+    MOZ_DIAGNOSTIC_ASSERT(opener->mPrivateBrowsingId == mPrivateBrowsingId);
+    MOZ_DIAGNOSTIC_ASSERT(
+        opener->mOriginAttributes.EqualsIgnoringFPD(mOriginAttributes));
+  }
+  if (RefPtr<BrowsingContext> parent = GetParent()) {
+    MOZ_DIAGNOSTIC_ASSERT(parent->mType == mType);
+    MOZ_DIAGNOSTIC_ASSERT(parent->mGroup == mGroup);
+    MOZ_DIAGNOSTIC_ASSERT(parent->mUseRemoteTabs == mUseRemoteTabs);
+    MOZ_DIAGNOSTIC_ASSERT(parent->mUseRemoteSubframes == mUseRemoteSubframes);
+    MOZ_DIAGNOSTIC_ASSERT(parent->mPrivateBrowsingId == mPrivateBrowsingId);
+    MOZ_DIAGNOSTIC_ASSERT(
+        parent->mOriginAttributes.EqualsIgnoringFPD(mOriginAttributes));
+  }
+
+  // UseRemoteSubframes and UseRemoteTabs must match.
+  MOZ_DIAGNOSTIC_ASSERT(
+      !mUseRemoteSubframes || mUseRemoteTabs,
+      "Cannot set useRemoteSubframes without also setting useRemoteTabs");
+
+  // Double-check OriginAttributes/Private Browsing
+  AssertOriginAttributesMatchPrivateBrowsing();
+#endif
+}
+
 void BrowsingContext::AssertOriginAttributesMatchPrivateBrowsing() {
   // Chrome browsing contexts must not have a private browsing OriginAttribute
   // Content browsing contexts must maintain the equality:
   // mOriginAttributes.mPrivateBrowsingId == mPrivateBrowsingId
   if (IsChrome()) {
     MOZ_DIAGNOSTIC_ASSERT(mOriginAttributes.mPrivateBrowsingId == 0);
   } else {
     MOZ_DIAGNOSTIC_ASSERT(mOriginAttributes.mPrivateBrowsingId ==
@@ -1913,16 +1950,24 @@ void BrowsingContext::DidSet(FieldIndex<
     PreOrderWalk([&](BrowsingContext* aContext) {
       if (aContext != this) {
         aContext->SetDefaultLoadFlags(loadFlags);
       }
     });
   }
 }
 
+bool BrowsingContext::CanSet(FieldIndex<IDX_UseGlobalHistory>,
+                             const bool& aUseGlobalHistory,
+                             ContentParent* aSource) {
+  // Should only be set in the parent process.
+  //  return XRE_IsParentProcess() && !aSource;
+  return true;
+}
+
 bool BrowsingContext::CanSet(FieldIndex<IDX_UserAgentOverride>,
                              const nsString& aUserAgent,
                              ContentParent* aSource) {
   if (!IsTop()) {
     return false;
   }
 
   return CheckOnlyOwningProcessCanSet(aSource);
--- a/docshell/base/BrowsingContext.h
+++ b/docshell/base/BrowsingContext.h
@@ -110,16 +110,17 @@ class WindowProxyHolder;
   FIELD(HistoryID, nsID)                                                     \
   FIELD(InRDMPane, bool)                                                     \
   FIELD(Loading, bool)                                                       \
   FIELD(AncestorLoading, bool)                                               \
   FIELD(AllowPlugins, bool)                                                  \
   FIELD(AllowContentRetargeting, bool)                                       \
   FIELD(AllowContentRetargetingOnChildren, bool)                             \
   FIELD(ForceEnableTrackingProtection, bool)                                 \
+  FIELD(UseGlobalHistory, bool)                                              \
   /* These field are used to store the states of autoplay media request on   \
    * GeckoView only, and it would only be modified on the top level browsing \
    * context. */                                                             \
   FIELD(GVAudibleAutoplayRequestStatus, GVAutoplayRequestStatus)             \
   FIELD(GVInaudibleAutoplayRequestStatus, GVAutoplayRequestStatus)           \
   /* ScreenOrientation-related APIs */                                       \
   FIELD(CurrentOrientationAngle, float)                                      \
   FIELD(CurrentOrientationType, mozilla::dom::OrientationType)               \
@@ -214,17 +215,16 @@ class BrowsingContext : public nsILoadCo
   }
 
   // Has this BrowsingContext been discarded. A discarded browsing context has
   // been destroyed, and may not be available on the other side of an IPC
   // message.
   bool IsDiscarded() const { return mIsDiscarded; }
 
   bool Windowless() const { return mWindowless; }
-  void SetWindowless();
 
   // Get the DocShell for this BrowsingContext if it is in-process, or
   // null if it's not.
   nsIDocShell* GetDocShell() const { return mDocShell; }
   void SetDocShell(nsIDocShell* aDocShell);
   void ClearDocShell() { mDocShell = nullptr; }
 
   // Get the Document for this BrowsingContext if it is in-process, or
@@ -380,16 +380,18 @@ class BrowsingContext : public nsILoadCo
   void GetOriginAttributes(JSContext* aCx, JS::MutableHandle<JS::Value> aVal,
                            ErrorResult& aError);
 
   bool InRDMPane() const { return GetInRDMPane(); }
 
   float FullZoom() const { return GetFullZoom(); }
   float TextZoom() const { return GetTextZoom(); }
 
+  bool UseGlobalHistory() const { return GetUseGlobalHistory(); }
+
   bool IsLoading();
 
   // ScreenOrientation related APIs
   void SetCurrentOrientation(OrientationType aType, float aAngle) {
     SetCurrentOrientationType(aType);
     SetCurrentOrientationAngle(aAngle);
   }
 
@@ -611,16 +613,20 @@ class BrowsingContext : public nsILoadCo
                                        BrowsingContext& aRequestingContext);
 
   // Is it early enough in the BrowsingContext's lifecycle that it is still
   // OK to set OriginAttributes?
   bool CanSetOriginAttributes();
 
   void AssertOriginAttributesMatchPrivateBrowsing();
 
+  // Assert that the BrowsingContext's LoadContext flags appear coherent
+  // relative to related BrowsingContexts.
+  void AssertCoherentLoadContext();
+
   friend class ::nsOuterWindowProxy;
   friend class ::nsGlobalWindowOuter;
   friend class WindowContext;
 
   // Update the window proxy object that corresponds to this browsing context.
   // This should be called from the window proxy object's objectMoved hook, if
   // the object mWindowProxy points to was moved by the JS GC.
   void UpdateWindowProxy(JSObject* obj, JSObject* old) {
@@ -726,16 +732,19 @@ class BrowsingContext : public nsILoadCo
               ContentParent* aSource);
   bool CanSet(FieldIndex<IDX_WatchedByDevtools>, const bool& aWatchedByDevtools,
               ContentParent* aSource);
 
   bool CanSet(FieldIndex<IDX_DefaultLoadFlags>,
               const uint32_t& aDefaultLoadFlags, ContentParent* aSource);
   void DidSet(FieldIndex<IDX_DefaultLoadFlags>);
 
+  bool CanSet(FieldIndex<IDX_UseGlobalHistory>, const bool& aUseGlobalHistory,
+              ContentParent* aSource);
+
   template <size_t I, typename T>
   bool CanSet(FieldIndex<I>, const T&, ContentParent*) {
     return true;
   }
 
   // Overload `DidSet` to get notifications for a particular field being set.
   //
   // You can also overload the variant that gets the old value if you need it.
--- a/docshell/base/CanonicalBrowsingContext.cpp
+++ b/docshell/base/CanonicalBrowsingContext.cpp
@@ -398,28 +398,20 @@ void CanonicalBrowsingContext::PendingRe
       if (!wasRemote) {
         resetInFlightId();
       }
     };
     embedderWindow->SendMakeFrameRemote(target, std::move(endpoint), tabId,
                                         callback, callback);
   }
 
-  // FIXME: We should get the correct principal for the to-be-created window so
-  // we can avoid creating unnecessary extra windows in the new process.
-  OriginAttributes attrs = embedderBrowser->OriginAttributesRef();
-  RefPtr<nsIPrincipal> principal = embedderBrowser->GetContentPrincipal();
-  if (principal) {
-    attrs.SetFirstPartyDomain(
-        true, principal->OriginAttributesRef().mFirstPartyDomain);
-  }
-
   nsCOMPtr<nsIPrincipal> initialPrincipal =
-      NullPrincipal::CreateWithInheritedAttributes(attrs,
-                                                   /* isFirstParty */ false);
+      NullPrincipal::CreateWithInheritedAttributes(
+          target->OriginAttributesRef(),
+          /* isFirstParty */ false);
   WindowGlobalInit windowInit =
       WindowGlobalActor::AboutBlankInitializer(target, initialPrincipal);
 
   // Actually create the new BrowserParent actor and finish initialization of
   // our new BrowserBridgeParent.
   nsresult rv = bridge->InitWithProcess(aContentParent, EmptyString(),
                                         windowInit, chromeFlags, tabId);
   if (NS_WARN_IF(NS_FAILED(rv))) {
--- a/docshell/base/ChildProcessChannelListener.cpp
+++ b/docshell/base/ChildProcessChannelListener.cpp
@@ -12,37 +12,36 @@ namespace mozilla {
 namespace dom {
 
 static StaticRefPtr<ChildProcessChannelListener> sCPCLSingleton;
 
 void ChildProcessChannelListener::RegisterCallback(uint64_t aIdentifier,
                                                    Callback&& aCallback) {
   if (auto args = mChannelArgs.GetAndRemove(aIdentifier)) {
     nsresult rv =
-        aCallback(args->mLoadState, std::move(args->mRedirects),
-                  std::move(args->mStreamFilterEndpoints), args->mTiming);
+        aCallback(args->mLoadState, std::move(args->mStreamFilterEndpoints),
+                  args->mTiming);
     args->mResolver(rv);
   } else {
     mCallbacks.Put(aIdentifier, std::move(aCallback));
   }
 }
 
 void ChildProcessChannelListener::OnChannelReady(
     nsDocShellLoadState* aLoadState, uint64_t aIdentifier,
-    nsTArray<net::DocumentChannelRedirect>&& aRedirects,
     nsTArray<Endpoint>&& aStreamFilterEndpoints, nsDOMNavigationTiming* aTiming,
     Resolver&& aResolver) {
   if (auto callback = mCallbacks.GetAndRemove(aIdentifier)) {
-    nsresult rv = (*callback)(aLoadState, std::move(aRedirects),
-                              std::move(aStreamFilterEndpoints), aTiming);
+    nsresult rv =
+        (*callback)(aLoadState, std::move(aStreamFilterEndpoints), aTiming);
     aResolver(rv);
   } else {
-    mChannelArgs.Put(aIdentifier, {aLoadState, std::move(aRedirects),
-                                   std::move(aStreamFilterEndpoints), aTiming,
-                                   std::move(aResolver)});
+    mChannelArgs.Put(aIdentifier,
+                     {aLoadState, std::move(aStreamFilterEndpoints), aTiming,
+                      std::move(aResolver)});
   }
 }
 
 already_AddRefed<ChildProcessChannelListener>
 ChildProcessChannelListener::GetSingleton() {
   if (!sCPCLSingleton) {
     sCPCLSingleton = new ChildProcessChannelListener();
     ClearOnShutdown(&sCPCLSingleton);
--- a/docshell/base/ChildProcessChannelListener.h
+++ b/docshell/base/ChildProcessChannelListener.h
@@ -20,34 +20,31 @@ namespace mozilla {
 namespace dom {
 
 class ChildProcessChannelListener final {
   NS_INLINE_DECL_REFCOUNTING(ChildProcessChannelListener)
 
   using Endpoint = mozilla::ipc::Endpoint<extensions::PStreamFilterParent>;
   using Resolver = std::function<void(const nsresult&)>;
   using Callback = std::function<nsresult(
-      nsDocShellLoadState*, nsTArray<net::DocumentChannelRedirect>&&,
-      nsTArray<Endpoint>&&, nsDOMNavigationTiming*)>;
+      nsDocShellLoadState*, nsTArray<Endpoint>&&, nsDOMNavigationTiming*)>;
 
   void RegisterCallback(uint64_t aIdentifier, Callback&& aCallback);
 
   void OnChannelReady(nsDocShellLoadState* aLoadState, uint64_t aIdentifier,
-                      nsTArray<net::DocumentChannelRedirect>&& aRedirects,
                       nsTArray<Endpoint>&& aStreamFilterEndpoints,
                       nsDOMNavigationTiming* aTiming, Resolver&& aResolver);
 
   static already_AddRefed<ChildProcessChannelListener> GetSingleton();
 
  private:
   ChildProcessChannelListener() = default;
   ~ChildProcessChannelListener() = default;
   struct CallbackArgs {
     RefPtr<nsDocShellLoadState> mLoadState;
-    nsTArray<net::DocumentChannelRedirect> mRedirects;
     nsTArray<Endpoint> mStreamFilterEndpoints;
     RefPtr<nsDOMNavigationTiming> mTiming;
     Resolver mResolver;
   };
 
   // TODO Backtrack.
   nsDataHashtable<nsUint64HashKey, Callback> mCallbacks;
   nsDataHashtable<nsUint64HashKey, CallbackArgs> mChannelArgs;
--- a/docshell/base/LoadContext.cpp
+++ b/docshell/base/LoadContext.cpp
@@ -5,23 +5,50 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/Assertions.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/LoadContext.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/ScriptSettings.h"  // for AutoJSAPI
+#include "mozilla/dom/BrowsingContext.h"
 #include "nsContentUtils.h"
 #include "xpcpublic.h"
 
 namespace mozilla {
 
 NS_IMPL_ISUPPORTS(LoadContext, nsILoadContext, nsIInterfaceRequestor)
 
+LoadContext::LoadContext(const IPC::SerializedLoadContext& aToCopy,
+                         dom::Element* aTopFrameElement,
+                         OriginAttributes& aAttrs)
+    : mTopFrameElement(do_GetWeakReference(aTopFrameElement)),
+      mIsContent(aToCopy.mIsContent),
+      mUseRemoteTabs(aToCopy.mUseRemoteTabs),
+      mUseRemoteSubframes(aToCopy.mUseRemoteSubframes),
+      mUseTrackingProtection(aToCopy.mUseTrackingProtection),
+#ifdef DEBUG
+      mIsNotNull(aToCopy.mIsNotNull),
+#endif
+      mOriginAttributes(aAttrs) {
+}
+
+LoadContext::LoadContext(OriginAttributes& aAttrs)
+    : mTopFrameElement(nullptr),
+      mIsContent(false),
+      mUseRemoteTabs(false),
+      mUseRemoteSubframes(false),
+      mUseTrackingProtection(false),
+#ifdef DEBUG
+      mIsNotNull(true),
+#endif
+      mOriginAttributes(aAttrs) {
+}
+
 LoadContext::LoadContext(nsIPrincipal* aPrincipal,
                          nsILoadContext* aOptionalBase)
     : mTopFrameElement(nullptr),
       mIsContent(true),
       mUseRemoteTabs(false),
       mUseRemoteSubframes(false),
       mUseTrackingProtection(false),
 #ifdef DEBUG
@@ -35,16 +62,18 @@ LoadContext::LoadContext(nsIPrincipal* a
   MOZ_ALWAYS_SUCCEEDS(aOptionalBase->GetIsContent(&mIsContent));
   MOZ_ALWAYS_SUCCEEDS(aOptionalBase->GetUseRemoteTabs(&mUseRemoteTabs));
   MOZ_ALWAYS_SUCCEEDS(
       aOptionalBase->GetUseRemoteSubframes(&mUseRemoteSubframes));
   MOZ_ALWAYS_SUCCEEDS(
       aOptionalBase->GetUseTrackingProtection(&mUseTrackingProtection));
 }
 
+LoadContext::~LoadContext() = default;
+
 //-----------------------------------------------------------------------------
 // LoadContext::nsILoadContext
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 LoadContext::GetAssociatedWindow(mozIDOMWindowProxy**) {
   MOZ_ASSERT(mIsNotNull);
 
--- a/docshell/base/LoadContext.h
+++ b/docshell/base/LoadContext.h
@@ -17,76 +17,39 @@
 
 namespace mozilla {
 
 /**
  * Class that provides nsILoadContext info in Parent process.  Typically copied
  * from Child via SerializedLoadContext.
  *
  * Note: this is not the "normal" or "original" nsILoadContext.  That is
- * typically provided by nsDocShell.  This is only used when the original
+ * typically provided by BrowsingContext.  This is only used when the original
  * docshell is in a different process and we need to copy certain values from
  * it.
  */
 
 class LoadContext final : public nsILoadContext, public nsIInterfaceRequestor {
  public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSILOADCONTEXT
   NS_DECL_NSIINTERFACEREQUESTOR
 
   LoadContext(const IPC::SerializedLoadContext& aToCopy,
-              dom::Element* aTopFrameElement, OriginAttributes& aAttrs)
-      : mTopFrameElement(do_GetWeakReference(aTopFrameElement)),
-        mIsContent(aToCopy.mIsContent),
-        mUseRemoteTabs(aToCopy.mUseRemoteTabs),
-        mUseRemoteSubframes(aToCopy.mUseRemoteSubframes),
-        mUseTrackingProtection(aToCopy.mUseTrackingProtection),
-#ifdef DEBUG
-        mIsNotNull(aToCopy.mIsNotNull),
-#endif
-        mOriginAttributes(aAttrs) {
-  }
-
-  LoadContext(dom::Element* aTopFrameElement, bool aIsContent,
-              bool aUsePrivateBrowsing, bool aUseRemoteTabs,
-              bool aUseRemoteSubframes, bool aUseTrackingProtection,
-              const OriginAttributes& aAttrs)
-      : mTopFrameElement(do_GetWeakReference(aTopFrameElement)),
-        mIsContent(aIsContent),
-        mUseRemoteTabs(aUseRemoteTabs),
-        mUseRemoteSubframes(aUseRemoteSubframes),
-        mUseTrackingProtection(aUseTrackingProtection),
-#ifdef DEBUG
-        mIsNotNull(true),
-#endif
-        mOriginAttributes(aAttrs) {
-    MOZ_DIAGNOSTIC_ASSERT(aUsePrivateBrowsing ==
-                          (aAttrs.mPrivateBrowsingId > 0));
-  }
+              dom::Element* aTopFrameElement, OriginAttributes& aAttrs);
 
   // Constructor taking reserved origin attributes.
-  explicit LoadContext(OriginAttributes& aAttrs)
-      : mTopFrameElement(nullptr),
-        mIsContent(false),
-        mUseRemoteTabs(false),
-        mUseRemoteSubframes(false),
-        mUseTrackingProtection(false),
-#ifdef DEBUG
-        mIsNotNull(true),
-#endif
-        mOriginAttributes(aAttrs) {
-  }
+  explicit LoadContext(OriginAttributes& aAttrs);
 
   // Constructor for creating a LoadContext with a given browser flag.
   explicit LoadContext(nsIPrincipal* aPrincipal,
                        nsILoadContext* aOptionalBase = nullptr);
 
  private:
-  ~LoadContext() {}
+  ~LoadContext();
 
   nsWeakPtr mTopFrameElement;
   bool mIsContent;
   bool mUseRemoteTabs;
   bool mUseRemoteSubframes;
   bool mUseTrackingProtection;
 #ifdef DEBUG
   bool mIsNotNull;
--- a/docshell/base/WindowContext.cpp
+++ b/docshell/base/WindowContext.cpp
@@ -103,43 +103,63 @@ void WindowContext::SendCommitTransactio
 }
 
 void WindowContext::SendCommitTransaction(ContentChild* aChild,
                                           const BaseTransaction& aTxn,
                                           uint64_t aEpoch) {
   aChild->SendCommitWindowContextTransaction(this, aTxn, aEpoch);
 }
 
+bool WindowContext::CheckOnlyOwningProcessCanSet(ContentParent* aSource) {
+  if (mInProcess) {
+    return true;
+  }
+
+  if (XRE_IsParentProcess() && aSource) {
+    return Canonical()->GetContentParent() == aSource;
+  }
+
+  return false;
+}
+
 bool WindowContext::CanSet(FieldIndex<IDX_AllowMixedContent>,
                            const bool& aAllowMixedContent,
                            ContentParent* aSource) {
-  return mBrowsingContext->CheckOnlyOwningProcessCanSet(aSource);
+  return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(
+    FieldIndex<IDX_CookieJarSettings>,
+    const Maybe<mozilla::net::CookieJarSettingsArgs>& aValue,
+    ContentParent* aSource) {
+  return CheckOnlyOwningProcessCanSet(aSource);
 }
 
 bool WindowContext::CanSet(FieldIndex<IDX_IsThirdPartyWindow>,
                            const bool& IsThirdPartyWindow,
                            ContentParent* aSource) {
-  return mBrowsingContext->CheckOnlyOwningProcessCanSet(aSource);
+  return CheckOnlyOwningProcessCanSet(aSource);
 }
 
 bool WindowContext::CanSet(FieldIndex<IDX_IsThirdPartyTrackingResourceWindow>,
                            const bool& aIsThirdPartyTrackingResourceWindow,
                            ContentParent* aSource) {
-  return mBrowsingContext->CheckOnlyOwningProcessCanSet(aSource);
+  return CheckOnlyOwningProcessCanSet(aSource);
 }
 
 already_AddRefed<WindowContext> WindowContext::Create(
     WindowGlobalChild* aWindow) {
   MOZ_RELEASE_ASSERT(XRE_IsContentProcess(),
                      "Should be a WindowGlobalParent in the parent");
 
   FieldTuple init;
   mozilla::Get<IDX_OuterWindowId>(init) = aWindow->OuterWindowId();
-  RefPtr<WindowContext> context = new WindowContext(
-      aWindow->BrowsingContext(), aWindow->InnerWindowId(), std::move(init));
+  RefPtr<WindowContext> context =
+      new WindowContext(aWindow->BrowsingContext(), aWindow->InnerWindowId(),
+                        /* aInProcess */ true, std::move(init));
   context->Init();
   return context.forget();
 }
 
 void WindowContext::CreateFromIPC(IPCInitializer&& aInit) {
   MOZ_RELEASE_ASSERT(XRE_IsContentProcess(),
                      "Should be a WindowGlobalParent in the parent");
 
@@ -149,17 +169,18 @@ void WindowContext::CreateFromIPC(IPCIni
   if (bc->IsDiscarded()) {
     // If we have already closed our browsing context, the
     // WindowGlobalChild actor is bound to be destroyed soon and it's
     // safe to ignore creating the WindowContext.
     return;
   }
 
   RefPtr<WindowContext> context =
-      new WindowContext(bc, aInit.mInnerWindowId, std::move(aInit.mFields));
+      new WindowContext(bc, aInit.mInnerWindowId, /* aInProcess */ false,
+                        std::move(aInit.mFields));
   context->Init();
 }
 
 void WindowContext::Init() {
   MOZ_LOG(GetLog(), LogLevel::Debug,
           ("Registering 0x%" PRIx64 " (bc=0x%" PRIx64 ")", mInnerWindowId,
            mBrowsingContext->Id()));
 
@@ -187,20 +208,22 @@ void WindowContext::Discard() {
 
   mIsDiscarded = true;
   gWindowContexts->Remove(InnerWindowId());
   mBrowsingContext->UnregisterWindowContext(this);
   Group()->Unregister(this);
 }
 
 WindowContext::WindowContext(BrowsingContext* aBrowsingContext,
-                             uint64_t aInnerWindowId, FieldTuple&& aFields)
+                             uint64_t aInnerWindowId, bool aInProcess,
+                             FieldTuple&& aFields)
     : mFields(std::move(aFields)),
       mInnerWindowId(aInnerWindowId),
-      mBrowsingContext(aBrowsingContext) {
+      mBrowsingContext(aBrowsingContext),
+      mInProcess(aInProcess) {
   MOZ_ASSERT(mBrowsingContext);
   MOZ_ASSERT(mInnerWindowId);
 }
 
 WindowContext::~WindowContext() {
   if (gWindowContexts) {
     gWindowContexts->Remove(InnerWindowId());
   }
--- a/docshell/base/WindowContext.h
+++ b/docshell/base/WindowContext.h
@@ -44,16 +44,18 @@ class WindowContext : public nsISupports
   BrowsingContextGroup* Group() const;
   uint64_t Id() const { return InnerWindowId(); }
   uint64_t InnerWindowId() const { return mInnerWindowId; }
   uint64_t OuterWindowId() const { return GetOuterWindowId(); }
   bool IsDiscarded() const { return mIsDiscarded; }
 
   bool IsCached() const;
 
+  bool IsInProcess() { return mInProcess; }
+
   // Get the parent WindowContext of this WindowContext, taking the BFCache into
   // account. This will not cross chrome/content <browser> boundaries.
   WindowContext* GetParentWindowContext();
   WindowContext* TopWindowContext();
 
   Span<RefPtr<BrowsingContext>> Children() { return mChildren; }
 
   // Cast this object to it's parent-process canonical form.
@@ -81,48 +83,47 @@ class WindowContext : public nsISupports
     return {mInnerWindowId, mBrowsingContext->Id(), mFields.Fields()};
   }
 
   static already_AddRefed<WindowContext> Create(WindowGlobalChild* aWindow);
   static void CreateFromIPC(IPCInitializer&& aInit);
 
  protected:
   WindowContext(BrowsingContext* aBrowsingContext, uint64_t aInnerWindowId,
-                FieldTuple&& aFields);
+                bool aInProcess, FieldTuple&& aFields);
   virtual ~WindowContext();
 
   void Init();
 
  private:
   friend class BrowsingContext;
 
   void AppendChildBrowsingContext(BrowsingContext* aBrowsingContext);
   void RemoveChildBrowsingContext(BrowsingContext* aBrowsingContext);
 
   // Send a given `BaseTransaction` object to the correct remote.
   void SendCommitTransaction(ContentParent* aParent,
                              const BaseTransaction& aTxn, uint64_t aEpoch);
   void SendCommitTransaction(ContentChild* aChild, const BaseTransaction& aTxn,
                              uint64_t aEpoch);
 
+  bool CheckOnlyOwningProcessCanSet(ContentParent* aSource);
+
   // Overload `CanSet` to get notifications for a particular field being set.
   bool CanSet(FieldIndex<IDX_OuterWindowId>, const uint64_t& aValue,
               ContentParent* aSource) {
     return GetOuterWindowId() == 0 && aValue != 0;
   }
 
   bool CanSet(FieldIndex<IDX_AllowMixedContent>, const bool& aAllowMixedContent,
               ContentParent* aSource);
 
   bool CanSet(FieldIndex<IDX_CookieJarSettings>,
               const Maybe<mozilla::net::CookieJarSettingsArgs>& aValue,
-              ContentParent* aSource) {
-    return true;
-  }
-
+              ContentParent* aSource);
   bool CanSet(FieldIndex<IDX_IsThirdPartyWindow>,
               const bool& IsThirdPartyWindow, ContentParent* aSource);
   bool CanSet(FieldIndex<IDX_IsThirdPartyTrackingResourceWindow>,
               const bool& aIsThirdPartyTrackingResourceWindow,
               ContentParent* aSource);
 
   // Overload `DidSet` to get notifications for a particular field being set.
   //
@@ -137,16 +138,17 @@ class WindowContext : public nsISupports
 
   // --- NEVER CHANGE `mChildren` DIRECTLY! ---
   // Changes to this list need to be synchronized to the list within our
   // `mBrowsingContext`, and should only be performed through the
   // `AppendChildBrowsingContext` and `RemoveChildBrowsingContext` methods.
   nsTArray<RefPtr<BrowsingContext>> mChildren;
 
   bool mIsDiscarded = false;
+  bool mInProcess = false;
 };
 
 using WindowContextTransaction = WindowContext::BaseTransaction;
 using WindowContextInitializer = WindowContext::IPCInitializer;
 using MaybeDiscardedWindowContext = MaybeDiscarded<WindowContext>;
 
 // Don't specialize the `Transaction` object for every translation unit it's
 // used in. This should help keep code size down.
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -374,17 +374,16 @@ nsDocShell::nsDocShell(BrowsingContext* 
       mAllowWindowControl(true),
       mUseErrorPages(true),
       mCSSErrorReportingEnabled(false),
       mAllowAuth(mItemType == typeContent),
       mAllowKeywordFixup(false),
       mIsOffScreenBrowser(false),
       mDisableMetaRefreshWhenInactive(false),
       mIsAppTab(false),
-      mUseGlobalHistory(false),
       mDeviceSizeIsPageSize(false),
       mWindowDraggingAllowed(false),
       mInFrameSwap(false),
       mCanExecuteScripts(false),
       mFiredUnloadEvent(false),
       mEODForCurrentDocument(false),
       mURIResultedInDocument(false),
       mIsBeingDestroyed(false),
@@ -2825,18 +2824,21 @@ nsDocShell::AddChild(nsIDocShellTreeItem
     GetCurrentSHEntry(getter_AddRefs(currentSH), &oshe);
     if (currentSH) {
       currentSH->HasDynamicallyAddedChild(&dynamic);
     }
   }
   childDocShell->SetChildOffset(dynamic ? -1 : mChildList.Length() - 1);
 
   /* Set the child's global history if the parent has one */
-  if (mUseGlobalHistory) {
-    childDocShell->SetUseGlobalHistory(true);
+  if (mBrowsingContext->GetUseGlobalHistory()) {
+    // childDocShell->SetUseGlobalHistory(true);
+    // this should be set through BC inherit
+    MOZ_ASSERT(nsDocShell::Cast(childDocShell)
+                   ->mBrowsingContext->GetUseGlobalHistory());
   }
 
   if (aChild->ItemType() != mItemType) {
     return NS_OK;
   }
 
   aChild->SetTreeOwner(mTreeOwner);
 
@@ -3003,33 +3005,16 @@ nsresult nsDocShell::AddChildSHEntryToPa
                mLoadedEntryIndex));
     }
   }
 
   return rv;
 }
 
 NS_IMETHODIMP
-nsDocShell::SetUseGlobalHistory(bool aUseGlobalHistory) {
-  mUseGlobalHistory = aUseGlobalHistory;
-  if (!aUseGlobalHistory) {
-    return NS_OK;
-  }
-
-  nsCOMPtr<IHistory> history = services::GetHistoryService();
-  return history ? NS_OK : NS_ERROR_FAILURE;
-}
-
-NS_IMETHODIMP
-nsDocShell::GetUseGlobalHistory(bool* aUseGlobalHistory) {
-  *aUseGlobalHistory = mUseGlobalHistory;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 nsDocShell::RemoveFromSessionHistory() {
   nsCOMPtr<nsIDocShellTreeItem> root;
   GetInProcessSameTypeRootTreeItem(getter_AddRefs(root));
   nsCOMPtr<nsIWebNavigation> rootAsWebnav = do_QueryInterface(root);
   if (!rootAsWebnav) {
     return NS_OK;
   }
   RefPtr<ChildSHistory> sessionHistory = rootAsWebnav->GetSessionHistory();
@@ -5649,30 +5634,21 @@ void nsDocShell::OnRedirectStateChange(n
 
   nsCOMPtr<nsIURI> oldURI, newURI;
   aOldChannel->GetURI(getter_AddRefs(oldURI));
   aNewChannel->GetURI(getter_AddRefs(newURI));
   if (!oldURI || !newURI) {
     return;
   }
 
-  // DocumentChannel only reports a single redirect via the normal
-  // confirmation mechanism (when they replace themselves with a real
-  // channel), but can have had an arbitrary number
-  // of redirects handled in the parent process.
-  // Query the full redirect chain directly, so that we can add history
-  // entries for them.
+  // DocumentChannel adds redirect chain to global history in the parent
+  // process. The redirect chain can't be queried from the content process, so
+  // there's no need to update global history here.
   RefPtr<DocumentChannel> docChannel = do_QueryObject(aOldChannel);
-  if (docChannel) {
-    nsCOMPtr<nsIURI> previousURI;
-    uint32_t previousFlags = 0;
-    docChannel->GetLastVisit(getter_AddRefs(previousURI), &previousFlags);
-    SavePreviousRedirectsAndLastVisit(aNewChannel, previousURI, previousFlags,
-                                      docChannel->GetRedirectChain());
-  } else {
+  if (!docChannel) {
     // Below a URI visit is saved (see AddURIVisit method doc).
     // The visit chain looks something like:
     //   ...
     //   Site N - 1
     //                =>  Site N
     //   (redirect to =>) Site N + 1 (we are here!)
 
     // Get N - 1 and transition type
@@ -6397,17 +6373,23 @@ nsresult nsDocShell::CreateAboutBlankCon
 
   nsCOMPtr<nsIDocumentLoaderFactory> docFactory =
       nsContentUtils::FindInternalContentViewer(
           NS_LITERAL_CSTRING("text/html"));
 
   if (docFactory) {
     nsCOMPtr<nsIPrincipal> principal, storagePrincipal;
     uint32_t sandboxFlags = mBrowsingContext->GetSandboxFlags();
-    if (sandboxFlags & SANDBOXED_ORIGIN) {
+    // If we're sandboxed, then create a new null principal. We skip
+    // this if we're being created from WindowGlobalChild, since in
+    // that case we already have a null principal if required.
+    // We can't compare againt the BrowsingContext sandbox flag, since
+    // the value was taken when the load initiated and may have since
+    // changed.
+    if ((sandboxFlags & SANDBOXED_ORIGIN) && !aActor) {
       if (aPrincipal) {
         principal = NullPrincipal::CreateWithInheritedAttributes(aPrincipal);
       } else {
         principal = NullPrincipal::CreateWithInheritedAttributes(this);
       }
       storagePrincipal = principal;
     } else {
       principal = aPrincipal;
@@ -10100,19 +10082,17 @@ bool nsDocShell::OnNewURI(nsIURI* aURI, 
       nsresult rv = httpChannel->GetResponseStatus(&responseStatus);
       if (mLSHE && NS_SUCCEEDED(rv) && responseStatus >= 400) {
         mLSHE->AbandonBFCacheEntry();
       }
     }
   }
 
   // Determine if this type of load should update history.
-  bool updateGHistory =
-      !(aLoadType == LOAD_BYPASS_HISTORY || aLoadType == LOAD_ERROR_PAGE ||
-        aLoadType & LOAD_CMD_HISTORY);
+  bool updateGHistory = ShouldUpdateGlobalHistory(aLoadType);
 
   // We don't update session history on reload unless we're loading
   // an iframe in shift-reload case.
   bool updateSHistory =
       updateGHistory && (!(aLoadType & LOAD_CMD_RELOAD) ||
                          (IsForceReloadType(aLoadType) && IsFrame()));
 
   // Create SH Entry (mLSHE) only if there is a SessionHistory object in the
@@ -10228,17 +10208,18 @@ bool nsDocShell::OnNewURI(nsIURI* aURI, 
     // Even if we don't add anything to SHistory, ensure the current index
     // points to the same SHEntry as our mLSHE.
 
     mSessionHistory->LegacySHistory()->EnsureCorrectEntryAtCurrIndex(mLSHE);
   }
 
   // If this is a POST request, we do not want to include this in global
   // history.
-  if (updateGHistory && aAddToGlobalHistory && !net::ChannelIsPost(aChannel)) {
+  if (ShouldAddURIVisit(aChannel) && updateGHistory && aAddToGlobalHistory &&
+      !net::ChannelIsPost(aChannel)) {
     nsCOMPtr<nsIURI> previousURI;
     uint32_t previousFlags = 0;
 
     if (aLoadType & LOAD_CMD_RELOAD) {
       // On a reload request, we don't set redirecting flags.
       previousURI = aURI;
     } else {
       ExtractLastVisit(aChannel, getter_AddRefs(previousURI), &previousFlags);
@@ -11173,16 +11154,27 @@ nsDocShell::MakeEditable(bool aInWaitFor
   nsresult rv = EnsureEditorData();
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   return mEditorData->MakeEditable(aInWaitForUriLoad);
 }
 
+/* static */ bool nsDocShell::ShouldAddURIVisit(nsIChannel* aChannel) {
+  bool needToAddURIVisit = true;
+  nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(aChannel));
+  if (props) {
+    mozilla::Unused << props->GetPropertyAsBool(
+        NS_LITERAL_STRING("docshell.needToAddURIVisit"), &needToAddURIVisit);
+  }
+
+  return needToAddURIVisit;
+}
+
 /* static */ void nsDocShell::ExtractLastVisit(
     nsIChannel* aChannel, nsIURI** aURI, uint32_t* aChannelRedirectFlags) {
   nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(aChannel));
   if (!props) {
     return;
   }
 
   nsresult rv = props->GetPropertyAsInterface(
@@ -11212,35 +11204,40 @@ void nsDocShell::SaveLastVisit(nsIChanne
   }
 
   props->SetPropertyAsInterface(NS_LITERAL_STRING("docshell.previousURI"),
                                 aURI);
   props->SetPropertyAsUint32(NS_LITERAL_STRING("docshell.previousFlags"),
                              aChannelRedirectFlags);
 }
 
-void nsDocShell::AddURIVisit(nsIURI* aURI, nsIURI* aPreviousURI,
-                             uint32_t aChannelRedirectFlags,
-                             uint32_t aResponseStatus) {
+/* static */ void nsDocShell::InternalAddURIVisit(
+    nsIURI* aURI, nsIURI* aPreviousURI, uint32_t aChannelRedirectFlags,
+    uint32_t aResponseStatus, BrowsingContext* aBrowsingContext,
+    nsIWidget* aWidget, uint32_t aLoadType) {
   MOZ_ASSERT(aURI, "Visited URI is null!");
-  MOZ_ASSERT(mLoadType != LOAD_ERROR_PAGE && mLoadType != LOAD_BYPASS_HISTORY,
+  MOZ_ASSERT(aLoadType != LOAD_ERROR_PAGE && aLoadType != LOAD_BYPASS_HISTORY,
              "Do not add error or bypass pages to global history");
 
+  bool usePrivateBrowsing = false;
+  aBrowsingContext->GetUsePrivateBrowsing(&usePrivateBrowsing);
+
   // Only content-type docshells save URI visits.  Also don't do
   // anything here if we're not supposed to use global history.
-  if (mItemType != typeContent || !mUseGlobalHistory || UsePrivateBrowsing()) {
+  if (!aBrowsingContext->IsContent() ||
+      !aBrowsingContext->GetUseGlobalHistory() || usePrivateBrowsing) {
     return;
   }
 
   nsCOMPtr<IHistory> history = services::GetHistoryService();
 
   if (history) {
     uint32_t visitURIFlags = 0;
 
-    if (!IsFrame()) {
+    if (aBrowsingContext->IsTop()) {
       visitURIFlags |= IHistory::TOP_LEVEL;
     }
 
     if (aChannelRedirectFlags & nsIChannelEventSink::REDIRECT_TEMPORARY) {
       visitURIFlags |= IHistory::REDIRECT_TEMPORARY;
     } else if (aChannelRedirectFlags &
                nsIChannelEventSink::REDIRECT_PERMANENT) {
       visitURIFlags |= IHistory::REDIRECT_PERMANENT;
@@ -11261,37 +11258,29 @@ void nsDocShell::AddURIVisit(nsIURI* aUR
     // 408 is special cased, since may actually indicate a temporary
     // connection problem.
     else if (aResponseStatus != 408 &&
              ((aResponseStatus >= 400 && aResponseStatus <= 501) ||
               aResponseStatus == 505)) {
       visitURIFlags |= IHistory::UNRECOVERABLE_ERROR;
     }
 
-    nsPIDOMWindowOuter* outer = GetWindow();
-    nsCOMPtr<nsIWidget> widget = widget::WidgetUtils::DOMWindowToWidget(outer);
-    (void)history->VisitURI(widget, aURI, aPreviousURI, visitURIFlags);
-  }
-}
-
-void nsDocShell::SavePreviousRedirectsAndLastVisit(
-    nsIChannel* aChannel, nsIURI* aPreviousURI, uint32_t aPreviousFlags,
-    const nsTArray<net::DocumentChannelRedirect>& aRedirects) {
-  nsCOMPtr<nsIURI> previousURI = aPreviousURI;
-  uint32_t previousFlags = aPreviousFlags;
-
-  for (auto& redirect : aRedirects) {
-    if (!redirect.isPost()) {
-      AddURIVisit(redirect.uri(), previousURI, previousFlags,
-                  redirect.responseStatus());
-      previousURI = redirect.uri();
-      previousFlags = redirect.redirectFlags();
-    }
-  }
-  SaveLastVisit(aChannel, previousURI, previousFlags);
+    mozilla::Unused << history->VisitURI(aWidget, aURI, aPreviousURI,
+                                         visitURIFlags);
+  }
+}
+
+void nsDocShell::AddURIVisit(nsIURI* aURI, nsIURI* aPreviousURI,
+                             uint32_t aChannelRedirectFlags,
+                             uint32_t aResponseStatus) {
+  nsPIDOMWindowOuter* outer = GetWindow();
+  nsCOMPtr<nsIWidget> widget = widget::WidgetUtils::DOMWindowToWidget(outer);
+
+  InternalAddURIVisit(aURI, aPreviousURI, aChannelRedirectFlags,
+                      aResponseStatus, mBrowsingContext, widget, mLoadType);
 }
 
 //*****************************************************************************
 // nsDocShell: Helper Routines
 //*****************************************************************************
 
 NS_IMETHODIMP
 nsDocShell::SetLoadType(uint32_t aLoadType) {
@@ -12243,34 +12232,32 @@ nsDocShell::ResumeRedirectedLoad(uint64_
   RefPtr<nsDocShell> self = this;
   RefPtr<ChildProcessChannelListener> cpcl =
       ChildProcessChannelListener::GetSingleton();
 
   // Call into InternalLoad with the pending channel when it is received.
   cpcl->RegisterCallback(
       aIdentifier, [self, aHistoryIndex](
                        nsDocShellLoadState* aLoadState,
-                       nsTArray<net::DocumentChannelRedirect>&& aRedirects,
                        nsTArray<Endpoint<extensions::PStreamFilterParent>>&&
                            aStreamFilterEndpoints,
                        nsDOMNavigationTiming* aTiming) {
         MOZ_ASSERT(aLoadState->GetPendingRedirectedChannel());
         if (NS_WARN_IF(self->mIsBeingDestroyed)) {
           aLoadState->GetPendingRedirectedChannel()->Cancel(NS_BINDING_ABORTED);
           return NS_BINDING_ABORTED;
         }
 
         self->mLoadType = aLoadState->LoadType();
         nsCOMPtr<nsIURI> previousURI;
         uint32_t previousFlags = 0;
         ExtractLastVisit(aLoadState->GetPendingRedirectedChannel(),
                          getter_AddRefs(previousURI), &previousFlags);
-        self->SavePreviousRedirectsAndLastVisit(
-            aLoadState->GetPendingRedirectedChannel(), previousURI,
-            previousFlags, aRedirects);
+        self->SaveLastVisit(aLoadState->GetPendingRedirectedChannel(),
+                            previousURI, previousFlags);
 
         if (aTiming) {
           self->mTiming = new nsDOMNavigationTiming(self, aTiming);
         }
 
         // If we're performing a history load, locate the correct history entry,
         // and set the relevant bits on our loadState.
         if (aHistoryIndex >= 0 && self->mSessionHistory) {
@@ -12350,18 +12337,24 @@ bool nsDocShell::HasUnloadedParent() {
     if (inUnload) {
       return true;
     }
     parent = parent->GetInProcessParentDocshell();
   }
   return false;
 }
 
+/* static */
+bool nsDocShell::ShouldUpdateGlobalHistory(uint32_t aLoadType) {
+  return !(aLoadType == LOAD_BYPASS_HISTORY || aLoadType == LOAD_ERROR_PAGE ||
+           aLoadType & LOAD_CMD_HISTORY);
+}
+
 void nsDocShell::UpdateGlobalHistoryTitle(nsIURI* aURI) {
-  if (!mUseGlobalHistory || UsePrivateBrowsing()) {
+  if (!mBrowsingContext->GetUseGlobalHistory() || UsePrivateBrowsing()) {
     return;
   }
 
   // Global history is interested into sub-frame visits only for link-coloring
   // purposes, thus title updates are skipped for those.
   //
   // Moreover, some iframe documents (such as the ones created via
   // document.open()) inherit the document uri of the caller, which would cause
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -62,17 +62,17 @@ class HTMLEditor;
 enum class TaskCategory;
 namespace dom {
 class ClientInfo;
 class ClientSource;
 class EventTarget;
 }  // namespace dom
 namespace net {
 class LoadInfo;
-class DocumentChannelRedirect;
+class DocumentLoadListener;
 }  // namespace net
 }  // namespace mozilla
 
 class nsIContentViewer;
 class nsIController;
 class nsIDocShellTreeOwner;
 class nsIHttpChannel;
 class nsIMutableArray;
@@ -504,16 +504,18 @@ class nsDocShell final : public nsDocLoa
       nsIChannel** aChannel);
 
   // Notify consumers of a search being loaded through the observer service:
   static void MaybeNotifyKeywordSearchLoading(const nsString& aProvider,
                                               const nsString& aKeyword);
 
   nsDocShell* GetInProcessChildAt(int32_t aIndex);
 
+  static bool ShouldAddURIVisit(nsIChannel* aChannel);
+
   /**
    * Helper function that finds the last URI and its transition flags for a
    * channel.
    *
    * This method first checks the channel's property bag to see if previous
    * info has been saved. If not, it gives back the referrer of the channel.
    *
    * @param aChannel
@@ -533,16 +535,17 @@ class nsDocShell final : public nsDocLoa
       mozilla::dom::BrowsingContext* aBrowsingContext, uint32_t aLoadType);
 
  private:  // member functions
   friend class nsDSURIContentListener;
   friend class FramingChecker;
   friend class OnLinkClickEvent;
   friend class nsIDocShell;
   friend class mozilla::dom::BrowsingContext;
+  friend class mozilla::net::DocumentLoadListener;
 
   // It is necessary to allow adding a timeline marker wherever a docshell
   // instance is available. This operation happens frequently and needs to
   // be very fast, so instead of using a Map or having to search for some
   // docshell-specific markers storage, a pointer to an `ObservedDocShell` is
   // is stored on docshells directly.
   friend void mozilla::TimelineConsumers::AddConsumer(nsDocShell*);
   friend void mozilla::TimelineConsumers::RemoveConsumer(nsDocShell*);
@@ -757,18 +760,18 @@ class nsDocShell final : public nsDocLoa
    *
    * @param aChannel
    *        Channel that will have these properties saved
    * @param aURI
    *        The URI to save for later
    * @param aChannelRedirectFlags
    *        The nsIChannelEventSink redirect flags to save for later
    */
-  void SaveLastVisit(nsIChannel* aChannel, nsIURI* aURI,
-                     uint32_t aChannelRedirectFlags);
+  static void SaveLastVisit(nsIChannel* aChannel, nsIURI* aURI,
+                            uint32_t aChannelRedirectFlags);
 
   /**
    * Helper function for adding a URI visit using IHistory.
    *
    * The IHistory API maintains chains of visits, tracking both HTTP referrers
    * and redirects for a user session. VisitURI requires the current URI and
    * the previous URI in the chain.
    *
@@ -788,32 +791,22 @@ class nsDocShell final : public nsDocLoa
    * @param aResponseStatus
    *        For HTTP channels, the response code (0 otherwise).
    */
   void AddURIVisit(nsIURI* aURI, nsIURI* aPreviousURI,
                    uint32_t aChannelRedirectFlags,
                    uint32_t aResponseStatus = 0);
 
   /**
-   * Helper function that will add the redirect chain found in aRedirects using
-   * IHistory (see AddURI and SaveLastVisit above for details)
-   *
-   * @param aChannel
-   *        Channel that will have these properties saved
-   * @param aURI
-   *        The URI that was just visited
-   * @param aChannelRedirectFlags
-   *        For redirects, the redirect flags from nsIChannelEventSink
-   *        (0 otherwise)
-   * @param aRedirects
-   *        The redirect chain collected by the DocumentChannelParent
+   * Internal helper funtion
    */
-  void SavePreviousRedirectsAndLastVisit(
-      nsIChannel* aChannel, nsIURI* aURI, uint32_t aChannelRedirectFlags,
-      const nsTArray<mozilla::net::DocumentChannelRedirect>& aRedirects);
+  static void InternalAddURIVisit(
+      nsIURI* aURI, nsIURI* aPreviousURI, uint32_t aChannelRedirectFlags,
+      uint32_t aResponseStatus, mozilla::dom::BrowsingContext* aBrowsingContext,
+      nsIWidget* aWidget, uint32_t aLoadType);
 
   already_AddRefed<nsIURIFixupInfo> KeywordToURI(const nsACString& aKeyword,
                                                  bool aIsPrivateContext,
                                                  nsIInputStream** aPostData);
 
   // Sets the current document's current state object to the given SHEntry's
   // state object. The current state object is eventually given to the page
   // in the PopState event.
@@ -978,16 +971,18 @@ class nsDocShell final : public nsDocLoa
   nsresult Dispatch(mozilla::TaskCategory aCategory,
                     already_AddRefed<nsIRunnable>&& aRunnable);
 
   void SetupReferrerInfoFromChannel(nsIChannel* aChannel);
   void SetReferrerInfo(nsIReferrerInfo* aReferrerInfo);
   void ReattachEditorToWindow(nsISHEntry* aSHEntry);
   void RecomputeCanExecuteScripts();
   void ClearFrameHistory(nsISHEntry* aEntry);
+  // Determine if this type of load should update history.
+  static bool ShouldUpdateGlobalHistory(uint32_t aLoadType);
   void UpdateGlobalHistoryTitle(nsIURI* aURI);
   bool IsFrame() { return mBrowsingContext->GetParent(); }
   bool CanSetOriginAttributes();
   bool ShouldBlockLoadingForBackButton();
   bool ShouldDiscardLayoutState(nsIHttpChannel* aChannel);
   bool HasUnloadedParent();
   bool JustStartedNetworkLoad();
   bool IsPrintingOrPP(bool aDisplayErrorDialog = true);
@@ -1258,17 +1253,16 @@ class nsDocShell final : public nsDocLoa
   bool mAllowWindowControl : 1;
   bool mUseErrorPages : 1;
   bool mCSSErrorReportingEnabled : 1;
   bool mAllowAuth : 1;
   bool mAllowKeywordFixup : 1;
   bool mIsOffScreenBrowser : 1;
   bool mDisableMetaRefreshWhenInactive : 1;
   bool mIsAppTab : 1;
-  bool mUseGlobalHistory : 1;
   bool mDeviceSizeIsPageSize : 1;
   bool mWindowDraggingAllowed : 1;
   bool mInFrameSwap : 1;
 
   // Because scriptability depends on the mAllowJavascript values of our
   // ancestors, we cache the effective scriptability and recompute it when
   // it might have changed;
   bool mCanExecuteScripts : 1;
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -784,21 +784,16 @@ interface nsIDocShell : nsIDocShellTreeI
    */
   void addChildSHEntry(in nsISHEntry aCloneReference,
                        in nsISHEntry aHistoryEntry,
                        in long aChildOffset,
                        in unsigned long aLoadType,
                        in boolean aCloneChilden);
 
   /**
-   * Whether this docshell should save entries in global history.
-   */
-  attribute boolean useGlobalHistory;
-
-  /**
    * Removes nsISHEntry objects related to this docshell from session history.
    * Use this only with subdocuments, like iframes.
    */
   void removeFromSessionHistory();
 
   /**
    * Set when an iframe/frame is added dynamically.
    */
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -229,17 +229,16 @@
 #include "nsTextNode.h"
 #include "nsThreadUtils.h"
 #include "nsTreeSanitizer.h"
 #include "nsUnicodeProperties.h"
 #include "nsURLHelper.h"
 #include "nsViewManager.h"
 #include "nsViewportInfo.h"
 #include "nsWidgetsCID.h"
-#include "nsIWindowProvider.h"
 #include "nsWrapperCacheInlines.h"
 #include "nsXULPopupManager.h"
 #include "xpcprivate.h"  // nsXPConnect
 #include "HTMLSplitOnSpacesTokenizer.h"
 #include "InProcessBrowserChildMessageManager.h"
 #include "nsContentTypeParser.h"
 #include "ThirdPartyUtil.h"
 #include "mozilla/EnumSet.h"
@@ -5350,22 +5349,16 @@ void nsContentUtils::RemoveScriptBlocker
 #ifdef DEBUG
   AutoRestore<bool> removingScriptBlockers(sRemovingScriptBlockers);
   sRemovingScriptBlockers = true;
 #endif
   sBlockedScriptRunners->RemoveElementsAt(originalFirstBlocker, blockersCount);
 }
 
 /* static */
-nsIWindowProvider* nsContentUtils::GetWindowProviderForContentProcess() {
-  MOZ_ASSERT(XRE_IsContentProcess());
-  return ContentChild::GetSingleton();
-}
-
-/* static */
 already_AddRefed<nsPIDOMWindowOuter>
 nsContentUtils::GetMostRecentNonPBWindow() {
   nsCOMPtr<nsIWindowMediator> wm = do_GetService(NS_WINDOWMEDIATOR_CONTRACTID);
 
   nsCOMPtr<mozIDOMWindowProxy> window;
   wm->GetMostRecentNonPBWindow(u"navigator:browser", getter_AddRefs(window));
   nsCOMPtr<nsPIDOMWindowOuter> pwindow;
   pwindow = do_QueryInterface(window);
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -104,17 +104,16 @@ class nsStringBuffer;
 class nsStringHashKey;
 class nsTextFragment;
 class nsView;
 class nsViewportInfo;
 class nsWrapperCache;
 class nsAttrValue;
 class nsITransferable;
 class nsPIWindowRoot;
-class nsIWindowProvider;
 class nsIReferrerInfo;
 
 struct JSRuntime;
 
 template <class E>
 class nsCOMArray;
 template <class K, class V>
 class nsDataHashtable;
@@ -2092,20 +2091,16 @@ class nsContentUtils {
    * run anything else, when this function returns false, but this is ok.
    */
   static bool IsSafeToRunScript() {
     MOZ_ASSERT(NS_IsMainThread(),
                "This static variable only makes sense on the main thread!");
     return sScriptBlockerCount == 0;
   }
 
-  // XXXcatalinb: workaround for weird include error when trying to reference
-  // ipdl types in WindowWatcher.
-  static nsIWindowProvider* GetWindowProviderForContentProcess();
-
   // Returns the browser window with the most recent time stamp that is
   // not in private browsing mode.
   static already_AddRefed<nsPIDOMWindowOuter> GetMostRecentNonPBWindow();
 
   /**
    * Call this function if !IsSafeToRunScript() and we fail to run the script
    * (rather than using AddScriptRunner as we usually do). |aDocument| is
    * optional as it is only used for showing the URL in the console.
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -63,16 +63,17 @@
 #include "Layers.h"
 #include "ClientLayerManager.h"
 
 #include "ContentParent.h"
 #include "BrowserParent.h"
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/ExpandedPrincipal.h"
+#include "mozilla/FlushType.h"
 #include "mozilla/GuardObjects.h"
 #include "mozilla/HTMLEditor.h"
 #include "mozilla/NullPrincipal.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/PresShell.h"
 #include "mozilla/PresShellInlines.h"
 #include "mozilla/Unused.h"
 #include "mozilla/dom/ChromeMessageSender.h"
@@ -1151,30 +1152,32 @@ nsresult nsFrameLoader::SwapWithOtherRem
 
   auto* browserParent = GetBrowserParent();
   auto* otherBrowserParent = aOther->GetBrowserParent();
 
   if (!browserParent || !otherBrowserParent) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
+  RefPtr<BrowsingContext> ourBc = browserParent->GetBrowsingContext();
+  RefPtr<BrowsingContext> otherBc = otherBrowserParent->GetBrowsingContext();
+
   // When we swap docShells, maybe we have to deal with a new page created just
   // for this operation. In this case, the browser code should already have set
   // the correct userContextId attribute value in the owning element, but our
   // docShell, that has been created way before) doesn't know that that
   // happened.
   // This is the reason why now we must retrieve the correct value from the
   // usercontextid attribute before comparing our originAttributes with the
   // other one.
-  OriginAttributes ourOriginAttributes = browserParent->OriginAttributesRef();
+  OriginAttributes ourOriginAttributes = ourBc->OriginAttributesRef();
   rv = PopulateOriginContextIdsFromAttributes(ourOriginAttributes);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  OriginAttributes otherOriginAttributes =
-      otherBrowserParent->OriginAttributesRef();
+  OriginAttributes otherOriginAttributes = otherBc->OriginAttributesRef();
   rv = aOther->PopulateOriginContextIdsFromAttributes(otherOriginAttributes);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (ourOriginAttributes != otherOriginAttributes) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   bool ourHasHistory =
@@ -1188,16 +1191,17 @@ nsresult nsFrameLoader::SwapWithOtherRem
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   if (mInSwap || aOther->mInSwap) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
   mInSwap = aOther->mInSwap = true;
 
+  // NOTE(emilio): This doesn't have to flush because the caller does already.
   nsIFrame* ourFrame = ourContent->GetPrimaryFrame();
   nsIFrame* otherFrame = otherContent->GetPrimaryFrame();
   if (!ourFrame || !otherFrame) {
     mInSwap = aOther->mInSwap = false;
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   nsSubDocumentFrame* ourFrameFrame = do_QueryFrame(ourFrame);
@@ -1389,24 +1393,34 @@ nsresult nsFrameLoader::SwapWithOtherLoa
   NS_ENSURE_STATE(!mInShow && !aOther->mInShow);
 
   if (IsRemoteFrame() != aOther->IsRemoteFrame()) {
     NS_WARNING(
         "Swapping remote and non-remote frames is not currently supported");
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
-  Element* ourContent = mOwnerContent;
-  Element* otherContent = aOther->mOwnerContent;
-
+  RefPtr<Element> ourContent = mOwnerContent;
+  RefPtr<Element> otherContent = aOther->mOwnerContent;
   if (!ourContent || !otherContent) {
     // Can't handle this
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
+  nsIFrame* ourFrame = ourContent->GetPrimaryFrame(FlushType::Frames);
+  nsIFrame* otherFrame = otherContent->GetPrimaryFrame(FlushType::Frames);
+  if (!ourFrame || !otherFrame) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
+  // Ensure the flushes above haven't changed all the world.
+  if (ourContent != mOwnerContent || otherContent != aOther->mOwnerContent) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
   bool ourHasSrcdoc = ourContent->IsHTMLElement(nsGkAtoms::iframe) &&
                       ourContent->HasAttr(kNameSpaceID_None, nsGkAtoms::srcdoc);
   bool otherHasSrcdoc =
       otherContent->IsHTMLElement(nsGkAtoms::iframe) &&
       otherContent->HasAttr(kNameSpaceID_None, nsGkAtoms::srcdoc);
   if (ourHasSrcdoc || otherHasSrcdoc) {
     // Ignore this case entirely for now, since we support XUL <-> HTML swapping
     return NS_ERROR_NOT_IMPLEMENTED;
@@ -1606,22 +1620,16 @@ nsresult nsFrameLoader::SwapWithOtherLoa
   }
 
   if (mInSwap || aOther->mInSwap) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
   AutoResetInFrameSwap autoFrameSwap(this, aOther, ourDocshell, otherDocshell,
                                      ourEventTarget, otherEventTarget);
 
-  nsIFrame* ourFrame = ourContent->GetPrimaryFrame();
-  nsIFrame* otherFrame = otherContent->GetPrimaryFrame();
-  if (!ourFrame || !otherFrame) {
-    return NS_ERROR_NOT_IMPLEMENTED;
-  }
-
   nsSubDocumentFrame* ourFrameFrame = do_QueryFrame(ourFrame);
   if (!ourFrameFrame) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   // OK.  First begin to swap the docshells in the two nsIFrames
   rv = ourFrameFrame->BeginSwapDocShells(otherFrame);
   if (NS_FAILED(rv)) {
@@ -1725,18 +1733,18 @@ nsresult nsFrameLoader::SwapWithOtherLoa
 
   ourFrameFrame->EndSwapDocShells(otherFrame);
 
   // If the content being swapped came from windows on two screens with
   // incompatible backing resolution (e.g. dragging a tab between windows on
   // hi-dpi and low-dpi screens), it will have style data that is based on
   // the wrong appUnitsPerDevPixel value. So we tell the PresShells that their
   // backing scale factor may have changed. (Bug 822266)
-  ourPresShell->BackingScaleFactorChanged();
-  otherPresShell->BackingScaleFactorChanged();
+  ourFrame->PresShell()->BackingScaleFactorChanged();
+  otherFrame->PresShell()->BackingScaleFactorChanged();
 
   // Initialize browser API if needed now that owner content has changed
   InitializeBrowserAPI();
   aOther->InitializeBrowserAPI();
 
   return NS_OK;
 }
 
@@ -3157,23 +3165,17 @@ already_AddRefed<nsIRemoteTab> nsFrameLo
   }
   if (auto* browserHost = mRemoteBrowser->AsBrowserHost()) {
     return do_AddRef(browserHost);
   }
   return nullptr;
 }
 
 already_AddRefed<nsILoadContext> nsFrameLoader::LoadContext() {
-  nsCOMPtr<nsILoadContext> loadContext;
-  if (IsRemoteFrame() && EnsureRemoteBrowser()) {
-    loadContext = mRemoteBrowser->GetLoadContext();
-  } else {
-    loadContext = do_GetInterface(ToSupports(GetDocShell(IgnoreErrors())));
-  }
-  return loadContext.forget();
+  return do_AddRef(GetBrowsingContext());
 }
 
 BrowsingContext* nsFrameLoader::GetBrowsingContext() {
   if (IsRemoteFrame()) {
     Unused << EnsureRemoteBrowser();
   } else if (mOwnerContent) {
     Unused << MaybeCreateDocShell();
   }
@@ -3298,17 +3300,16 @@ nsresult nsFrameLoader::GetNewTabContext
   mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::mozpresentation,
                          presentationURLStr);
 
   nsCOMPtr<nsIDocShell> docShell = mOwnerContent->OwnerDoc()->GetDocShell();
   nsCOMPtr<nsILoadContext> parentContext = do_QueryInterface(docShell);
   NS_ENSURE_STATE(parentContext);
 
   MOZ_ASSERT(mPendingBrowsingContext->EverAttached());
-  OriginAttributes attrs = mPendingBrowsingContext->OriginAttributesRef();
 
   UIStateChangeType showFocusRings = UIStateChangeType_NoChange;
   uint64_t chromeOuterWindowID = 0;
 
   Document* doc = mOwnerContent->OwnerDoc();
   if (doc) {
     nsCOMPtr<nsPIWindowRoot> root = nsContentUtils::GetWindowRoot(doc);
     if (root) {
@@ -3319,19 +3320,18 @@ nsresult nsFrameLoader::GetNewTabContext
       if (outerWin) {
         chromeOuterWindowID = outerWin->WindowID();
       }
     }
   }
 
   uint32_t maxTouchPoints = BrowserParent::GetMaxTouchPoints(mOwnerContent);
 
-  bool tabContextUpdated =
-      aTabContext->SetTabContext(chromeOuterWindowID, showFocusRings, attrs,
-                                 presentationURLStr, maxTouchPoints);
+  bool tabContextUpdated = aTabContext->SetTabContext(
+      chromeOuterWindowID, showFocusRings, presentationURLStr, maxTouchPoints);
   NS_ENSURE_STATE(tabContextUpdated);
 
   return NS_OK;
 }
 
 nsresult nsFrameLoader::PopulateOriginContextIdsFromAttributes(
     OriginAttributes& aAttr) {
   // Only XUL or mozbrowser frames are allowed to set context IDs
--- a/dom/chrome-webidl/BrowsingContext.webidl
+++ b/dom/chrome-webidl/BrowsingContext.webidl
@@ -83,16 +83,21 @@ interface BrowsingContext {
   // The inRDMPane flag indicates whether or not Responsive Design Mode is
   // active for the browsing context.
   attribute boolean inRDMPane;
 
   attribute float fullZoom;
 
   attribute float textZoom;
 
+  /**
+   * Whether this docshell should save entries in global history.
+   */
+  attribute boolean useGlobalHistory;
+
   // Extension to give chrome JS the ability to set the window screen
   // orientation while in RDM.
   void setRDMPaneOrientation(OrientationType type, float rotationAngle);
 
   // Extension to give chrome JS the ability to set a maxTouchPoints override
   // while in RDM.
   void setRDMPaneMaxTouchPoints(octet maxTouchPoints);
 };
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -3255,40 +3255,16 @@ nsresult EventStateManager::PostHandleEv
           if (nsIContent* par = activeContent->GetFlattenedTreeParent()) {
             activeContent = par;
           }
         }
       } else {
         // if we're here, the event handler returned false, so stop
         // any of our own processing of a drag. Workaround for bug 43258.
         StopTrackingDragGesture(true);
-
-        // When the event was cancelled, there is currently a chrome document
-        // focused and a mousedown just occurred on a content document, ensure
-        // that the window that was clicked is focused.
-        EnsureDocument(mPresContext);
-        nsIFocusManager* fm = nsFocusManager::GetFocusManager();
-        if (mDocument && fm) {
-          nsCOMPtr<mozIDOMWindowProxy> window;
-          fm->GetFocusedWindow(getter_AddRefs(window));
-          auto* currentWindow = nsPIDOMWindowOuter::From(window);
-          if (currentWindow && mDocument->GetWindow() &&
-              currentWindow != mDocument->GetWindow() &&
-              !nsContentUtils::IsChromeDoc(mDocument)) {
-            nsCOMPtr<nsPIDOMWindowOuter> currentTop;
-            nsCOMPtr<nsPIDOMWindowOuter> newTop;
-            currentTop = currentWindow->GetInProcessTop();
-            newTop = mDocument->GetWindow()->GetInProcessTop();
-            nsCOMPtr<Document> currentDoc = currentWindow->GetExtantDoc();
-            if (nsContentUtils::IsChromeDoc(currentDoc) ||
-                (currentTop && newTop && currentTop != newTop)) {
-              fm->SetFocusedWindow(mDocument->GetWindow());
-            }
-          }
-        }
       }
       SetActiveManager(this, activeContent);
     } break;
     case ePointerCancel:
     case ePointerUp: {
       WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent();
       MOZ_ASSERT(pointerEvent);
       // Implicitly releasing capture for given pointer. ePointerLostCapture
--- a/dom/ipc/BrowserBridgeChild.cpp
+++ b/dom/ipc/BrowserBridgeChild.cpp
@@ -61,16 +61,20 @@ already_AddRefed<BrowserBridgeHost> Brow
       }
     }
   }
 #endif  // defined(ACCESSIBILITY)
 
   return MakeAndAddRef<BrowserBridgeHost>(this);
 }
 
+nsILoadContext* BrowserBridgeChild::GetLoadContext() {
+  return mBrowsingContext;
+}
+
 void BrowserBridgeChild::NavigateByKey(bool aForward,
                                        bool aForDocumentNavigation) {
   Unused << SendNavigateByKey(aForward, aForDocumentNavigation);
 }
 
 void BrowserBridgeChild::Activate() { Unused << SendActivate(); }
 
 void BrowserBridgeChild::Deactivate(bool aWindowLowering) {
--- a/dom/ipc/BrowserBridgeChild.h
+++ b/dom/ipc/BrowserBridgeChild.h
@@ -40,18 +40,17 @@ class BrowserBridgeChild : public PBrows
   TabId GetTabId() { return mId; }
 
   LayersId GetLayersId() { return mLayersId; }
 
   nsFrameLoader* GetFrameLoader() const { return mFrameLoader; }
 
   BrowsingContext* GetBrowsingContext() { return mBrowsingContext; }
 
-  // XXX(nika): We should have a load context here. (bug 1532664)
-  nsILoadContext* GetLoadContext() { return nullptr; }
+  nsILoadContext* GetLoadContext();
 
   void NavigateByKey(bool aForward, bool aForDocumentNavigation);
 
   void Activate();
 
   void Deactivate(bool aWindowLowering);
 
   void SetIsUnderHiddenEmbedderElement(bool aIsUnderHiddenEmbedderElement);
--- a/dom/ipc/BrowserBridgeParent.cpp
+++ b/dom/ipc/BrowserBridgeParent.cpp
@@ -32,30 +32,19 @@ nsresult BrowserBridgeParent::InitWithPr
     const WindowGlobalInit& aWindowInit, uint32_t aChromeFlags, TabId aTabId) {
   if (aWindowInit.browsingContext().IsNullOrDiscarded()) {
     return NS_ERROR_UNEXPECTED;
   }
 
   RefPtr<CanonicalBrowsingContext> browsingContext =
       aWindowInit.browsingContext().get_canonical();
 
-  // We can inherit most TabContext fields for the new BrowserParent actor from
-  // our Manager BrowserParent. We also need to sync the first party domain if
-  // the content principal exists.
   MutableTabContext tabContext;
-  OriginAttributes attrs;
-  attrs = Manager()->OriginAttributesRef();
-  RefPtr<nsIPrincipal> principal = Manager()->GetContentPrincipal();
-  if (principal) {
-    attrs.SetFirstPartyDomain(
-        true, principal->OriginAttributesRef().mFirstPartyDomain);
-  }
-
   tabContext.SetTabContext(Manager()->ChromeOuterWindowID(),
-                           Manager()->ShowFocusRings(), attrs, aPresentationURL,
+                           Manager()->ShowFocusRings(), aPresentationURL,
                            Manager()->GetMaxTouchPoints());
 
   // Ensure that our content process is subscribed to our newly created
   // BrowsingContextGroup.
   browsingContext->Group()->EnsureSubscribed(aContentParent);
   browsingContext->SetOwnerProcessId(aContentParent->ChildID());
 
   // Construct the BrowserParent object for our subframe.
--- a/dom/ipc/BrowserChild.cpp
+++ b/dom/ipc/BrowserChild.cpp
@@ -484,18 +484,18 @@ nsresult BrowserChild::Init(mozIDOMWindo
     NS_ERROR("couldn't create fake widget");
     return NS_ERROR_FAILURE;
   }
   mPuppetWidget->InfallibleCreate(nullptr,
                                   nullptr,  // no parents
                                   LayoutDeviceIntRect(0, 0, 0, 0),
                                   nullptr);  // HandleWidgetEvent
 
-  mWebBrowser = nsWebBrowser::Create(this, mPuppetWidget, OriginAttributesRef(),
-                                     mBrowsingContext, aInitialWindowChild);
+  mWebBrowser = nsWebBrowser::Create(this, mPuppetWidget, mBrowsingContext,
+                                     aInitialWindowChild);
   nsIWebBrowser* webBrowser = mWebBrowser;
 
   mWebNav = do_QueryInterface(webBrowser);
   NS_ASSERTION(mWebNav, "nsWebBrowser doesn't implement nsIWebNavigation?");
 
   // Set the tab context attributes then pass to docShell
   NotifyTabContextUpdated();
 
@@ -521,18 +521,16 @@ nsresult BrowserChild::Init(mozIDOMWindo
 
   docShell->SetAffectPrivateSessionLifetime(
       mBrowsingContext->UsePrivateBrowsing() ||
       mChromeFlags & nsIWebBrowserChrome::CHROME_PRIVATE_LIFETIME);
 
 #ifdef DEBUG
   nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(WebNavigation());
   MOZ_ASSERT(loadContext);
-  MOZ_ASSERT(loadContext->UsePrivateBrowsing() ==
-             (OriginAttributesRef().mPrivateBrowsingId > 0));
   MOZ_ASSERT(loadContext->UseRemoteTabs() ==
              !!(mChromeFlags & nsIWebBrowserChrome::CHROME_REMOTE_WINDOW));
   MOZ_ASSERT(loadContext->UseRemoteSubframes() ==
              !!(mChromeFlags & nsIWebBrowserChrome::CHROME_FISSION_WINDOW));
 #endif  // defined(DEBUG)
 
   // Few lines before, baseWindow->Create() will end up creating a new
   // window root in nsGlobalWindow::SetDocShell.
@@ -2282,31 +2280,16 @@ mozilla::ipc::IPCResult BrowserChild::Re
       localEvent.mWidget = mPuppetWidget;
       SendAccessKeyNotHandled(localEvent);
     }
   }
 
   return IPC_OK();
 }
 
-mozilla::ipc::IPCResult BrowserChild::RecvSetUseGlobalHistory(
-    const bool& aUse) {
-  nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation());
-  if (!docShell) {
-    return IPC_OK();
-  }
-
-  nsresult rv = docShell->SetUseGlobalHistory(aUse);
-  if (NS_FAILED(rv)) {
-    NS_WARNING("Failed to set UseGlobalHistory on BrowserChild docShell");
-  }
-
-  return IPC_OK();
-}
-
 mozilla::ipc::IPCResult BrowserChild::RecvPrint(const uint64_t& aOuterWindowID,
                                                 const PrintData& aPrintData) {
 #ifdef NS_PRINTING
   nsGlobalWindowOuter* outerWindow =
       nsGlobalWindowOuter::GetOuterWindowWithId(aOuterWindowID);
   if (NS_WARN_IF(!outerWindow)) {
     return IPC_OK();
   }
--- a/dom/ipc/BrowserChild.h
+++ b/dom/ipc/BrowserChild.h
@@ -520,18 +520,16 @@ class BrowserChild final : public nsMess
                                                   const double& aScale);
 
   mozilla::ipc::IPCResult RecvThemeChanged(
       nsTArray<LookAndFeelInt>&& aLookAndFeelIntCache);
 
   mozilla::ipc::IPCResult RecvHandleAccessKey(const WidgetKeyboardEvent& aEvent,
                                               nsTArray<uint32_t>&& aCharCodes);
 
-  mozilla::ipc::IPCResult RecvSetUseGlobalHistory(const bool& aUse);
-
   mozilla::ipc::IPCResult RecvHandledWindowedPluginKeyEvent(
       const mozilla::NativeEventData& aKeyEventData, const bool& aIsConsumed);
 
   mozilla::ipc::IPCResult RecvPrint(const uint64_t& aOuterWindowID,
                                     const PrintData& aPrintData);
 
   mozilla::ipc::IPCResult RecvUpdateNativeWindowHandle(
       const uintptr_t& aNewHandle);
--- a/dom/ipc/BrowserParent.cpp
+++ b/dom/ipc/BrowserParent.cpp
@@ -85,17 +85,16 @@
 #include "nsQueryObject.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "PermissionMessageUtils.h"
 #include "StructuredCloneData.h"
 #include "ColorPickerParent.h"
 #include "FilePickerParent.h"
 #include "BrowserChild.h"
-#include "LoadContext.h"
 #include "nsNetCID.h"
 #include "nsIAuthInformation.h"
 #include "nsIAuthPromptCallback.h"
 #include "nsAuthInformationHolder.h"
 #include "nsICancelable.h"
 #include "gfxUtils.h"
 #include "nsILoginManagerAuthPrompter.h"
 #include "nsPIWindowRoot.h"
@@ -163,30 +162,28 @@ namespace dom {
 BrowserParent::LayerToBrowserParentTable*
     BrowserParent::sLayerToBrowserParentTable = nullptr;
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrowserParent)
   NS_INTERFACE_MAP_ENTRY(nsIAuthPromptProvider)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventListener)
 NS_INTERFACE_MAP_END
-NS_IMPL_CYCLE_COLLECTION_WEAK(BrowserParent, mLoadContext, mFrameLoader,
-                              mBrowsingContext)
+NS_IMPL_CYCLE_COLLECTION_WEAK(BrowserParent, mFrameLoader, mBrowsingContext)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(BrowserParent)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(BrowserParent)
 
 BrowserParent::BrowserParent(ContentParent* aManager, const TabId& aTabId,
                              const TabContext& aContext,
                              CanonicalBrowsingContext* aBrowsingContext,
                              uint32_t aChromeFlags)
     : TabContext(aContext),
       mTabId(aTabId),
       mManager(aManager),
       mBrowsingContext(aBrowsingContext),
-      mLoadContext(nullptr),
       mFrameElement(nullptr),
       mBrowserDOMWindow(nullptr),
       mFrameLoader(nullptr),
       mChromeFlags(aChromeFlags),
       mBrowserBridgeParent(nullptr),
       mBrowserHost(nullptr),
       mContentCache(*this),
       mRemoteLayerTreeOwner{},
@@ -293,37 +290,17 @@ void BrowserParent::RemoveBrowserParentF
   sLayerToBrowserParentTable->Remove(uint64_t(aLayersId));
   if (sLayerToBrowserParentTable->Count() == 0) {
     delete sLayerToBrowserParentTable;
     sLayerToBrowserParentTable = nullptr;
   }
 }
 
 already_AddRefed<nsILoadContext> BrowserParent::GetLoadContext() {
-  nsCOMPtr<nsILoadContext> loadContext;
-  if (mLoadContext) {
-    loadContext = mLoadContext;
-  } else {
-    bool isPrivate = mChromeFlags & nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW;
-    SetPrivateBrowsingAttributes(isPrivate);
-    bool useTrackingProtection = false;
-    if (mFrameElement) {
-      nsCOMPtr<nsIDocShell> docShell = mFrameElement->OwnerDoc()->GetDocShell();
-      if (docShell) {
-        docShell->GetUseTrackingProtection(&useTrackingProtection);
-      }
-    }
-    loadContext = new LoadContext(
-        GetOwnerElement(), true /* aIsContent */, isPrivate,
-        mChromeFlags & nsIWebBrowserChrome::CHROME_REMOTE_WINDOW,
-        mChromeFlags & nsIWebBrowserChrome::CHROME_FISSION_WINDOW,
-        useTrackingProtection, OriginAttributesRef());
-    mLoadContext = loadContext;
-  }
-  return loadContext.forget();
+  return do_AddRef(mBrowsingContext);
 }
 
 /**
  * Will return nullptr if there is no outer window available for the
  * document hosting the owner element of this BrowserParent. Also will return
  * nullptr if that outer window is in the process of closing.
  */
 already_AddRefed<nsPIDOMWindowOuter> BrowserParent::GetParentWindowOuter() {
@@ -519,22 +496,16 @@ void BrowserParent::SetOwnerElement(Elem
 
   // Update to the new content, and register to listen for events from it.
   mFrameElement = aElement;
 
   if (mBrowserHost && newTopLevelWin && !isSameTopLevelWin) {
     newTopLevelWin->AddBrowser(mBrowserHost);
   }
 
-  if (mFrameElement) {
-    bool useGlobalHistory = !mFrameElement->HasAttr(
-        kNameSpaceID_None, nsGkAtoms::disableglobalhistory);
-    Unused << SendSetUseGlobalHistory(useGlobalHistory);
-  }
-
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
   if (!mIsDestroyed) {
     uintptr_t newWindowHandle = 0;
     if (nsCOMPtr<nsIWidget> widget = GetWidget()) {
       newWindowHandle =
           reinterpret_cast<uintptr_t>(widget->GetNativeData(NS_NATIVE_WINDOW));
     }
     Unused << SendUpdateNativeWindowHandle(newWindowHandle);
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -606,17 +606,16 @@ ContentChild::~ContentChild() {
 }
 
 #ifdef _MSC_VER
 #  pragma warning(pop)
 #endif
 
 NS_INTERFACE_MAP_BEGIN(ContentChild)
   NS_INTERFACE_MAP_ENTRY(nsIContentChild)
-  NS_INTERFACE_MAP_ENTRY(nsIWindowProvider)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentChild)
 NS_INTERFACE_MAP_END
 
 mozilla::ipc::IPCResult ContentChild::RecvSetXPCOMProcessAttributes(
     XPCOMInitData&& aXPCOMInit, const StructuredCloneData& aInitialData,
     nsTArray<LookAndFeelInt>&& aLookAndFeelIntCache,
     nsTArray<SystemFontListEntry>&& aFontList,
     const Maybe<SharedMemoryHandle>& aSharedUASheetHandle,
@@ -775,30 +774,16 @@ void ContentChild::SetProcessName(const 
   mProcessName = aName;
   NS_LossyConvertUTF16toASCII asciiName(aName);
   mozilla::ipc::SetThisProcessName(asciiName.get());
 #ifdef MOZ_GECKO_PROFILER
   profiler_set_process_name(asciiName);
 #endif
 }
 
-NS_IMETHODIMP
-ContentChild::ProvideWindow(nsIOpenWindowInfo* aOpenWindowInfo,
-                            uint32_t aChromeFlags, bool aCalledFromJS,
-                            bool aWidthSpecified, nsIURI* aURI,
-                            const nsAString& aName, const nsACString& aFeatures,
-                            bool aForceNoOpener, bool aForceNoReferrer,
-                            nsDocShellLoadState* aLoadState, bool* aWindowIsNew,
-                            BrowsingContext** aReturn) {
-  return ProvideWindowCommon(nullptr, aOpenWindowInfo, aChromeFlags,
-                             aCalledFromJS, aWidthSpecified, aURI, aName,
-                             aFeatures, aForceNoOpener, aForceNoReferrer,
-                             aLoadState, aWindowIsNew, aReturn);
-}
-
 static nsresult GetCreateWindowParams(nsIOpenWindowInfo* aOpenWindowInfo,
                                       nsDocShellLoadState* aLoadState,
                                       bool aForceNoReferrer, float* aFullZoom,
                                       nsIReferrerInfo** aReferrerInfo,
                                       nsIPrincipal** aTriggeringPrincipal,
                                       nsIContentSecurityPolicy** aCsp) {
   *aFullZoom = 1.0f;
   if (!aTriggeringPrincipal || !aCsp) {
@@ -859,28 +844,34 @@ static nsresult GetCreateWindowParams(ns
 }
 
 nsresult ContentChild::ProvideWindowCommon(
     BrowserChild* aTabOpener, nsIOpenWindowInfo* aOpenWindowInfo,
     uint32_t aChromeFlags, bool aCalledFromJS, bool aWidthSpecified,
     nsIURI* aURI, const nsAString& aName, const nsACString& aFeatures,
     bool aForceNoOpener, bool aForceNoReferrer, nsDocShellLoadState* aLoadState,
     bool* aWindowIsNew, BrowsingContext** aReturn) {
+  MOZ_DIAGNOSTIC_ASSERT(aTabOpener, "We must have a tab opener");
+
   *aReturn = nullptr;
 
-  UniquePtr<IPCTabContext> ipcContext;
-  TabId openerTabId = TabId(0);
   nsAutoCString features(aFeatures);
   nsAutoString name(aName);
 
   nsresult rv;
 
   RefPtr<BrowsingContext> parent = aOpenWindowInfo->GetParent();
-  MOZ_ASSERT(!parent || aTabOpener,
-             "If parent is non-null, we should have an aTabOpener");
+  MOZ_DIAGNOSTIC_ASSERT(parent, "We must have a parent BC");
+
+  // Block the attempt to open a new window if the opening BrowsingContext is
+  // not marked to use remote tabs. This ensures that the newly opened window is
+  // correctly remote.
+  if (NS_WARN_IF(!parent->UseRemoteTabs())) {
+    return NS_ERROR_ABORT;
+  }
 
   // Cache the boolean preference for allowing noopener windows to open in a
   // separate process.
   static bool sNoopenerNewProcess = false;
   static bool sNoopenerNewProcessInited = false;
   if (!sNoopenerNewProcessInited) {
     Preferences::AddBoolVarCache(&sNoopenerNewProcess,
                                  "dom.noopener.newprocess.enabled");
@@ -892,17 +883,17 @@ nsresult ContentChild::ProvideWindowComm
 
   // Check if we should load in a different process. Under Fission, we never
   // want to do this, since the Fission process selection logic will handle
   // everything for us. Outside of Fission, we always want to load in a
   // different process if we have noopener set, but we also might if we can't
   // load in the current process.
   bool loadInDifferentProcess =
       aForceNoOpener && sNoopenerNewProcess && !useRemoteSubframes;
-  if (aTabOpener && !loadInDifferentProcess && aURI) {
+  if (!loadInDifferentProcess && aURI) {
     // Only special-case cross-process loads if Fission is disabled. With
     // Fission enabled, the initial in-process load will automatically be
     // retargeted to the correct process.
     if (!(parent && parent->UseRemoteSubframes())) {
       nsCOMPtr<nsIWebBrowserChrome3> browserChrome3;
       rv = aTabOpener->GetWebBrowserChrome(getter_AddRefs(browserChrome3));
       if (NS_SUCCEEDED(rv) && browserChrome3) {
         bool shouldLoad;
@@ -938,36 +929,23 @@ nsresult ContentChild::ProvideWindowComm
         features, fullZoom, name, triggeringPrincipal, csp, referrerInfo,
         aOpenWindowInfo->GetOriginAttributes());
 
     // We return NS_ERROR_ABORT, so that the caller knows that we've abandoned
     // the window open as far as it is concerned.
     return NS_ERROR_ABORT;
   }
 
-  if (aTabOpener) {
-    PopupIPCTabContext context;
-    openerTabId = aTabOpener->GetTabId();
-    context.opener() = openerTabId;
-    ipcContext = MakeUnique<IPCTabContext>(context);
-  } else {
-    // It's possible to not have a BrowserChild opener in the case
-    // of ServiceWorker::OpenWindow.
-    UnsafeIPCTabContext unsafeTabContext;
-    ipcContext = MakeUnique<IPCTabContext>(unsafeTabContext);
-  }
-
-  MOZ_ASSERT(ipcContext);
   TabId tabId(nsContentUtils::GenerateTabId());
 
   // We need to assign a TabGroup to the PBrowser actor before we send it to the
   // parent. Otherwise, the parent could send messages to us before we have a
   // proper TabGroup for that actor.
   RefPtr<BrowsingContext> openerBC;
-  if (aTabOpener && !aForceNoOpener) {
+  if (!aForceNoOpener) {
     openerBC = parent;
   }
 
   RefPtr<BrowsingContext> browsingContext = BrowsingContext::CreateDetached(
       nullptr, openerBC, aName, BrowsingContext::Type::Content);
   MOZ_ALWAYS_SUCCEEDS(browsingContext->SetRemoteTabs(true));
   MOZ_ALWAYS_SUCCEEDS(browsingContext->SetRemoteSubframes(useRemoteSubframes));
   MOZ_ALWAYS_SUCCEEDS(browsingContext->SetOriginAttributes(
@@ -977,29 +955,19 @@ nsresult ContentChild::ProvideWindowComm
   browsingContext->SetPendingInitialization(true);
   auto unsetPending = MakeScopeExit([browsingContext]() {
     browsingContext->SetPendingInitialization(false);
   });
 
   // Awkwardly manually construct the new TabContext in order to ensure our
   // OriginAttributes perfectly matches it.
   MutableTabContext newTabContext;
-  if (aTabOpener) {
-    newTabContext.SetTabContext(
-        aTabOpener->ChromeOuterWindowID(), aTabOpener->ShowFocusRings(),
-        browsingContext->OriginAttributesRef(), aTabOpener->PresentationURL(),
-        aTabOpener->MaxTouchPoints());
-  } else {
-    newTabContext.SetTabContext(
-        /* chromeOuterWindowID */ 0,
-        /* showFocusRings */ UIStateChangeType_NoChange,
-        browsingContext->OriginAttributesRef(),
-        /* presentationURL */ EmptyString(),
-        /* maxTouchPoints */ 0);
-  }
+  newTabContext.SetTabContext(
+      aTabOpener->ChromeOuterWindowID(), aTabOpener->ShowFocusRings(),
+      aTabOpener->PresentationURL(), aTabOpener->MaxTouchPoints());
 
   // The initial about:blank document we generate within the nsDocShell will
   // almost certainly be replaced at some point. Unfortunately, getting the
   // principal right here causes bugs due to frame scripts not getting events
   // they expect, due to the real initial about:blank not being created yet.
   //
   // For this reason, we intentionally mispredict the initial principal here, so
   // that we can act the same as we did before when not predicting a result
@@ -1010,21 +978,16 @@ nsresult ContentChild::ProvideWindowComm
       browsingContext, initialPrincipal);
 
   auto windowChild = MakeRefPtr<WindowGlobalChild>(windowInit, nullptr);
 
   auto newChild = MakeRefPtr<BrowserChild>(this, tabId, newTabContext,
                                            browsingContext, aChromeFlags,
                                            /* aIsTopLevel */ true);
 
-  if (aTabOpener) {
-    MOZ_ASSERT(ipcContext->type() == IPCTabContext::TPopupIPCTabContext);
-    ipcContext->get_PopupIPCTabContext().opener() = aTabOpener;
-  }
-
   if (IsShuttingDown()) {
     return NS_ERROR_ABORT;
   }
 
   // Open a remote endpoint for our PBrowser actor.
   ManagedEndpoint<PBrowserParent> parentEp = OpenPBrowserEndpoint(newChild);
   if (NS_WARN_IF(!parentEp.IsValid())) {
     return NS_ERROR_ABORT;
@@ -1033,18 +996,20 @@ nsresult ContentChild::ProvideWindowComm
   // Open a remote endpoint for our PWindowGlobal actor.
   ManagedEndpoint<PWindowGlobalParent> windowParentEp =
       newChild->OpenPWindowGlobalEndpoint(windowChild);
   if (NS_WARN_IF(!windowParentEp.IsValid())) {
     return NS_ERROR_ABORT;
   }
 
   // Tell the parent process to set up its PBrowserParent.
+  PopupIPCTabContext ipcContext;
+  ipcContext.openerChild() = aTabOpener;
   if (NS_WARN_IF(!SendConstructPopupBrowser(
-          std::move(parentEp), std::move(windowParentEp), tabId, *ipcContext,
+          std::move(parentEp), std::move(windowParentEp), tabId, ipcContext,
           windowInit, aChromeFlags))) {
     return NS_ERROR_ABORT;
   }
 
   windowChild->Init();
   auto guardNullWindowGlobal = MakeScopeExit([&] {
     if (!windowChild->GetWindowGlobal()) {
       windowChild->Destroy();
@@ -1091,23 +1056,20 @@ nsresult ContentChild::ProvideWindowComm
     }
 
     // If the BrowserChild has been torn down, we don't need to do this anymore.
     if (NS_WARN_IF(!newChild->IPCOpen() || newChild->IsDestroyed())) {
       rv = NS_ERROR_ABORT;
       return;
     }
 
-    ParentShowInfo showInfo(EmptyString(), false, true, false, 0, 0, 0);
-    if (aTabOpener) {
-      showInfo = ParentShowInfo(
-          EmptyString(), false, true, false, aTabOpener->WebWidget()->GetDPI(),
-          aTabOpener->WebWidget()->RoundsWidgetCoordinatesTo(),
-          aTabOpener->WebWidget()->GetDefaultScale().scale);
-    }
+    ParentShowInfo showInfo(
+        EmptyString(), false, true, false, aTabOpener->WebWidget()->GetDPI(),
+        aTabOpener->WebWidget()->RoundsWidgetCoordinatesTo(),
+        aTabOpener->WebWidget()->GetDefaultScale().scale);
 
     newChild->SetMaxTouchPoints(maxTouchPoints);
     newChild->SetHasSiblings(hasSiblings);
 
 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
     if (nsCOMPtr<nsPIDOMWindowOuter> outer =
             do_GetInterface(newChild->WebNavigation())) {
       BrowsingContext* bc = outer->GetBrowsingContext();
@@ -2922,23 +2884,16 @@ void ContentChild::ShutdownInternal() {
       NS_LITERAL_CSTRING("SendFinishShutdown (sending)"));
   bool sent = SendFinishShutdown();
   CrashReporter::AnnotateCrashReport(
       CrashReporter::Annotation::IPCShutdownState,
       sent ? NS_LITERAL_CSTRING("SendFinishShutdown (sent)")
            : NS_LITERAL_CSTRING("SendFinishShutdown (failed)"));
 }
 
-PBrowserOrId ContentChild::GetBrowserOrId(BrowserChild* aBrowserChild) {
-  if (!aBrowserChild || this == aBrowserChild->Manager()) {
-    return PBrowserOrId(aBrowserChild);
-  }
-  return PBrowserOrId(aBrowserChild->GetTabId());
-}
-
 mozilla::ipc::IPCResult ContentChild::RecvUpdateWindow(
     const uintptr_t& aChildId) {
 #if defined(XP_WIN)
   NS_ASSERTION(aChildId,
                "Expected child hwnd value for remote plugin instance.");
   mozilla::plugins::PluginInstanceParent* parentInstance =
       mozilla::plugins::PluginInstanceParent::LookupPluginInstanceByID(
           aChildId);
@@ -3522,18 +3477,18 @@ mozilla::ipc::IPCResult ContentChild::Re
   if (IsValidLoadType(aArgs.loadStateLoadType())) {
     loadState->SetLoadType(aArgs.loadStateLoadType());
   }
 
   RefPtr<ChildProcessChannelListener> processListener =
       ChildProcessChannelListener::GetSingleton();
   // The listener will call completeRedirectSetup or asyncOpen on the channel.
   processListener->OnChannelReady(
-      loadState, aArgs.redirectIdentifier(), std::move(aArgs.redirects()),
-      std::move(aEndpoints), aArgs.timing().refOr(nullptr), std::move(resolve));
+      loadState, aArgs.redirectIdentifier(), std::move(aEndpoints),
+      aArgs.timing().refOr(nullptr), std::move(resolve));
   scopeExit.release();
 
   // scopeExit will call CrossProcessRedirectFinished(rv) here
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult ContentChild::RecvStartDelayedAutoplayMediaComponents(
     const MaybeDiscarded<BrowsingContext>& aContext) {
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -9,32 +9,29 @@
 
 #include "base/shared_memory.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/dom/BrowserBridgeChild.h"
 #include "mozilla/dom/ProcessActor.h"
 #include "mozilla/dom/JSProcessActorChild.h"
-#include "mozilla/dom/PBrowserOrId.h"
 #include "mozilla/dom/PContentChild.h"
 #include "mozilla/dom/RemoteBrowser.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/ipc/InputStreamUtils.h"
 #include "mozilla/ipc/Shmem.h"
 #include "nsHashKeys.h"
 #include "nsIContentChild.h"
 #include "nsIObserver.h"
 #include "nsTHashtable.h"
 #include "nsStringFwd.h"
 #include "nsTArrayForwardDeclare.h"
 #include "nsRefPtrHashtable.h"
 
-#include "nsIWindowProvider.h"
-
 #if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
 #  include "nsIFile.h"
 #endif
 
 struct ChromePackage;
 class nsIObserver;
 struct SubstitutionMapping;
 struct OverrideMapping;
@@ -71,32 +68,29 @@ class SharedMap;
 class AlertObserver;
 class ConsoleListener;
 class ClonedMessageData;
 class BrowserChild;
 class GetFilesHelperChild;
 class TabContext;
 enum class MediaControlKeysEvent : uint32_t;
 
-class ContentChild final
-    : public PContentChild,
-      public nsIContentChild,
-      public nsIWindowProvider,
-      public mozilla::ipc::IShmemAllocator,
-      public mozilla::ipc::ChildToParentStreamActorManager,
-      public ProcessActor {
+class ContentChild final : public PContentChild,
+                           public nsIContentChild,
+                           public mozilla::ipc::IShmemAllocator,
+                           public mozilla::ipc::ChildToParentStreamActorManager,
+                           public ProcessActor {
   typedef mozilla::dom::ClonedMessageData ClonedMessageData;
   typedef mozilla::ipc::FileDescriptor FileDescriptor;
   typedef mozilla::ipc::PFileDescriptorSetChild PFileDescriptorSetChild;
 
   friend class PContentChild;
 
  public:
   NS_DECL_NSICONTENTCHILD
-  NS_DECL_NSIWINDOWPROVIDER
 
   ContentChild();
   virtual ~ContentChild();
   NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
   NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override { return 1; }
   NS_IMETHOD_(MozExternalRefCountType) Release(void) override { return 1; }
 
   struct AppInfo {
@@ -502,18 +496,16 @@ class ContentChild final
       const IPCTabContext& aContext, const WindowGlobalInit& aWindowInit,
       const uint32_t& aChromeFlags, const ContentParentId& aCpID,
       const bool& aIsForBrowser, const bool& aIsTopLevel);
 
   FORWARD_SHMEM_ALLOCATOR_TO(PContentChild)
 
   void GetAvailableDictionaries(nsTArray<nsString>& aDictionaries);
 
-  PBrowserOrId GetBrowserOrId(BrowserChild* aBrowserChild);
-
   PWebrtcGlobalChild* AllocPWebrtcGlobalChild();
 
   bool DeallocPWebrtcGlobalChild(PWebrtcGlobalChild* aActor);
 
   PContentPermissionRequestChild* AllocPContentPermissionRequestChild(
       const nsTArray<PermissionRequest>& aRequests,
       const IPC::Principal& aPrincipal,
       const IPC::Principal& aTopLevelPrincipal,
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1278,17 +1278,17 @@ already_AddRefed<RemoteBrowser> ContentP
   if (NS_WARN_IF(!childEp.IsValid())) {
     return nullptr;
   }
 
   ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
   cpm->RegisterRemoteFrame(browserParent);
 
   nsCOMPtr<nsIPrincipal> initialPrincipal =
-      NullPrincipal::Create(aContext.OriginAttributesRef());
+      NullPrincipal::Create(aBrowsingContext->OriginAttributesRef());
   WindowGlobalInit windowInit = WindowGlobalActor::AboutBlankInitializer(
       aBrowsingContext, initialPrincipal);
 
   auto windowParent =
       MakeRefPtr<WindowGlobalParent>(windowInit, /* inprocess */ false);
 
   // Open a remote endpoint for the initial PWindowGlobal actor.
   ManagedEndpoint<PWindowGlobalChild> windowEp =
@@ -3155,33 +3155,26 @@ bool ContentParent::DeallocPJavaScriptPa
   return true;
 }
 
 bool ContentParent::CanOpenBrowser(const IPCTabContext& aContext) {
   // (PopupIPCTabContext lets the child process prove that it has access to
   // the app it's trying to open.)
   // On e10s we also allow UnsafeTabContext to allow service workers to open
   // windows. This is enforced in MaybeInvalidTabContext.
-  if (aContext.type() != IPCTabContext::TPopupIPCTabContext &&
-      aContext.type() != IPCTabContext::TUnsafeIPCTabContext) {
+  if (aContext.type() != IPCTabContext::TPopupIPCTabContext) {
     ASSERT_UNLESS_FUZZING(
         "Unexpected IPCTabContext type.  Aborting AllocPBrowserParent.");
     return false;
   }
 
   if (aContext.type() == IPCTabContext::TPopupIPCTabContext) {
     const PopupIPCTabContext& popupContext = aContext.get_PopupIPCTabContext();
-    if (popupContext.opener().type() != PBrowserOrId::TPBrowserParent) {
-      ASSERT_UNLESS_FUZZING(
-          "Unexpected PopupIPCTabContext type.  Aborting AllocPBrowserParent.");
-      return false;
-    }
-
-    auto opener =
-        BrowserParent::GetFrom(popupContext.opener().get_PBrowserParent());
+
+    auto opener = BrowserParent::GetFrom(popupContext.openerParent());
     if (!opener) {
       ASSERT_UNLESS_FUZZING(
           "Got null opener from child; aborting AllocPBrowserParent.");
       return false;
     }
   }
 
   MaybeInvalidTabContext tc(aContext);
@@ -3217,18 +3210,17 @@ mozilla::ipc::IPCResult ContentParent::R
   uint32_t chromeFlags = aChromeFlags;
   TabId openerTabId(0);
   ContentParentId openerCpId(0);
   if (aContext.type() == IPCTabContext::TPopupIPCTabContext) {
     // CanOpenBrowser has ensured that the IPCTabContext is of
     // type PopupIPCTabContext, and that the opener BrowserParent is
     // reachable.
     const PopupIPCTabContext& popupContext = aContext.get_PopupIPCTabContext();
-    auto opener =
-        BrowserParent::GetFrom(popupContext.opener().get_PBrowserParent());
+    auto opener = BrowserParent::GetFrom(popupContext.openerParent());
     openerTabId = opener->GetTabId();
     openerCpId = opener->Manager()->ChildID();
 
     // We must ensure that the private browsing and remoteness flags
     // match those of the opener.
     nsCOMPtr<nsILoadContext> loadContext = opener->GetLoadContext();
     if (!loadContext) {
       return IPC_FAIL(this, "Missing Opener LoadContext");
@@ -3262,20 +3254,19 @@ mozilla::ipc::IPCResult ContentParent::R
 
   // Bind the created BrowserParent to IPC to actually link the actor.
   if (NS_WARN_IF(!BindPBrowserEndpoint(std::move(aBrowserEp), parent))) {
     return IPC_FAIL(this, "BindPBrowserEndpoint failed");
   }
 
   // XXX: Why are we checking these requirements? It seems we should register
   // the created frame unconditionally?
-  if (openerTabId > 0 ||
-      aContext.type() == IPCTabContext::TUnsafeIPCTabContext) {
+  if (openerTabId > 0) {
     // The creation of PBrowser was triggered from content process through
-    // either window.open() or service worker's openWindow().
+    // window.open().
     // We need to register remote frame with the child generated tab id.
     auto* cpm = ContentProcessManager::GetSingleton();
     if (!cpm->RegisterRemoteFrame(parent)) {
       return IPC_FAIL(this, "RegisterRemoteFrame Failed");
     }
   }
 
   if (NS_WARN_IF(!parent->BindPWindowGlobalEndpoint(std::move(aWindowEp),
--- a/dom/ipc/DOMTypes.ipdlh
+++ b/dom/ipc/DOMTypes.ipdlh
@@ -24,17 +24,17 @@ using moveonly struct mozilla::Serialize
 using LayoutDeviceIntRect from "Units.h";
 using DesktopIntRect from "Units.h";
 using DesktopToLayoutDeviceScale from "Units.h";
 using CSSToLayoutDeviceScale from "Units.h";
 using CSSRect from "Units.h";
 using CSSSize from "Units.h";
 using ScreenIntSize from "Units.h";
 using mozilla::LayoutDeviceIntPoint from "Units.h";
-using nsSizeMode from "nsIWidgetListener.h";
+using nsSizeMode from "mozilla/dom/TabMessageUtils.h";
 using ScrollbarPreference from "mozilla/ScrollbarPreferences.h";
 using hal::ScreenOrientation from "mozilla/HalScreenConfiguration.h";
 using mozilla::gfx::SurfaceFormat from "mozilla/gfx/Types.h";
 using refcounted class nsIPrincipal from "mozilla/dom/PermissionMessageUtils.h";
 using mozilla::dom::MaybeDiscardedBrowsingContext from "mozilla/dom/BrowsingContext.h";
 using refcounted class nsIURI from "mozilla/ipc/URIUtils.h";
 using refcounted class nsIContentSecurityPolicy from "mozilla/dom/CSPMessageUtils.h";
 using refcounted class nsIInputStream from "mozilla/ipc/IPCStreamUtils.h";
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -959,23 +959,16 @@ child:
      *
      * @param event keyboard event
      * @param isTrusted true if triggered by a trusted key event
      */
     async HandleAccessKey(WidgetKeyboardEvent event,
                           uint32_t[] charCodes);
 
     /**
-     * Tells the root child docShell whether or not to use
-     * global history. This is sent right after the PBrowser
-     * is bound to a frameloader element.
-     */
-    async SetUseGlobalHistory(bool aUse);
-
-    /**
      * HandledWindowedPluginKeyEvent() is always called after posting a native
      * key event with OnWindowedPluginKeyEvent().
      *
      * @param aKeyEventData      The key event which was posted to the parent
      *                           process.
      * @param aIsConsumed        true if aKeyEventData is consumed in the
      *                           parent process.  Otherwise, false.
      */
deleted file mode 100644
--- a/dom/ipc/PBrowserOrId.ipdlh
+++ /dev/null
@@ -1,21 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set sw=2 ts=8 et tw=80 ft=c: */
-/* 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 protocol PBrowser;
-
-using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h";
-
-namespace mozilla {
-namespace dom {
-
-union PBrowserOrId
-{
-  nullable PBrowser;
-  TabId;
-};
-
-} // namespace dom
-} // namespace mozilla
\ No newline at end of file
--- a/dom/ipc/PTabContext.ipdlh
+++ b/dom/ipc/PTabContext.ipdlh
@@ -2,38 +2,34 @@
 /* vim: set sw=4 ts=8 et tw=80 ft=cpp : */
 /* 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 "mozilla/dom/TabMessageUtils.h";
 
 include protocol PBrowser;
-include PBrowserOrId;
 
 using UIStateChangeType from "nsPIDOMWindow.h";
 using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h";
 
 namespace mozilla {
 namespace dom {
 
 // An IPCTabContext which corresponds to a PBrowser opened by a child when it
 // receives window.open().
 struct PopupIPCTabContext
 {
-  PBrowserOrId opener;
+  PBrowser opener;
   uint64_t chromeOuterWindowID;
 };
 
 // An IPCTabContext which corresponds to an app, browser, or normal frame.
 struct FrameIPCTabContext
 {
-  // The originAttributes dictionary.
-  OriginAttributes originAttributes;
-
   uint64_t chromeOuterWindowID;
 
   // The requested presentation URL.
   // This value would be empty if the TabContext isn't created for
   // presented content.
   nsString presentationURL;
 
   // Keyboard indicator state inherited from the parent.
@@ -43,32 +39,24 @@ struct FrameIPCTabContext
   uint32_t maxTouchPoints;
 };
 
 struct JSPluginFrameIPCTabContext
 {
   uint32_t jsPluginId;
 };
 
-// XXXcatalinb: This is only used by ServiceWorkerClients::OpenWindow.
-// Because service workers don't have an associated BrowserChild
-// we can't satisfy the security constraints on b2g. As such, the parent
-// process will accept this tab context only on desktop.
-struct UnsafeIPCTabContext
-{ };
-
 // IPCTabContext is an analog to mozilla::dom::TabContext.  Both specify an
 // iframe/PBrowser's own and containing app-ids and tell you whether the
 // iframe/PBrowser is a browser frame.  But only IPCTabContext is allowed to
 // travel over IPC.
 //
 // We need IPCTabContext (specifically, PopupIPCTabContext) to prevent a
 // privilege escalation attack by a compromised child process.
 union IPCTabContext
 {
   PopupIPCTabContext;
   FrameIPCTabContext;
   JSPluginFrameIPCTabContext;
-  UnsafeIPCTabContext;
 };
 
 }
 }
--- a/dom/ipc/TabContext.cpp
+++ b/dom/ipc/TabContext.cpp
@@ -36,59 +36,41 @@ bool TabContext::SetTabContext(const Tab
   NS_ENSURE_FALSE(mInitialized, false);
 
   *this = aContext;
   mInitialized = true;
 
   return true;
 }
 
-void TabContext::SetPrivateBrowsingAttributes(bool aIsPrivateBrowsing) {
-  mOriginAttributes.SyncAttributesWithPrivateBrowsing(aIsPrivateBrowsing);
-}
-
-void TabContext::SetFirstPartyDomainAttributes(
-    const nsAString& aFirstPartyDomain) {
-  mOriginAttributes.SetFirstPartyDomain(true, aFirstPartyDomain);
-}
-
 bool TabContext::UpdateTabContextAfterSwap(const TabContext& aContext) {
   // This is only used after already initialized.
   MOZ_ASSERT(mInitialized);
 
   // The only permissable changes are to mChromeOuterWindowID.  All other fields
   // must match for the change to be accepted.
-  if (aContext.mOriginAttributes != mOriginAttributes) {
-    return false;
-  }
 
   mChromeOuterWindowID = aContext.mChromeOuterWindowID;
   return true;
 }
 
-const OriginAttributes& TabContext::OriginAttributesRef() const {
-  return mOriginAttributes;
-}
-
 const nsAString& TabContext::PresentationURL() const {
   return mPresentationURL;
 }
 
 UIStateChangeType TabContext::ShowFocusRings() const { return mShowFocusRings; }
 
 bool TabContext::SetTabContext(uint64_t aChromeOuterWindowID,
                                UIStateChangeType aShowFocusRings,
-                               const OriginAttributes& aOriginAttributes,
                                const nsAString& aPresentationURL,
                                uint32_t aMaxTouchPoints) {
   NS_ENSURE_FALSE(mInitialized, false);
 
   mInitialized = true;
   mChromeOuterWindowID = aChromeOuterWindowID;
-  mOriginAttributes = aOriginAttributes;
   mPresentationURL = aPresentationURL;
   mShowFocusRings = aShowFocusRings;
   mMaxTouchPoints = aMaxTouchPoints;
   return true;
 }
 
 bool TabContext::SetTabContextForJSPluginFrame(int32_t aJSPluginID) {
   NS_ENSURE_FALSE(mInitialized, false);
@@ -98,106 +80,63 @@ bool TabContext::SetTabContextForJSPlugi
   return true;
 }
 
 IPCTabContext TabContext::AsIPCTabContext() const {
   if (IsJSPlugin()) {
     return IPCTabContext(JSPluginFrameIPCTabContext(mJSPluginID));
   }
 
-  return IPCTabContext(
-      FrameIPCTabContext(mOriginAttributes, mChromeOuterWindowID,
-                         mPresentationURL, mShowFocusRings, mMaxTouchPoints));
+  return IPCTabContext(FrameIPCTabContext(mChromeOuterWindowID,
+                                          mPresentationURL, mShowFocusRings,
+                                          mMaxTouchPoints));
 }
 
 MaybeInvalidTabContext::MaybeInvalidTabContext(const IPCTabContext& aParams)
     : mInvalidReason(nullptr) {
   uint64_t chromeOuterWindowID = 0;
   int32_t jsPluginId = -1;
-  OriginAttributes originAttributes;
   nsAutoString presentationURL;
   UIStateChangeType showFocusRings = UIStateChangeType_NoChange;
   uint32_t maxTouchPoints = 0;
 
   switch (aParams.type()) {
     case IPCTabContext::TPopupIPCTabContext: {
       const PopupIPCTabContext& ipcContext = aParams.get_PopupIPCTabContext();
 
-      TabContext* context;
-      if (ipcContext.opener().type() == PBrowserOrId::TPBrowserParent) {
-        context =
-            BrowserParent::GetFrom(ipcContext.opener().get_PBrowserParent());
-        if (!context) {
-          mInvalidReason =
-              "Child is-browser process tried to "
-              "open a null tab.";
-          return;
-        }
-      } else if (ipcContext.opener().type() == PBrowserOrId::TPBrowserChild) {
-        context =
-            static_cast<BrowserChild*>(ipcContext.opener().get_PBrowserChild());
-      } else if (ipcContext.opener().type() == PBrowserOrId::TTabId) {
-        // We should never get here because this PopupIPCTabContext is only
-        // used for allocating a new tab id, not for allocating a PBrowser.
-        mInvalidReason =
-            "Child process tried to open an tab without the opener "
-            "information.";
-        return;
-      } else {
-        // This should be unreachable because PopupIPCTabContext::opener is not
-        // a nullable field.
-        mInvalidReason = "PopupIPCTabContext::opener was null (?!).";
-        return;
-      }
-
-      originAttributes = context->mOriginAttributes;
       chromeOuterWindowID = ipcContext.chromeOuterWindowID();
       break;
     }
     case IPCTabContext::TJSPluginFrameIPCTabContext: {
       const JSPluginFrameIPCTabContext& ipcContext =
           aParams.get_JSPluginFrameIPCTabContext();
 
       jsPluginId = ipcContext.jsPluginId();
       break;
     }
     case IPCTabContext::TFrameIPCTabContext: {
       const FrameIPCTabContext& ipcContext = aParams.get_FrameIPCTabContext();
 
       chromeOuterWindowID = ipcContext.chromeOuterWindowID();
       presentationURL = ipcContext.presentationURL();
       showFocusRings = ipcContext.showFocusRings();
-      originAttributes = ipcContext.originAttributes();
       maxTouchPoints = ipcContext.maxTouchPoints();
       break;
     }
-    case IPCTabContext::TUnsafeIPCTabContext: {
-      // XXXcatalinb: This used *only* by ServiceWorkerClients::OpenWindow.
-      // It is meant as a temporary solution until service workers can
-      // provide a BrowserChild equivalent. Don't allow this on b2g since
-      // it might be used to escalate privileges.
-      if (!StaticPrefs::dom_serviceWorkers_enabled()) {
-        mInvalidReason = "ServiceWorkers should be enabled.";
-        return;
-      }
-
-      break;
-    }
     default: {
       MOZ_CRASH();
     }
   }
 
   bool rv;
   if (jsPluginId >= 0) {
     rv = mTabContext.SetTabContextForJSPluginFrame(jsPluginId);
   } else {
     rv = mTabContext.SetTabContext(chromeOuterWindowID, showFocusRings,
-                                   originAttributes, presentationURL,
-                                   maxTouchPoints);
+                                   presentationURL, maxTouchPoints);
   }
   if (!rv) {
     mInvalidReason = "Couldn't initialize TabContext.";
   }
 }
 
 bool MaybeInvalidTabContext::IsValid() { return mInvalidReason == nullptr; }
 
--- a/dom/ipc/TabContext.h
+++ b/dom/ipc/TabContext.h
@@ -40,23 +40,16 @@ class TabContext {
   IPCTabContext AsIPCTabContext() const;
 
   bool IsJSPlugin() const;
   int32_t JSPluginId() const;
 
   uint64_t ChromeOuterWindowID() const;
 
   /**
-   * OriginAttributesRef() returns the OriginAttributes of this frame to
-   * the caller. This is used to store any attribute associated with the frame's
-   * docshell.
-   */
-  const OriginAttributes& OriginAttributesRef() const;
-
-  /**
    * Returns the presentation URL associated with the tab if this tab is
    * created for presented content
    */
   const nsAString& PresentationURL() const;
 
   UIStateChangeType ShowFocusRings() const;
 
   uint32_t MaxTouchPoints() const { return mMaxTouchPoints; }
@@ -73,29 +66,18 @@ class TabContext {
    * other than the no-args constructor.
    */
 
   /**
    * Set this TabContext to match the given TabContext.
    */
   bool SetTabContext(const TabContext& aContext);
 
-  /**
-   * Set the tab context's origin attributes to a private browsing value.
-   */
-  void SetPrivateBrowsingAttributes(bool aIsPrivateBrowsing);
-
-  /**
-   * Set the first party domain of the tab context's origin attributes.
-   */
-  void SetFirstPartyDomainAttributes(const nsAString& aFirstPartyDomain);
-
   bool SetTabContext(uint64_t aChromeOuterWindowID,
                      UIStateChangeType aShowFocusRings,
-                     const OriginAttributes& aOriginAttributes,
                      const nsAString& aPresentationURL,
                      uint32_t aMaxTouchPoints);
 
   /**
    * Modify this TabContext to match the given TabContext.  This is a special
    * case triggered by nsFrameLoader::SwapWithOtherRemoteLoader which may have
    * caused the owner content to change.
    *
@@ -128,21 +110,16 @@ class TabContext {
   /**
    * The outerWindowID of the window hosting the remote frameloader.
    */
   uint64_t mChromeOuterWindowID;
 
   int32_t mJSPluginID;
 
   /**
-   * OriginAttributes of the top level tab docShell
-   */
-  OriginAttributes mOriginAttributes;
-
-  /**
    * The requested presentation URL.
    */
   nsString mPresentationURL;
 
   /**
    * Keyboard indicator state (focus rings).
    */
   UIStateChangeType mShowFocusRings;
@@ -161,31 +138,25 @@ class TabContext {
 class MutableTabContext : public TabContext {
  public:
   bool SetTabContext(const TabContext& aContext) {
     return TabContext::SetTabContext(aContext);
   }
 
   bool SetTabContext(uint64_t aChromeOuterWindowID,
                      UIStateChangeType aShowFocusRings,
-                     const OriginAttributes& aOriginAttributes,
                      const nsAString& aPresentationURL,
                      uint32_t aMaxTouchPoints) {
     return TabContext::SetTabContext(aChromeOuterWindowID, aShowFocusRings,
-                                     aOriginAttributes, aPresentationURL,
-                                     aMaxTouchPoints);
+                                     aPresentationURL, aMaxTouchPoints);
   }
 
   bool SetTabContextForJSPluginFrame(uint32_t aJSPluginID) {
     return TabContext::SetTabContextForJSPluginFrame(aJSPluginID);
   }
-
-  void SetFirstPartyDomainAttributes(const nsAString& aFirstPartyDomain) {
-    TabContext::SetFirstPartyDomainAttributes(aFirstPartyDomain);
-  }
 };
 
 /**
  * MaybeInvalidTabContext is a simple class that lets you transform an
  * IPCTabContext into a TabContext.
  *
  * The issue is that an IPCTabContext is not necessarily valid.  So to convert
  * an IPCTabContext into a TabContext, you construct a MaybeInvalidTabContext,
--- a/dom/ipc/WindowGlobalParent.cpp
+++ b/dom/ipc/WindowGlobalParent.cpp
@@ -49,20 +49,19 @@ using namespace mozilla::ipc;
 using namespace mozilla::dom::ipc;
 
 namespace mozilla {
 namespace dom {
 
 WindowGlobalParent::WindowGlobalParent(const WindowGlobalInit& aInit,
                                        bool aInProcess)
     : WindowContext(aInit.browsingContext().GetMaybeDiscarded(),
-                    aInit.innerWindowId(), {}),
+                    aInit.innerWindowId(), aInProcess, {}),
       mDocumentPrincipal(aInit.principal()),
       mDocumentURI(aInit.documentURI()),
-      mInProcess(aInProcess),
       mIsInitialDocument(false),
       mHasBeforeUnload(false),
       mSandboxFlags(0),
       mDocumentHasLoaded(false),
       mDocumentHasUserInteracted(false),
       mBlockAllMixedContent(false),
       mUpgradeInsecureRequests(false) {
   MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess(), "Parent process only");
@@ -80,17 +79,17 @@ void WindowGlobalParent::Init(const Wind
 
   // Invoke our base class' `Init` method. This will register us in
   // `gWindowContexts`.
   WindowContext::Init();
 
   // Determine which content process the window global is coming from.
   dom::ContentParentId processId(0);
   ContentParent* cp = nullptr;
-  if (!mInProcess) {
+  if (!IsInProcess()) {
     cp = static_cast<ContentParent*>(Manager()->Manager());
     processId = cp->ChildID();
 
     // Ensure the content process has permissions for this principal.
     cp->TransmitPermissionsForPrincipal(mDocumentPrincipal);
   }
 
   MOZ_DIAGNOSTIC_ASSERT(
@@ -639,34 +638,34 @@ void WindowGlobalParent::ActorDestroy(Ac
   for (auto& context : toDiscard) {
     context->Detach(/* aFromIPC */ true);
   }
 
   // Note that our WindowContext has become discarded.
   WindowContext::Discard();
 
   ContentParent* cp = nullptr;
-  if (!mInProcess) {
+  if (!IsInProcess()) {
     cp = static_cast<ContentParent*>(Manager()->Manager());
   }
 
   RefPtr<WindowGlobalParent> self(this);
   Group()->EachOtherParent(cp, [&](ContentParent* otherContent) {
     // Keep the WindowContext alive until other processes have acknowledged it
     // has been discarded.
     auto resolve = [self](bool) {};
     auto reject = [self](mozilla::ipc::ResponseRejectReason) {};
     otherContent->SendDiscardWindowContext(InnerWindowId(), resolve, reject);
   });
 
   // Report content blocking log when destroyed.
   // There shouldn't have any content blocking log when a documnet is loaded in
   // the parent process(See NotifyContentBlockingeEvent), so we could skip
   // reporting log when it is in-process.
-  if (!mInProcess) {
+  if (!IsInProcess()) {
     RefPtr<BrowserParent> browserParent =
         static_cast<BrowserParent*>(Manager());
     if (browserParent) {
       nsCOMPtr<nsILoadContext> loadContext = browserParent->GetLoadContext();
       if (loadContext && !loadContext->UsePrivateBrowsing() &&
           BrowsingContext()->IsTopContent()) {
         GetContentBlockingLog()->ReportLog(DocumentPrincipal());
 
--- a/dom/ipc/WindowGlobalParent.h
+++ b/dom/ipc/WindowGlobalParent.h
@@ -74,20 +74,16 @@ class WindowGlobalParent final : public 
   }
   CanonicalBrowsingContext* GetBrowsingContext() {
     return CanonicalBrowsingContext::Cast(WindowContext::GetBrowsingContext());
   }
 
   // Has this actor been shut down
   bool IsClosed() { return !CanSend(); }
 
-  // Check if this actor is managed by PInProcess, as-in the document is loaded
-  // in-process.
-  bool IsInProcess() { return mInProcess; }
-
   // Get the other side of this actor if it is an in-process actor. Returns
   // |nullptr| if the actor has been torn down, or is not in-process.
   already_AddRefed<WindowGlobalChild> GetChildActor();
 
   // Get a JS actor object by name.
   already_AddRefed<JSWindowActorParent> GetActor(const nsACString& aName,
                                                  ErrorResult& aRv);
 
@@ -243,17 +239,16 @@ class WindowGlobalParent final : public 
   // NOTE: This document principal doesn't reflect possible |document.domain|
   // mutations which may have been made in the actual document.
   nsCOMPtr<nsIPrincipal> mDocumentPrincipal;
   nsCOMPtr<nsIPrincipal> mDocContentBlockingAllowListPrincipal;
   nsCOMPtr<nsIURI> mDocumentURI;
   nsString mDocumentTitle;
 
   nsRefPtrHashtable<nsCStringHashKey, JSWindowActorParent> mWindowActors;
-  bool mInProcess;
   bool mIsInitialDocument;
 
   // True if this window has a "beforeunload" event listener.
   bool mHasBeforeUnload;
 
   // The log of all content blocking actions taken on the document related to
   // this WindowGlobalParent. This is only stored on top-level documents and
   // includes the activity log for all of the nested subdocuments as well.
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -155,17 +155,16 @@ PREPROCESSED_IPDL_SOURCES += [
     'PBrowser.ipdl',
     'PBrowserBridge.ipdl',
     'PContent.ipdl',
 ]
 
 IPDL_SOURCES += [
     'DOMTypes.ipdlh',
     'MemoryReportTypes.ipdlh',
-    'PBrowserOrId.ipdlh',
     'PColorPicker.ipdl',
     'PContentPermission.ipdlh',
     'PContentPermissionRequest.ipdl',
     'PCycleCollectWithLogs.ipdl',
     'PFilePicker.ipdl',
     'PLoginReputation.ipdl',
     'PPluginWidget.ipdl',
     'PProcessHangMonitor.ipdl',
--- a/dom/media/eme/EMEUtils.cpp
+++ b/dom/media/eme/EMEUtils.cpp
@@ -46,24 +46,16 @@ void CopyArrayBufferViewOrArrayBufferDat
   ArrayData data = GetArrayBufferViewOrArrayBufferData(aBufferOrView);
   aOutData.Clear();
   if (!data.IsValid()) {
     return;
   }
   aOutData.AppendElements(data.mData, data.mLength);
 }
 
-void CopyArrayBufferViewOrArrayBufferData(const dom::ArrayBuffer& aBuffer,
-                                          nsTArray<uint8_t>& aOutData) {
-  JS::AutoCheckCannotGC nogc;
-  aBuffer.ComputeState();
-  aOutData.Clear();
-  aOutData.AppendElements(aBuffer.Data(), aBuffer.Length());
-}
-
 bool IsClearkeyKeySystem(const nsAString& aKeySystem) {
   return aKeySystem.EqualsLiteral(EME_KEY_SYSTEM_CLEARKEY);
 }
 
 bool IsWidevineKeySystem(const nsAString& aKeySystem) {
   return aKeySystem.EqualsLiteral(EME_KEY_SYSTEM_WIDEVINE);
 }
 
--- a/dom/media/eme/EMEUtils.h
+++ b/dom/media/eme/EMEUtils.h
@@ -42,20 +42,16 @@ LogModule* GetEMEVerboseLog();
 // Helper function to extract a copy of data coming in from JS in an
 // (ArrayBuffer or ArrayBufferView) IDL typed function argument.
 //
 // Only call this on a properly initialized ArrayBufferViewOrArrayBuffer.
 void CopyArrayBufferViewOrArrayBufferData(
     const dom::ArrayBufferViewOrArrayBuffer& aBufferOrView,
     nsTArray<uint8_t>& aOutData);
 
-// Overload for ArrayBuffer
-void CopyArrayBufferViewOrArrayBufferData(const dom::ArrayBuffer& aBufferOrView,
-                                          nsTArray<uint8_t>& aOutData);
-
 struct ArrayData {
   explicit ArrayData(const uint8_t* aData, size_t aLength)
       : mData(aData), mLength(aLength) {}
   const uint8_t* mData;
   const size_t mLength;
   bool IsValid() const { return mData != nullptr && mLength != 0; }
   bool operator==(const nsTArray<uint8_t>& aOther) const {
     return mLength == aOther.Length() &&
--- a/dom/media/eme/MediaEncryptedEvent.cpp
+++ b/dom/media/eme/MediaEncryptedEvent.cpp
@@ -2,16 +2,17 @@
 /* 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 "MediaEncryptedEvent.h"
 #include "mozilla/dom/MediaEncryptedEventBinding.h"
 #include "nsContentUtils.h"
+#include "js/ArrayBuffer.h"
 #include "jsfriendapi.h"
 #include "nsINode.h"
 #include "mozilla/dom/MediaKeys.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(MediaEncryptedEvent)
@@ -72,21 +73,19 @@ already_AddRefed<MediaEncryptedEvent> Me
     const GlobalObject& aGlobal, const nsAString& aType,
     const MediaKeyNeededEventInit& aEventInitDict, ErrorResult& aRv) {
   nsCOMPtr<EventTarget> owner = do_QueryInterface(aGlobal.GetAsSupports());
   RefPtr<MediaEncryptedEvent> e = new MediaEncryptedEvent(owner);
   bool trusted = e->Init(owner);
   e->InitEvent(aType, aEventInitDict.mBubbles, aEventInitDict.mCancelable);
   e->mInitDataType = aEventInitDict.mInitDataType;
   if (!aEventInitDict.mInitData.IsNull()) {
-    const auto& a = aEventInitDict.mInitData.Value();
-    nsTArray<uint8_t> initData;
-    CopyArrayBufferViewOrArrayBufferData(a, initData);
-    e->mInitData = ArrayBuffer::Create(aGlobal.Context(), initData.Length(),
-                                       initData.Elements());
+    JS::Rooted<JSObject*> buffer(aGlobal.Context(),
+                                 aEventInitDict.mInitData.Value().Obj());
+    e->mInitData = JS::CopyArrayBuffer(aGlobal.Context(), buffer);
     if (!e->mInitData) {
       aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
       return nullptr;
     }
   }
   e->SetTrusted(trusted);
   return e.forget();
 }
--- a/dom/media/eme/MediaKeyMessageEvent.cpp
+++ b/dom/media/eme/MediaKeyMessageEvent.cpp
@@ -1,16 +1,17 @@
 /* -*- 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 "mozilla/dom/MediaKeyMessageEvent.h"
 #include "mozilla/dom/MediaKeyMessageEventBinding.h"
+#include "js/ArrayBuffer.h"
 #include "js/RootingAPI.h"
 #include "jsfriendapi.h"
 #include "mozilla/dom/Nullable.h"
 #include "mozilla/dom/PrimitiveConversions.h"
 #include "mozilla/HoldDropJSObjects.h"
 #include "mozilla/dom/TypedArray.h"
 #include "nsContentUtils.h"
 #include "mozilla/dom/MediaKeys.h"
@@ -71,20 +72,19 @@ already_AddRefed<MediaKeyMessageEvent> M
 
 already_AddRefed<MediaKeyMessageEvent> MediaKeyMessageEvent::Constructor(
     const GlobalObject& aGlobal, const nsAString& aType,
     const MediaKeyMessageEventInit& aEventInitDict, ErrorResult& aRv) {
   nsCOMPtr<EventTarget> owner = do_QueryInterface(aGlobal.GetAsSupports());
   RefPtr<MediaKeyMessageEvent> e = new MediaKeyMessageEvent(owner);
   bool trusted = e->Init(owner);
   e->InitEvent(aType, aEventInitDict.mBubbles, aEventInitDict.mCancelable);
-  nsTArray<uint8_t> initData;
-  CopyArrayBufferViewOrArrayBufferData(aEventInitDict.mMessage, initData);
-  e->mMessage = ArrayBuffer::Create(aGlobal.Context(), initData.Length(),
-                                    initData.Elements());
+  JS::Rooted<JSObject*> buffer(aGlobal.Context(),
+                               aEventInitDict.mMessage.Obj());
+  e->mMessage = JS::CopyArrayBuffer(aGlobal.Context(), buffer);
   if (!e->mMessage) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return nullptr;
   }
   e->mMessageType = aEventInitDict.mMessageType;
   e->SetTrusted(trusted);
   e->SetComposed(aEventInitDict.mComposed);
   return e.forget();
--- a/dom/security/nsCSPService.cpp
+++ b/dom/security/nsCSPService.cpp
@@ -14,16 +14,17 @@
 #include "nsCSPService.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsError.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
 #include "nsAsyncRedirectVerifyHelper.h"
 #include "nsContentUtils.h"
 #include "nsContentPolicyUtils.h"
 #include "nsNetUtil.h"
+#include "mozilla/net/DocumentLoadListener.h"
 
 using namespace mozilla;
 
 static LazyLogModule gCspPRLog("CSP");
 
 CSPService::CSPService() = default;
 
 CSPService::~CSPService() = default;
@@ -225,20 +226,26 @@ NS_IMETHODIMP
 CSPService::AsyncOnChannelRedirect(nsIChannel* oldChannel,
                                    nsIChannel* newChannel, uint32_t flags,
                                    nsIAsyncVerifyRedirectCallback* callback) {
   net::nsAsyncRedirectAutoCallback autoCallback(callback);
 
   if (XRE_IsE10sParentProcess()) {
     nsCOMPtr<nsIParentChannel> parentChannel;
     NS_QueryNotificationCallbacks(oldChannel, parentChannel);
+    RefPtr<net::DocumentLoadListener> docListener =
+        do_QueryObject(parentChannel);
     // Since this is an IPC'd channel we do not have access to the request
     // context. In turn, we do not have an event target for policy violations.
     // Enforce the CSP check in the content process where we have that info.
-    if (parentChannel) {
+    // We allow redirect checks to run for document loads via
+    // DocumentLoadListener, since these are fully supported and we don't expose
+    // the redirects to the content process. We can't do this for all request
+    // types yet because we don't serialize nsICSPEventListener.
+    if (parentChannel && !docListener) {
       return NS_OK;
     }
   }
 
   nsCOMPtr<nsIURI> newUri;
   nsresult rv = newChannel->GetURI(getter_AddRefs(newUri));
   NS_ENSURE_SUCCESS(rv, rv);
 
--- a/dom/security/nsMixedContentBlocker.cpp
+++ b/dom/security/nsMixedContentBlocker.cpp
@@ -10,16 +10,17 @@
 #include "nsCSPContext.h"
 #include "nsThreadUtils.h"
 #include "nsINode.h"
 #include "nsCOMPtr.h"
 #include "nsDocShell.h"
 #include "nsIWebProgressListener.h"
 #include "nsContentUtils.h"
 #include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/WindowContext.h"
 #include "mozilla/dom/Document.h"
 #include "nsIChannel.h"
 #include "nsIParentChannel.h"
 #include "mozilla/Preferences.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsIProtocolHandler.h"
 #include "nsCharSeparatedTokenizer.h"
 #include "nsISecureBrowserUI.h"
@@ -323,29 +324,22 @@ nsMixedContentBlocker::AsyncOnChannelRed
  * API and AsyncOnChannelRedirect().  See nsIContentPolicy::ShouldLoad()
  * for detailed description of the parameters.
  */
 NS_IMETHODIMP
 nsMixedContentBlocker::ShouldLoad(nsIURI* aContentLocation,
                                   nsILoadInfo* aLoadInfo,
                                   const nsACString& aMimeGuess,
                                   int16_t* aDecision) {
-  uint32_t contentType = aLoadInfo->InternalContentPolicyType();
-  nsCOMPtr<nsISupports> requestingContext = aLoadInfo->GetLoadingContext();
-  nsCOMPtr<nsIPrincipal> loadingPrincipal = aLoadInfo->GetLoadingPrincipal();
-  nsCOMPtr<nsIPrincipal> triggeringPrincipal = aLoadInfo->TriggeringPrincipal();
-
   // We pass in false as the first parameter to ShouldLoad(), because the
   // callers of this method don't know whether the load went through cached
   // image redirects.  This is handled by direct callers of the static
   // ShouldLoad.
-  nsresult rv =
-      ShouldLoad(false,  // aHadInsecureImageRedirect
-                 contentType, aContentLocation, loadingPrincipal,
-                 triggeringPrincipal, requestingContext, aMimeGuess, aDecision);
+  nsresult rv = ShouldLoad(false,  // aHadInsecureImageRedirect
+                           aContentLocation, aLoadInfo, aMimeGuess, aDecision);
 
   if (*aDecision == nsIContentPolicy::REJECT_REQUEST) {
     NS_SetRequestBlockingReason(aLoadInfo,
                                 nsILoadInfo::BLOCKING_REASON_MIXED_BLOCKED);
   }
 
   return rv;
 }
@@ -497,38 +491,43 @@ bool nsMixedContentBlocker::IsPotentiall
     return true;
   }
   return false;
 }
 
 /* Static version of ShouldLoad() that contains all the Mixed Content Blocker
  * logic.  Called from non-static ShouldLoad().
  */
-nsresult nsMixedContentBlocker::ShouldLoad(
-    bool aHadInsecureImageRedirect, uint32_t aContentType,
-    nsIURI* aContentLocation, nsIPrincipal* aLoadingPrincipal,
-    nsIPrincipal* aTriggeringPrincipal, nsISupports* aRequestingContext,
-    const nsACString& aMimeGuess, int16_t* aDecision) {
+nsresult nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect,
+                                           nsIURI* aContentLocation,
+                                           nsILoadInfo* aLoadInfo,
+                                           const nsACString& aMimeGuess,
+                                           int16_t* aDecision) {
   // Asserting that we are on the main thread here and hence do not have to lock
   // and unlock security.mixed_content.block_active_content and
   // security.mixed_content.block_display_content before reading/writing to
   // them.
   MOZ_ASSERT(NS_IsMainThread());
 
-  bool isPreload = nsContentUtils::IsPreloadType(aContentType);
+  uint32_t contentType = aLoadInfo->InternalContentPolicyType();
+  nsCOMPtr<nsISupports> requestingContext = aLoadInfo->GetLoadingContext();
+  nsCOMPtr<nsIPrincipal> loadingPrincipal = aLoadInfo->GetLoadingPrincipal();
+  nsCOMPtr<nsIPrincipal> triggeringPrincipal = aLoadInfo->TriggeringPrincipal();
+  RefPtr<WindowContext> requestingWindow =
+      WindowContext::GetById(aLoadInfo->GetInnerWindowID());
 
   // The content policy type that we receive may be an internal type for
   // scripts.  Let's remember if we have seen a worker type, and reset it to the
   // external type in all cases right now.
   bool isWorkerType =
-      aContentType == nsIContentPolicy::TYPE_INTERNAL_WORKER ||
-      aContentType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER ||
-      aContentType == nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER;
-  aContentType =
-      nsContentUtils::InternalContentPolicyTypeToExternal(aContentType);
+      contentType == nsIContentPolicy::TYPE_INTERNAL_WORKER ||
+      contentType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER ||
+      contentType == nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER;
+  contentType =
+      nsContentUtils::InternalContentPolicyTypeToExternal(contentType);
 
   // Assume active (high risk) content and blocked by default
   MixedContentTypes classification = eMixedScript;
   // Make decision to block/reject by default
   *aDecision = REJECT_REQUEST;
 
   // Notes on non-obvious decisions:
   //
@@ -586,17 +585,17 @@ nsresult nsMixedContentBlocker::ShouldLo
   // TYPE_SAVEAS_DOWNLOAD: Save-link-as feature is used to download a resource
   // without involving a docShell. This kind of loading must be always be
   // allowed.
 
   static_assert(TYPE_DATAREQUEST == TYPE_XMLHTTPREQUEST,
                 "TYPE_DATAREQUEST is not a synonym for "
                 "TYPE_XMLHTTPREQUEST");
 
-  switch (aContentType) {
+  switch (contentType) {
     // The top-level document cannot be mixed content by definition
     case TYPE_DOCUMENT:
       *aDecision = ACCEPT;
       return NS_OK;
     // Creating insecure websocket connections in a secure page is blocked
     // already in the websocket constructor. We don't need to check the blocking
     // here and we don't want to un-block
     case TYPE_WEBSOCKET:
@@ -682,39 +681,39 @@ nsresult nsMixedContentBlocker::ShouldLo
    * 2) If aLoadingPrincipal does not yield to a requestingLocation, then we
    *    fall back to querying the requestingLocation from aTriggeringPrincipal.
    * 3) If we still end up not having a requestingLocation, we reject the load.
    */
 
   // 1) Check if the load was triggered by the system (SystemPrincipal) or
   // a content script from addons code (ExpandedPrincipal) in which case the
   // load is not subject to mixed content blocking.
-  if (aTriggeringPrincipal) {
-    if (aTriggeringPrincipal->IsSystemPrincipal()) {
+  if (triggeringPrincipal) {
+    if (triggeringPrincipal->IsSystemPrincipal()) {
       *aDecision = ACCEPT;
       return NS_OK;
     }
     nsCOMPtr<nsIExpandedPrincipal> expanded =
-        do_QueryInterface(aTriggeringPrincipal);
+        do_QueryInterface(triggeringPrincipal);
     if (expanded) {
       *aDecision = ACCEPT;
       return NS_OK;
     }
   }
 
   // 2) If aLoadingPrincipal does not provide a requestingLocation, then
   // we fall back to to querying the requestingLocation from
   // aTriggeringPrincipal.
   nsCOMPtr<nsIURI> requestingLocation;
-  auto* baseLoadingPrincipal = BasePrincipal::Cast(aLoadingPrincipal);
+  auto* baseLoadingPrincipal = BasePrincipal::Cast(loadingPrincipal);
   if (baseLoadingPrincipal) {
     baseLoadingPrincipal->GetURI(getter_AddRefs(requestingLocation));
   }
   if (!requestingLocation) {
-    auto* baseTriggeringPrincipal = BasePrincipal::Cast(aTriggeringPrincipal);
+    auto* baseTriggeringPrincipal = BasePrincipal::Cast(triggeringPrincipal);
     if (baseTriggeringPrincipal) {
       baseTriggeringPrincipal->GetURI(getter_AddRefs(requestingLocation));
     }
   }
 
   // 3) Giving up. We still don't have a requesting location, therefore we can't
   // tell if this is a mixed content load. Deny to be safe.
   if (!requestingLocation) {
@@ -773,103 +772,99 @@ nsresult nsMixedContentBlocker::ShouldLo
   // Please note that the CSP directive 'upgrade-insecure-requests' only applies
   // to http: and ws: (for websockets). Websockets are not subject to mixed
   // content blocking since insecure websockets are not allowed within secure
   // pages. Hence, we only have to check against http: here. Skip mixed content
   // blocking if the subresource load uses http: and the CSP directive
   // 'upgrade-insecure-requests' is present on the page.
 
   nsCOMPtr<nsIDocShell> docShell =
-      NS_CP_GetDocShellFromContext(aRequestingContext);
+      NS_CP_GetDocShellFromContext(requestingContext);
   // Carve-out: if we're in the parent and we're loading media, e.g. through
   // webbrowserpersist, don't reject it if we can't find a docshell.
   if (XRE_IsParentProcess() && !docShell &&
-      (aContentType == TYPE_IMAGE || aContentType == TYPE_MEDIA)) {
+      (contentType == TYPE_IMAGE || contentType == TYPE_MEDIA)) {
     *aDecision = ACCEPT;
     return NS_OK;
   }
   // Otherwise, we must have a docshell
   NS_ENSURE_TRUE(docShell, NS_OK);
+  NS_ENSURE_TRUE(requestingWindow, NS_OK);
 
-  Document* document = docShell->GetDocument();
-  MOZ_ASSERT(document, "Expected a document");
-  if (isHttpScheme && document->GetUpgradeInsecureRequests(isPreload)) {
+  if (isHttpScheme && aLoadInfo->GetUpgradeInsecureRequests()) {
     *aDecision = ACCEPT;
     return NS_OK;
   }
 
   // Allow http: mixed content if we are choosing to upgrade them when the
   // pref "security.mixed_content.upgrade_display_content" is true.
   // This behaves like GetUpgradeInsecureRequests above in that the channel will
   // be upgraded to https before fetching any data from the netwerk.
   bool isUpgradableDisplayType =
-      nsContentUtils::IsUpgradableDisplayType(aContentType) &&
+      nsContentUtils::IsUpgradableDisplayType(contentType) &&
       StaticPrefs::security_mixed_content_upgrade_display_content();
   if (isHttpScheme && isUpgradableDisplayType) {
     *aDecision = ACCEPT;
     return NS_OK;
   }
 
   // The page might have set the CSP directive 'block-all-mixed-content' which
   // should block not only active mixed content loads but in fact all mixed
   // content loads, see https://www.w3.org/TR/mixed-content/#strict-checking
   // Block all non secure loads in case the CSP directive is present. Please
   // note that at this point we already know, based on |schemeSecure| that the
   // load is not secure, so we can bail out early at this point.
-  if (document->GetBlockAllMixedContent(isPreload)) {
+  if (aLoadInfo->GetBlockAllMixedContent()) {
     // log a message to the console before returning.
     nsAutoCString spec;
     nsresult rv = aContentLocation->GetSpec(spec);
     NS_ENSURE_SUCCESS(rv, rv);
 
     AutoTArray<nsString, 1> params;
     CopyUTF8toUTF16(spec, *params.AppendElement());
 
-    CSP_LogLocalizedStr(
-        "blockAllMixedContent", params,
-        EmptyString(),  // aSourceFile
-        EmptyString(),  // aScriptSample
-        0,              // aLineNumber
-        0,              // aColumnNumber
-        nsIScriptError::errorFlag, NS_LITERAL_CSTRING("blockAllMixedContent"),
-        document->InnerWindowID(),
-        !!document->NodePrincipal()->OriginAttributesRef().mPrivateBrowsingId);
+    CSP_LogLocalizedStr("blockAllMixedContent", params,
+                        EmptyString(),  // aSourceFile
+                        EmptyString(),  // aScriptSample
+                        0,              // aLineNumber
+                        0,              // aColumnNumber
+                        nsIScriptError::errorFlag,
+                        NS_LITERAL_CSTRING("blockAllMixedContent"),
+                        requestingWindow->Id(),
+                        !!aLoadInfo->GetOriginAttributes().mPrivateBrowsingId);
     *aDecision = REJECT_REQUEST;
     return NS_OK;
   }
 
   // Determine if the rootDoc is https and if the user decided to allow Mixed
   // Content
-  RefPtr<BrowsingContext> bc = docShell->GetBrowsingContext();
-  RefPtr<BrowsingContext> rootBC = bc->Top();
-  bool rootHasSecureConnection = rootBC->GetIsSecure();
-  WindowContext* topWC = bc->GetTopWindowContext();
+  WindowContext* topWC = requestingWindow->TopWindowContext();
+  bool rootHasSecureConnection = topWC->GetBrowsingContext()->GetIsSecure();
   bool allowMixedContent = topWC->GetAllowMixedContent();
 
   // When navigating an iframe, the iframe may be https
   // but its parents may not be.  Check the parents to see if any of them are
   // https. If none of the parents are https, allow the load.
-  if (aContentType == TYPE_SUBDOCUMENT && !rootHasSecureConnection) {
+  if (contentType == TYPE_SUBDOCUMENT && !rootHasSecureConnection) {
     bool httpsParentExists = false;
 
-    RefPtr<BrowsingContext> curBC = docShell->GetBrowsingContext();
-
-    while (!httpsParentExists && curBC) {
-      httpsParentExists = curBC->GetIsSecure();
-      curBC = curBC->GetParent();
+    RefPtr<WindowContext> curWindow = requestingWindow;
+    while (!httpsParentExists && curWindow) {
+      httpsParentExists = curWindow->GetBrowsingContext()->GetIsSecure();
+      curWindow = curWindow->GetParentWindowContext();
     }
 
     if (!httpsParentExists) {
       *aDecision = nsIContentPolicy::ACCEPT;
       return NS_OK;
     }
   }
 
   // Get the root document from the rootShell
-  nsCOMPtr<nsIDocShell> rootShell = rootBC->GetDocShell();
+  nsCOMPtr<nsIDocShell> rootShell = topWC->GetBrowsingContext()->GetDocShell();
   nsCOMPtr<Document> rootDoc = rootShell ? rootShell->GetDocument() : nullptr;
 
   // TODO Fission: Bug 1631405: Make Mixed Content UI fission compatible
   // At this point we know it's a mixed content load, which means we we would
   // allow mixed passive content to load but only allow mixed active content
   // if the user has updated prefs or overriden mixed content using the UI.
   // In fission however, we might not have access to the rootShell or RootDoc
   // so might not be able to access Mixed Content UI. Until we have fixed
@@ -903,20 +898,20 @@ nsresult nsMixedContentBlocker::ShouldLo
   // Allow load and return early.
   if (!securityUI) {
     *aDecision = nsIContentPolicy::ACCEPT;
     return NS_OK;
   }
   nsresult stateRV = securityUI->GetState(&state);
 
   OriginAttributes originAttributes;
-  if (aLoadingPrincipal) {
-    originAttributes = aLoadingPrincipal->OriginAttributesRef();
-  } else if (aTriggeringPrincipal) {
-    originAttributes = aTriggeringPrincipal->OriginAttributesRef();
+  if (loadingPrincipal) {
+    originAttributes = loadingPrincipal->OriginAttributesRef();
+  } else if (triggeringPrincipal) {
+    originAttributes = triggeringPrincipal->OriginAttributesRef();
   }
 
   // At this point we know that the request is mixed content, and the only
   // question is whether we block it.  Record telemetry at this point as to
   // whether HSTS would have fixed things by making the content location
   // into an HTTPS URL.
   //
   // Note that we count this for redirects as well as primary requests. This
@@ -938,17 +933,17 @@ nsresult nsMixedContentBlocker::ShouldLo
       if (cc) {
         cc->SendAccumulateMixedContentHSTS(innerContentLocation, active,
                                            originAttributes);
       }
     }
   }
 
   // set hasMixedContentObjectSubrequest on this object if necessary
-  if (aContentType == TYPE_OBJECT_SUBREQUEST) {
+  if (contentType == TYPE_OBJECT_SUBREQUEST) {
     if (!StaticPrefs::security_mixed_content_block_object_subrequest()) {
       rootDoc->WarnOnceAbout(Document::eMixedDisplayObjectSubrequest);
     }
   }
 
   // If the content is display content, and the pref says display content should
   // be blocked, block it.
   if (StaticPrefs::security_mixed_content_block_display_content() &&
@@ -973,38 +968,38 @@ nsresult nsMixedContentBlocker::ShouldLo
 
         // If mixed active content is loaded, make sure to include that in the
         // state.
         if (rootDoc->GetHasMixedActiveContentLoaded()) {
           state |= nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT;
         }
 
         nativeDocShell->nsDocLoader::OnSecurityChange(
-            aRequestingContext,
+            requestingContext,
             (state |
              nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
       } else {
         // User has overriden the pref and the root is not https;
         // mixed display content was allowed on an https subframe.
         if (NS_SUCCEEDED(stateRV)) {
           nativeDocShell->nsDocLoader::OnSecurityChange(
-              aRequestingContext,
+              requestingContext,
               (state |
                nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
         }
       }
     } else {
       *aDecision = nsIContentPolicy::REJECT_REQUEST;
       LogMixedContentMessage(classification, aContentLocation, topInnerWindowID,
                              eBlocked, requestingLocation);
       if (!rootDoc->GetHasMixedDisplayContentBlocked() &&
           NS_SUCCEEDED(stateRV)) {
         rootDoc->SetHasMixedDisplayContentBlocked(true);
         nativeDocShell->nsDocLoader::OnSecurityChange(
-            aRequestingContext,
+            requestingContext,
             (state |
              nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT));
       }
     }
     return NS_OK;
 
   } else if (StaticPrefs::security_mixed_content_block_active_content() &&
              classification == eMixedScript) {
@@ -1029,27 +1024,27 @@ nsresult nsMixedContentBlocker::ShouldLo
 
         // If mixed display content is loaded, make sure to include that in the
         // state.
         if (rootDoc->GetHasMixedDisplayContentLoaded()) {
           state |= nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT;
         }
 
         nativeDocShell->nsDocLoader::OnSecurityChange(
-            aRequestingContext,
+            requestingContext,
             (state |
              nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
 
         return NS_OK;
       } else {
         // User has already overriden the pref and the root is not https;
         // mixed active content was allowed on an https subframe.
         if (NS_SUCCEEDED(stateRV)) {
           nativeDocShell->nsDocLoader::OnSecurityChange(
-              aRequestingContext,
+              requestingContext,
               (state |
                nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
         }
         return NS_OK;
       }
     } else {
       // User has not overriden the pref by Disabling protection. Reject the
       // request and update the security state.
@@ -1062,33 +1057,33 @@ nsresult nsMixedContentBlocker::ShouldLo
         return NS_OK;
       }
       rootDoc->SetHasMixedActiveContentBlocked(true);
 
       // The user has not overriden the pref, so make sure they still have an
       // option by calling nativeDocShell which will invoke the doorhanger
       if (NS_SUCCEEDED(stateRV)) {
         nativeDocShell->nsDocLoader::OnSecurityChange(
-            aRequestingContext,
+            requestingContext,
             (state |
              nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT));
       }
       return NS_OK;
     }
   } else {
     // The content is not blocked by the mixed content prefs.
 
     // Log a message that we are loading mixed content.
     LogMixedContentMessage(classification, aContentLocation, topInnerWindowID,
                            eUserOverride, requestingLocation);
 
     // Fire the event from a script runner as it is unsafe to run script
     // from within ShouldLoad
     nsContentUtils::AddScriptRunner(new nsMixedContentEvent(
-        aRequestingContext, classification, rootHasSecureConnection));
+        requestingContext, classification, rootHasSecureConnection));
     *aDecision = ACCEPT;
     return NS_OK;
   }
 }
 
 bool nsMixedContentBlocker::URISafeToBeLoadedInSecureContext(nsIURI* aURI) {
   /* Returns a bool if the URI can be loaded as a sub resource safely.
    *
--- a/dom/security/nsMixedContentBlocker.h
+++ b/dom/security/nsMixedContentBlocker.h
@@ -60,20 +60,17 @@ class nsMixedContentBlocker : public nsI
    * Called directly from imageLib when an insecure redirect exists in a cached
    * image load.
    * @param aHadInsecureImageRedirect
    *        boolean flag indicating that an insecure redirect through http
    *        occured when this image was initially loaded and cached.
    * Remaining parameters are from nsIContentPolicy::ShouldLoad().
    */
   static nsresult ShouldLoad(bool aHadInsecureImageRedirect,
-                             uint32_t aContentType, nsIURI* aContentLocation,
-                             nsIPrincipal* aLoadingPrincipal,
-                             nsIPrincipal* aTriggeringPrincipal,
-                             nsISupports* aRequestingContext,
+                             nsIURI* aContentLocation, nsILoadInfo* aLoadInfo,
                              const nsACString& aMimeGuess, int16_t* aDecision);
   static void AccumulateMixedContentHSTS(
       nsIURI* aURI, bool aActive, const OriginAttributes& aOriginAttributes);
 
   static bool URISafeToBeLoadedInSecureContext(nsIURI* aURI);
 
   static void OnPrefChange(const char* aPref, void* aClosure);
   static void GetSecureContextWhiteList(nsACString& aList);
--- a/dom/security/test/csp/test_redirects.html
+++ b/dom/security/test/csp/test_redirects.html
@@ -20,19 +20,22 @@ var path = "/tests/dom/security/test/csp
 // debugging
 function log(s) {
   return;
   dump("**" + s + "\n");
   var log = document.getElementById("log");
   log.textContent = log.textContent+s+"\n";
 }
 
+SpecialPowers.registerObservers("csp-on-violate-policy");
+
 // used to watch if requests are blocked by CSP or allowed through
 function examiner() {
   SpecialPowers.addObserver(this, "csp-on-violate-policy");
+  SpecialPowers.addObserver(this, "specialpowers-csp-on-violate-policy");
   SpecialPowers.addObserver(this, "specialpowers-http-notify-request");
 }
 examiner.prototype  = {
   observe(subject, topic, data) {
     var testpat = new RegExp("testid=([a-z0-9-]+)");
     var asciiSpec;
     var testid;
 
@@ -41,32 +44,33 @@ examiner.prototype  = {
       var allowedUri = data;
       if (!testpat.test(allowedUri)) return;
       testid = testpat.exec(allowedUri)[1];
       if (testExpectedResults[testid] == "completed") return;
       log("allowed: "+allowedUri);
       window.testResult(testid, allowedUri, true);
     }
 
-    else if (topic === "csp-on-violate-policy") {
+    else if (topic === "csp-on-violate-policy" || topic === "specialpowers-csp-on-violate-policy") {
       // request was blocked
       asciiSpec = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIURI"), "asciiSpec");
       if (!testpat.test(asciiSpec)) return;
       testid = testpat.exec(asciiSpec)[1];
       // had to add this check because http-on-modify-request can fire after
       // csp-on-violate-policy, apparently, even though the request does
       // not hit the wire.
       if (testExpectedResults[testid] == "completed") return;
       log("BLOCKED: "+asciiSpec);
       window.testResult(testid, asciiSpec, false);
     }
   },
 
   remove() {
     SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+    SpecialPowers.removeObserver(this, "specialpowers-csp-on-violate-policy");
     SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
   }
 }
 window.examiner = new examiner();
 
 // contains { test_frame_id : expected_result }
 var testExpectedResults = { "font-src": true,
                             "font-src-redir": false,
--- a/dom/tests/mochitest/chrome/chrome.ini
+++ b/dom/tests/mochitest/chrome/chrome.ini
@@ -4,17 +4,16 @@ support-files =
   489127.html
   DOMWindowCreated_chrome.xhtml
   DOMWindowCreated_content.html
   MozDomFullscreen_chrome.xhtml
   child_focus_frame.html
   file_clipboard_events_chrome.html
   file_DOM_element_instanceof.xhtml
   file_MozDomFullscreen.html
-  file_bug799299.xhtml
   file_bug800817.xhtml
   file_bug830858.xhtml
   file_bug1224790-1_modal.xhtml
   file_bug1224790-1_nonmodal.xhtml
   file_bug1224790-2_modal.xhtml
   file_bug1224790-2_nonmodal.xhtml
   file_popup_blocker_chrome.html
   file_subscript_bindings.js
@@ -37,17 +36,16 @@ support-files =
   !/dom/tests/mochitest/general/file_moving_nodeList.html
   !/dom/tests/mochitest/general/file_moving_xhr.html
   !/dom/tests/mochitest/geolocation/network_geolocation.sjs
 
 [test_DOMWindowCreated.xhtml]
 [test_DOM_element_instanceof.xhtml]
 [test_activation.xhtml]
 tags = fullscreen
-[test_bug799299.xhtml]
 [test_bug800817.xhtml]
 [test_bug830858.xhtml]
 [test_bug1224790-1.xhtml]
 tags = openwindow
 skip-if = os != 'mac' || os_version == '10.14' # 10.14 due to bug 1558642
 [test_bug1224790-2.xhtml]
 tags = openwindow
 skip-if = os != 'mac' || os_version == '10.14' # 10.14 due to bug 1558642
deleted file mode 100644
--- a/dom/tests/mochitest/chrome/file_bug799299.xhtml
+++ /dev/null
@@ -1,65 +0,0 @@
-<?xml version="1.0"?>
-<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
-<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=799299
--->
-<window title="Mozilla Bug 799299"
-        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
-  <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
-
-  <!-- test results are displayed in the html:body -->
-  <body xmlns="http://www.w3.org/1999/xhtml">
-  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=799299"
-     target="_blank">Mozilla Bug 799299</a>
-  </body>
-
-  <!-- test code goes here -->
-  <script type="application/javascript">
-  <![CDATA[
-  /** Test for Bug 799299 **/
-
-  function sendClick(win) {
-    var wu = win.windowUtils;
-    wu.sendMouseEventToWindow("mousedown", 10, 10, 0, 0, 0);
-    wu.sendMouseEventToWindow("mouseup", 10, 10, 0, 0, 0);
-  }
-
-  function runTests() {
-    var b1 = document.getElementById("b1");
-    var b2 = document.getElementById("b2");
-    b1.contentWindow.focus();
-    window.arguments[0].is(document.activeElement, b1,
-                           "Focused first iframe");
-
-    var didCallDummy = false;
-    b2.contentWindow.addEventListener("mousedown", function(e) { didCallDummy = true; });
-    sendClick(b2.contentWindow);
-    window.arguments[0].ok(didCallDummy, "dummy mousedown handler should fire");
-    window.arguments[0].is(document.activeElement, b2,
-                           "Focus shifted to second iframe");
-
-    b1.contentWindow.focus();
-    window.arguments[0].is(document.activeElement, b1,
-                           "Re-focused first iframe for the first time");
-
-    var didCallListener = false;
-    b2.contentWindow.addEventListener("mousedown", function(e) { didCallListener = true; e.preventDefault(); });
-    sendClick(b2.contentWindow);
-    window.arguments[0].ok(didCallListener, "mousedown handler should fire");
-    window.arguments[0].is(document.activeElement, b2,
-                           "focus should move to the second iframe");
-
-    window.close();
-    window.arguments[0].SimpleTest.finish();
-  }
-
-  SimpleTest.waitForFocus(runTests);
-  ]]>
-  </script>
-  <hbox flex="1">
-    <browser id="b1" type="content" src="about:blank" flex="1" style="border: 1px solid black;"/>
-    <browser id="b2" type="content" src="about:blank" flex="1" style="border: 1px solid black;"/>
-  </hbox>
-</window>
deleted file mode 100644
--- a/dom/tests/mochitest/chrome/test_bug799299.xhtml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0"?>
-<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
-<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=799299
--->
-<window title="Mozilla Bug 799299" onload="runTests()"
-        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
-  <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
-
-  <!-- test results are displayed in the html:body -->
-  <body xmlns="http://www.w3.org/1999/xhtml">
-  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=799299"
-     target="_blank">Mozilla Bug 799299</a>
-  </body>
-
-  <!-- test code goes here -->
-  <script type="application/javascript">
-  <![CDATA[
-  /** Test for Bug 799299 **/
-
-  function runTests() {
-    window.openDialog("file_bug799299.xhtml", "_blank", "chrome,width=600,height=550,noopener", window);
-  }
-
-  SimpleTest.waitForExplicitFinish();
-
-  ]]>
-  </script>
-</window>
--- a/dom/tests/mochitest/chrome/window_focus.xhtml
+++ b/dom/tests/mochitest/chrome/window_focus.xhtml
@@ -808,49 +808,16 @@ function startTest()
   input2.tabIndex = 2;
   gLastFocusMethod = 0;
   expectFocusShift(() => input2.focus(),
                    null, input2, true, "focus on input with tabindex set");
   gLastFocusMethod = fm.FLAG_BYKEY;
   expectFocusShift(() => synthesizeKey("KEY_Tab", {shiftKey: true}),
                    null, input1, true, "shift+tab on input with tabindex set");
 
-  // ---- test for bug 618907 which ensures that canceling the mousedown event still focuses the
-  //      right frame
-
-  var childContentFrame = document.getElementById("ifa")
-  childContentFrame.style.MozUserFocus = "";
-
-  var frab = childContentFrame.contentDocument.getElementById("fra-b");
-  var mouseDownListener = event => event.preventDefault();
-  frab.addEventListener("mousedown", mouseDownListener, false);
-
-  var childElementToFocus = childContentFrame.contentDocument.getElementById("fra");
-  gLastFocus = childElementToFocus;
-  gLastFocusWindow = childContentFrame.contentWindow;
-  gLastFocus.focus();
-  gEvents = "";
-
-  setFocusTo("t1", window);
-
-  gLastFocusMethod = -1;
-  expectFocusShift(() => synthesizeMouse(frab, 5, 5, { }, childContentFrame.contentWindow),
-                   null, childElementToFocus, true,
-                   "mousedown event canceled - chrome to content");
-
-  frab.removeEventListener("mousedown", mouseDownListener, false);
-
-  var t5 = getById("t5");
-  t5.addEventListener("mousedown", mouseDownListener, false);
-  synthesizeMouse(t5, 10, 10, { })
-  t5.removeEventListener("mousedown", mouseDownListener, false);
-  is(fm.focusedElement, childElementToFocus,
-     "mousedown event cancelled - content to chrome - element");
-  is(fm.focusedWindow, childContentFrame.contentWindow, "mousedown event cancelled - content to chrome - window");
-
   // ---- test to check that refocusing an element during a blur event doesn't succeed
 
   var t1 = getById("t1");
   t1.addEventListener("blur", () => t1.focus(), true);
   t1.focus();
   var t3 = getById("t3");
   synthesizeMouse(t3, 2, 2, { });
   is(fm.focusedElement, t3, "focus during blur");
--- a/editor/libeditor/HTMLEditSubActionHandler.cpp
+++ b/editor/libeditor/HTMLEditSubActionHandler.cpp
@@ -1694,16 +1694,48 @@ EditActionResult HTMLEditor::HandleInser
   }
 
   IgnoredErrorResult ignoredError;
   SelectionRefPtr()->SetInterlinePosition(false, ignoredError);
   NS_WARNING_ASSERTION(!ignoredError.Failed(),
                        "Failed to unset interline position");
 
   if (currentPoint.IsSet()) {
+    if (currentPoint.IsInTextNode() &&
+        IsVisibleTextNode(*currentPoint.ContainerAsText())) {
+      // If the text is visible, we should remove padding <br> element if
+      // there is.  Currently, we should remove it when it's immediately
+      // after the text node.  I.e., not scanning next <br> element outside
+      // the parent element even if there is one in same block because <br>
+      // element should be in same element if it's created for empty line.
+      RefPtr<nsIContent> nextSibling =
+          currentPoint.GetContainer()->GetNextSibling();
+      if (nextSibling && nextSibling->IsHTMLElement(nsGkAtoms::br) &&
+          !IsVisibleBRElement(nextSibling)) {
+        {
+          AutoTrackDOMPoint tracker(RangeUpdaterRef(), &currentPoint);
+          nsresult rv = DeleteNodeWithTransaction(*nextSibling);
+          if (NS_WARN_IF(Destroyed())) {
+            return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
+          }
+          if (NS_FAILED(rv)) {
+            NS_WARNING(
+                "HTMLEditor::DeleteNodeWithTransaction() failed to remove "
+                "unnecessary padding <br> element");
+            return EditActionHandled(rv);
+          }
+        }
+        if (!currentPoint.IsSetAndValid()) {
+          NS_WARNING(
+              "Mutation event listener changed the DOM tree unexpected while "
+              "removing invisible <br> element");
+          return EditActionHandled(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
+        }
+      }
+    }
     nsresult rv = CollapseSelectionTo(currentPoint);
     if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
       return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
     }
     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                          "Selection::Collapse() failed, but ignored");
   }
 
--- a/editor/libeditor/tests/test_bug1250010.html
+++ b/editor/libeditor/tests/test_bug1250010.html
@@ -40,18 +40,18 @@ SimpleTest.waitForFocus(function() {
 
   synthesizeKey("KEY_Enter");
   synthesizeKey("KEY_Enter");
   sendString("b");
   synthesizeKey("KEY_ArrowUp");
   sendString("a");
 
   is(div.innerHTML, "<p><b><font color=\"red\">1234567890</font></b></p>" +
-                    "<p><b><font color=\"red\">a<br></font></b></p>" +
-                    "<p><b><font color=\"red\">b<br></font></b></p>",
+                    "<p><b><font color=\"red\">a</font></b></p>" +
+                    "<p><b><font color=\"red\">b</font></b></p>",
                     "unexpected HTML");
 
   // Second test: Since we modified the code path that splits non-text nodes,
   // test that this works, if the split node is not empty.
   div = document.getElementById("test2");
   div.focus();
   synthesizeMouseAtCenter(div, {});
 
--- a/editor/libeditor/tests/test_bug1330796.html
+++ b/editor/libeditor/tests/test_bug1330796.html
@@ -36,31 +36,31 @@ https://bugzilla.mozilla.org/show_bug.cg
 // at and the expected result HTML.
 var tests = [
   // With style="display: block;".
   [ "<span _moz_quote=true style=\"display: block;\">&gt; mailcite<br></span>", 0,
     "x<br><span style=\"display: block;\">&gt; mailcite<br></span>" ],
   [ "<span _moz_quote=true style=\"display: block;\">&gt; mailcite<br></span>", 5,
     "<span style=\"display: block;\">&gt; mai<br></span>x<br><span style=\"display: block;\">lcite<br></span>"],
   [ "<span _moz_quote=true style=\"display: block;\">&gt; mailcite<br></span>", 10,
-    "<span style=\"display: block;\">&gt; mailcite<br></span>x<br>" ],
+    "<span style=\"display: block;\">&gt; mailcite<br></span>x" ],
   // No <br> at the end to simulate prior deletion to the end of the quote.
   [ "<span _moz_quote=true style=\"display: block;\">&gt; mailcite</span>", 10,
-    "<span style=\"display: block;\">&gt; mailcite<br></span>x<br>" ],
+    "<span style=\"display: block;\">&gt; mailcite<br></span>x" ],
 
   // Without style="display: block;".
   [ "<span _moz_quote=true>&gt; mailcite<br></span>", 0,
     "x<br><span>&gt; mailcite<br></span>" ],
   [ "<span _moz_quote=true>&gt; mailcite<br></span>", 5,
     "<span>&gt; mai</span><br>x<br><span>lcite<br></span>" ],
   [ "<span _moz_quote=true>&gt; mailcite<br></span>", 10,
-    "<span>&gt; mailcite<br></span>x<br>" ],
+    "<span>&gt; mailcite<br></span>x" ],
   // No <br> at the end to simulate prior deletion to the end of the quote.
   [ "<span _moz_quote=true>&gt; mailcite</span>", 10,
-    "<span>&gt; mailcite</span><br>x<br>" ],
+    "<span>&gt; mailcite</span><br>x" ],
 ];
 
 /** Test for Bug 1330796 **/
 
 SimpleTest.waitForExplicitFinish();
 
 SimpleTest.waitForFocus(function() {
   var sel = window.getSelection();
--- a/editor/libeditor/tests/test_bug1385905.html
+++ b/editor/libeditor/tests/test_bug1385905.html
@@ -28,19 +28,18 @@ SimpleTest.waitForFocus(() => {
   var editor = document.getElementById("editor");
   // Click the left blank area of the first line to set cursor to the start of "contents".
   synthesizeMouse(editor, 3, 10, {});
   synthesizeKey("KEY_Enter");
   is(editor.innerHTML, "<div><br></div><div>contents</div>",
      "Typing Enter at start of the <div> element should split the <div> element");
   synthesizeKey("KEY_ArrowUp");
   sendString("x");
-  is(editor.innerHTML, "<div>x<br></div><div>contents</div>",
-     "Typing 'x' at the empty <div> element should just insert 'x' into the <div> element");
-  ensureNoPaddingBR();
+  is(editor.innerHTML, "<div>x</div><div>contents</div>",
+     "Typing 'x' at the empty <div> element should just insert 'x' into the <div> element and remove the padding <br> element");
   synthesizeKey("KEY_Enter");
   is(editor.innerHTML, "<div>x</div><div><br></div><div>contents</div>",
      "Typing Enter next to 'x' in the first <div> element should split the <div> element and inserts <br> element to a new <div> element");
   ensureNoPaddingBR();
   synthesizeKey("KEY_Enter");
   is(editor.innerHTML, "<div>x</div><div><br></div><div><br></div><div>contents</div>",
      "Typing Enter in the empty <div> should split the <div> element and inserts <br> element to a new <div> element");
   ensureNoPaddingBR();
--- a/editor/libeditor/tests/test_bug1397412.xhtml
+++ b/editor/libeditor/tests/test_bug1397412.xhtml
@@ -23,17 +23,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   <p/>
   <pre id="test">
   </pre>
   </body>
   <script class="testbody" type="application/javascript">
   <![CDATA[
 function runTest() {
   var initialHTML1 = "xx<br><br>";
-  var expectedHTML1 = "xx<br>t<br>";
+  var expectedHTML1 = "xx<br>t";
   var initialHTML2 = "xx<br><br>yy<br>";
   var expectedHTML2 = "xx<br>t<br>yy<br>";
   window.docShell
      .rootTreeItem
      .QueryInterface(Ci.nsIDocShell)
      .appType = Ci.nsIDocShell.APP_TYPE_EDITOR;
   var e = document.getElementById("editor");
   var doc = e.contentDocument;
@@ -42,17 +42,18 @@ function runTest() {
   var selection = doc.defaultView.getSelection();
   var body = doc.body;
 
   // Test 1.
   body.innerHTML = initialHTML1;
   selection.collapse(body, 2);
   sendString("t");
   var actualHTML = body.innerHTML;
-  is(actualHTML, expectedHTML1, "'t' should be inserted between <br>s");
+  is(actualHTML, expectedHTML1,
+      "'t' should be inserted immediately after the first <br> and the other one should be removed");
 
   // Test 2.
   body.innerHTML = initialHTML2;
   selection.collapse(body, 2);
   sendString("t");
   actualHTML = body.innerHTML;
   is(actualHTML, expectedHTML2, "'t' should be inserted between <br>s");
 
--- a/editor/libeditor/tests/test_bug551704.html
+++ b/editor/libeditor/tests/test_bug551704.html
@@ -107,17 +107,17 @@ function continueTest() {
     }
     var div = divs[current++];
     let type;
     if (div.textContent == "a") {
       type = typeBCDEF;
     } else {
       type = typeABCDEF;
     }
-    var expectedHTML = "<div>abc</div><div>def<br></div>";
+    var expectedHTML = "<div>abc</div><div>def</div>";
     var expectedText = "abc\ndef";
     testLineBreak(div, type, expectedText, expectedHTML, doNextTest);
   }
 
   doNextTest();
 }
 
 </script>
--- a/editor/libeditor/tests/test_bug780035.html
+++ b/editor/libeditor/tests/test_bug780035.html
@@ -12,12 +12,12 @@ https://bugzilla.mozilla.org/show_bug.cg
 SimpleTest.waitForExplicitFinish();
 SimpleTest.waitForFocus(function() {
   document.querySelector("div").focus();
   document.execCommand("stylewithcss", false, true);
   document.execCommand("defaultParagraphSeparator", false, "div");
   sendKey("RETURN");
   sendChar("x");
   is(document.querySelector("div").innerHTML,
-     "<div><br></div><div>x<br></div>", "No <font> tag should be generated");
+     "<div><br></div><div>x</div>", "No <font> tag should be generated");
   SimpleTest.finish();
 });
 </script>
--- a/editor/libeditor/tests/test_bug832025.html
+++ b/editor/libeditor/tests/test_bug832025.html
@@ -30,14 +30,14 @@ sel.collapse(test, test.childNodes.lengt
 
 // make it a H1
 document.execCommand("heading", false, "H1");
 // simulate a CR key
 sendKey("return");
 // insert some text
 document.execCommand("insertText", false, "abc");
 
-is(test.innerHTML, "<h1>header1</h1><div>abc<br></div>",
+is(test.innerHTML, "<h1>header1</h1><div>abc</div>",
    "A paragraph automatically created after a CR at the end of an H1 should not be bold");
 
 </script>
 </body>
 </html>
--- a/editor/libeditor/tests/test_dom_input_event_on_htmleditor.html
+++ b/editor/libeditor/tests/test_dom_input_event_on_htmleditor.html
@@ -220,43 +220,33 @@ async function runTests() {
     } else {
       todo(!inputEvent, `${aDescription}"input" event should be fired at ${action} but we replace the padding <br> element`);
     }
 
     reset();
     cancelBeforeInput = false;
     action = 'typing "B"';
     synthesizeKey("B", {shiftKey: true}, aWindow);
-    // XXX This inconsistency must be a bug.
-    if (!isEditTargetIsDescendantOfEditingHost) {
-      is(editTarget.innerHTML, "B<br>", `${aDescription}"B" should've been inserted by ${action}`);
-    } else {
-      is(editTarget.innerHTML, "B", `${aDescription}"B" should've been inserted by ${action}`);
-    }
+    is(editTarget.innerHTML, "B", `${aDescription}"B" should've been inserted by ${action}`);
     is(beforeInputEvent.cancelable, true, `${aDescription}"beforeinput" event for ${action} should be cancelable`);
     is(beforeInputEvent.inputType, "insertText", `${aDescription}inputType of "beforeinput" event for ${action} should be "insertText"`);
     is(beforeInputEvent.data, "B", `${aDescription}data of "beforeinput" event for ${action} should be "B"`);
     is(beforeInputEvent.dataTransfer, null, `${aDescription}dataTransfer of "beforeinput" event for ${action} should be null`);
     checkTargetRanges(beforeInputEvent, selectionRanges);
     ok(inputEvent, `${aDescription}"input" event should've been fired at ${action}`);
     is(inputEvent.inputType, "insertText", `${aDescription}inputType of "input" event for ${action} should be "insertText"`);
     is(inputEvent.data, "B", `${aDescription}data of "input" event for ${action} should be "B"`);
     is(inputEvent.dataTransfer, null, `${aDescription}dataTransfer of "input" event for ${action} should be null`);
     checkTargetRanges(inputEvent, []);
 
     reset();
     cancelBeforeInput = true;
     action = 'typing "Enter"';
     synthesizeKey("KEY_Enter", {}, aWindow);
-    // XXX This inconsistency must be a bug.
-    if (!isEditTargetIsDescendantOfEditingHost) {
-      is(editTarget.innerHTML, "B<br>", `${aDescription}shouldn't modify the editor by ${action} since "beforeinput" was canceled`);
-    } else {
-      is(editTarget.innerHTML, "B", `${aDescription}shouldn't modify the editor by ${action} since "beforeinput" was canceled`);
-    }
+    is(editTarget.innerHTML, "B", `${aDescription}shouldn't modify the editor by ${action} since "beforeinput" was canceled`);
     ok(beforeInputEvent, `${aDescription}"beforeinput" event should've been fired at ${action}`);
     ok(!inputEvent, `${aDescription}"input" event shouldn't been fired at ${action} since "beforeinput" was canceled`);
 
     reset();
     cancelBeforeInput = false;
     action = 'typing "Enter"';
     editTarget.innerHTML = "B";
     selection.collapse(editTarget.firstChild, 1);
@@ -278,19 +268,19 @@ async function runTests() {
     is(inputEvent.dataTransfer, null, `${aDescription}dataTransfer of "input" event for ${action} should be null`);
     checkTargetRanges(inputEvent, []);
 
     reset();
     cancelBeforeInput = false;
     action = 'typing "C" in new paragraph';
     synthesizeKey("C", {shiftKey: true}, aWindow);
     if (!isEditTargetIsDescendantOfEditingHost) {
-      is(editTarget.innerHTML, "<div>B</div><div>C<br></div>", `${aDescription}should insert "C" into the new paragraph by ${action}`);
+      is(editTarget.innerHTML, "<div>B</div><div>C</div>", `${aDescription}should insert "C" into the new paragraph by ${action}`);
     } else {
-      is(editTarget.innerHTML, "B<br>C<br>", `${aDescription}should insert "C" into the new paragraph by ${action}`);
+      is(editTarget.innerHTML, "B<br>C", `${aDescription}should insert "C" into the new paragraph by ${action}`);
     }
     is(beforeInputEvent.cancelable, true, `${aDescription}"beforeinput" event for ${action} should be cancelable`);
     is(beforeInputEvent.inputType, "insertText", `${aDescription}inputType of "beforeinput" event for ${action} should be "insertText"`);
     is(beforeInputEvent.data, "C", `${aDescription}data of "beforeinput" event for ${action} should be "C"`);
     is(beforeInputEvent.dataTransfer, null, `${aDescription}dataTransfer of "beforeinput" event for ${action} should be null`);
     checkTargetRanges(beforeInputEvent, selectionRanges);
     ok(inputEvent, `${aDescription}"input" event should've been fired at ${action}`);
     is(inputEvent.inputType, "insertText", `${aDescription}inputType of "input" event for ${action} should be "insertText"`);
--- a/editor/libeditor/tests/test_dragdrop.html
+++ b/editor/libeditor/tests/test_dragdrop.html
@@ -1608,17 +1608,17 @@ async function doTest() {
   };
   document.addEventListener("drop", onDrop);
   await synthesizePlainDragAndDrop({
     srcSelection: SpecialPowers.wrap(input).editor.selection,
     destElement: contenteditable,
   });
   is(input.value, "Somt",
      `${description}: dragged range should be removed from <input>`);
-  is(contenteditable.innerHTML, "e Tex<br>",
+  is(contenteditable.innerHTML, "e Tex",
      `${description}: dragged content should be inserted into contenteditable`);
   is(beforeinputEvents.length, 2,
      `${description}: 2 "beforeinput" events should be fired on <input> and contenteditable`);
   checkInputEvent(beforeinputEvents[0], input, "deleteByDrag", null, null, [], description);
   checkInputEvent(beforeinputEvents[1], contenteditable, "insertFromDrop", null,
                   [{type: "text/plain", data: "e Tex"}],
                   [{startContainer: contenteditable, startOffset: 0,
                     endContainer: contenteditable, endOffset: 0}],
@@ -1654,17 +1654,17 @@ async function doTest() {
   document.addEventListener("drop", onDrop);
   document.addEventListener("beforeinput", preventDefaultDeleteByDrag);
   await synthesizePlainDragAndDrop({
     srcSelection: SpecialPowers.wrap(input).editor.selection,
     destElement: contenteditable,
   });
   is(input.value, "Some Text",
      `${description}: dragged range shouldn't be removed from <input>`);
-  is(contenteditable.innerHTML, "e Tex<br>",
+  is(contenteditable.innerHTML, "e Tex",
      `${description}: dragged content should be inserted into contenteditable`);
   is(beforeinputEvents.length, 2,
      `${description}: 2 "beforeinput" events should be fired on <input> and contenteditable`);
   checkInputEvent(beforeinputEvents[0], input, "deleteByDrag", null, null, [], description);
   checkInputEvent(beforeinputEvents[1], contenteditable, "insertFromDrop", null,
                   [{type: "text/plain", data: "e Tex"}],
                   [{startContainer: contenteditable, startOffset: 0,
                     endContainer: contenteditable, endOffset: 0}],
@@ -1743,17 +1743,17 @@ async function doTest() {
   document.addEventListener("drop", onDrop);
   await synthesizePlainDragAndDrop({
     srcSelection: SpecialPowers.wrap(input).editor.selection,
     destElement: contenteditable,
     dragEvent: kModifiersToCopy,
   });
   is(input.value, "Some Text",
      `${description}: dragged range shouldn't be removed from <input>`);
-  is(contenteditable.innerHTML, "e Tex<br>",
+  is(contenteditable.innerHTML, "e Tex",
      `${description}: dragged content should be inserted into contenteditable`);
   is(beforeinputEvents.length, 1,
      `${description}: only one "beforeinput" events should be fired on contenteditable`);
   checkInputEvent(beforeinputEvents[0], contenteditable, "insertFromDrop", null,
                   [{type: "text/plain", data: "e Tex"}],
                   [{startContainer: contenteditable, startOffset: 0,
                     endContainer: contenteditable, endOffset: 0}],
                   description);
@@ -1788,17 +1788,17 @@ async function doTest() {
   await synthesizePlainDragAndDrop({
     srcSelection: SpecialPowers.wrap(textarea).editor.selection,
     destElement: contenteditable,
   });
   is(textarea.value, "Linne2",
      `${description}: dragged range should be removed from <textarea>`);
   todo_is(contenteditable.innerHTML, "<div>e1</div><div>Li</div>",
      `${description}: dragged content should be inserted into contenteditable`);
-  todo_isnot(contenteditable.innerHTML, "e1<br>Li<br>",
+  todo_isnot(contenteditable.innerHTML, "e1<br>Li",
      `${description}: dragged content should be inserted into contenteditable`);
   is(beforeinputEvents.length, 2,
      `${description}: 2 "beforeinput" events should be fired on <input> and contenteditable`);
   checkInputEvent(beforeinputEvents[0], textarea, "deleteByDrag", null, null, [], description);
   checkInputEvent(beforeinputEvents[1], contenteditable, "insertFromDrop", null,
                   [{type: "text/plain", data: `e1${kNativeLF}Li`}],
                   [{startContainer: contenteditable, startOffset: 0,
                     endContainer: contenteditable, endOffset: 0}],
@@ -1836,17 +1836,17 @@ async function doTest() {
     srcSelection: SpecialPowers.wrap(textarea).editor.selection,
     destElement: contenteditable,
     dragEvent: kModifiersToCopy,
   });
   is(textarea.value, "Line1\nLine2",
      `${description}: dragged range should be removed from <textarea>`);
   todo_is(contenteditable.innerHTML, "<div>e1</div><div>Li</div>",
      `${description}: dragged content should be inserted into contenteditable`);
-  todo_isnot(contenteditable.innerHTML, "e1<br>Li<br>",
+  todo_isnot(contenteditable.innerHTML, "e1<br>Li",
      `${description}: dragged content should be inserted into contenteditable`);
   is(beforeinputEvents.length, 1,
      `${description}: only one "beforeinput" events should be fired on contenteditable`);
   checkInputEvent(beforeinputEvents[0], contenteditable, "insertFromDrop", null,
                   [{type: "text/plain", data: `e1${kNativeLF}Li`}],
                   [{startContainer: contenteditable, startOffset: 0,
                     endContainer: contenteditable, endOffset: 0}],
                   description);
--- a/gfx/src/FilterSupport.cpp
+++ b/gfx/src/FilterSupport.cpp
@@ -794,24 +794,28 @@ static already_AddRefed<FilterNode> Filt
       }
       filter->SetAttribute(ATT_TILE_SOURCE_RECT, mSourceRegions[0]);
       filter->SetInput(IN_TILE_IN, mSources[0]);
       return filter.forget();
     }
 
     already_AddRefed<FilterNode> operator()(
         const ComponentTransferAttributes& aComponentTransfer) {
+      MOZ_ASSERT(aComponentTransfer.mTypes[0] !=
+                 SVG_FECOMPONENTTRANSFER_SAME_AS_R);
+      MOZ_ASSERT(aComponentTransfer.mTypes[3] !=
+                 SVG_FECOMPONENTTRANSFER_SAME_AS_R);
+
       RefPtr<FilterNode> filters[4];  // one for each FILTER_*_TRANSFER type
-      bool useRgb = aComponentTransfer.mTypes[kChannelG] ==
-                        SVG_FECOMPONENTTRANSFER_TYPE_UNKNOWN &&
-                    aComponentTransfer.mTypes[kChannelB] ==
-                        SVG_FECOMPONENTTRANSFER_TYPE_UNKNOWN;
-
       for (int32_t i = 0; i < 4; i++) {
-        int32_t inputIndex = useRgb && i < 3 ? 0 : i;
+        int32_t inputIndex = (aComponentTransfer.mTypes[i] ==
+                              SVG_FECOMPONENTTRANSFER_SAME_AS_R) &&
+                                     (i < 3)
+                                 ? 0
+                                 : i;
         ConvertComponentTransferFunctionToFilter(aComponentTransfer, inputIndex,
                                                  i, mDT, filters[0], filters[1],
                                                  filters[2], filters[3]);
       }
 
       // Connect all used filters nodes.
       RefPtr<FilterNode> lastFilter = mSources[0];
       for (int32_t i = 0; i < 4; i++) {
--- a/gfx/src/FilterSupport.h
+++ b/gfx/src/FilterSupport.h
@@ -51,16 +51,17 @@ const unsigned short SVG_FECOLORMATRIX_T
 
 // ComponentTransfer types
 const unsigned short SVG_FECOMPONENTTRANSFER_TYPE_UNKNOWN = 0;
 const unsigned short SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY = 1;
 const unsigned short SVG_FECOMPONENTTRANSFER_TYPE_TABLE = 2;
 const unsigned short SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE = 3;
 const unsigned short SVG_FECOMPONENTTRANSFER_TYPE_LINEAR = 4;
 const unsigned short SVG_FECOMPONENTTRANSFER_TYPE_GAMMA = 5;
+const unsigned short SVG_FECOMPONENTTRANSFER_SAME_AS_R = 6;
 
 // Blend Mode Values
 const unsigned short SVG_FEBLEND_MODE_UNKNOWN = 0;
 const unsigned short SVG_FEBLEND_MODE_NORMAL = 1;
 const unsigned short SVG_FEBLEND_MODE_MULTIPLY = 2;
 const unsigned short SVG_FEBLEND_MODE_SCREEN = 3;
 const unsigned short SVG_FEBLEND_MODE_DARKEN = 4;
 const unsigned short SVG_FEBLEND_MODE_LIGHTEN = 5;
@@ -248,18 +249,18 @@ struct ColorMatrixAttributes {
   uint32_t mType;
   ImplicitlyCopyableFloatArray mValues;
 
   bool operator==(const ColorMatrixAttributes& aOther) const {
     return mType == aOther.mType && mValues == aOther.mValues;
   }
 };
 
-// If the types for G and B are SVG_FECOMPONENTTRANSFER_TYPE_UNKNOWN,
-// assume the R values are RGB - this lets us avoid copies.
+// If the types for G and B are SVG_FECOMPONENTTRANSFER_SAME_AS_R,
+// use the R channel values - this lets us avoid copies.
 const uint32_t kChannelROrRGB = 0;
 const uint32_t kChannelG = 1;
 const uint32_t kChannelB = 2;
 const uint32_t kChannelA = 3;
 
 const uint32_t kComponentTransferSlopeIndex = 0;
 const uint32_t kComponentTransferInterceptIndex = 1;
 
--- a/gfx/tests/gtest/TestVsync.cpp
+++ b/gfx/tests/gtest/TestVsync.cpp
@@ -122,16 +122,19 @@ TEST_F(VsyncTester, CompositorGetVsyncNo
   FlushMainThreadLoop();
   ASSERT_TRUE(globalDisplay.IsVsyncEnabled());
 
   testVsyncObserver->WaitForVsyncNotification();
   ASSERT_TRUE(testVsyncObserver->DidGetVsyncNotification());
 
   vsyncDispatcher = nullptr;
   testVsyncObserver = nullptr;
+
+  globalDisplay.DisableVsync();
+  ASSERT_FALSE(globalDisplay.IsVsyncEnabled());
 }
 
 // Test that if we have vsync enabled, the parent refresh driver should get
 // notifications
 TEST_F(VsyncTester, ParentRefreshDriverGetVsyncNotifications) {
   VsyncSource::Display& globalDisplay = mVsyncSource->GetGlobalDisplay();
   globalDisplay.DisableVsync();
   ASSERT_FALSE(globalDisplay.IsVsyncEnabled());
@@ -149,16 +152,19 @@ TEST_F(VsyncTester, ParentRefreshDriverG
   vsyncDispatcher->SetParentRefreshTimer(nullptr);
 
   testVsyncObserver->ResetVsyncNotification();
   testVsyncObserver->WaitForVsyncNotification();
   ASSERT_FALSE(testVsyncObserver->DidGetVsyncNotification());
 
   vsyncDispatcher = nullptr;
   testVsyncObserver = nullptr;
+
+  globalDisplay.DisableVsync();
+  ASSERT_FALSE(globalDisplay.IsVsyncEnabled());
 }
 
 // Test that child refresh vsync observers get vsync notifications
 TEST_F(VsyncTester, ChildRefreshDriverGetVsyncNotifications) {
   VsyncSource::Display& globalDisplay = mVsyncSource->GetGlobalDisplay();
   globalDisplay.DisableVsync();
   ASSERT_FALSE(globalDisplay.IsVsyncEnabled());
 
@@ -175,28 +181,23 @@ TEST_F(VsyncTester, ChildRefreshDriverGe
 
   vsyncDispatcher->RemoveChildRefreshTimer(testVsyncObserver);
   testVsyncObserver->ResetVsyncNotification();
   testVsyncObserver->WaitForVsyncNotification();
   ASSERT_FALSE(testVsyncObserver->DidGetVsyncNotification());
 
   vsyncDispatcher = nullptr;
   testVsyncObserver = nullptr;
+
+  globalDisplay.DisableVsync();
+  ASSERT_FALSE(globalDisplay.IsVsyncEnabled());
 }
 
 // Test that we can read the vsync rate
 TEST_F(VsyncTester, VsyncSourceHasVsyncRate) {
   VsyncSource::Display& globalDisplay = mVsyncSource->GetGlobalDisplay();
   TimeDuration vsyncRate = globalDisplay.GetVsyncRate();
   ASSERT_NE(vsyncRate, TimeDuration::Forever());
   ASSERT_GT(vsyncRate.ToMilliseconds(), 0);
-}
 
-// Tests that we can disable vsync notifications
-// And has the side effect of turning off vsync notifications so they don't
-// keep running for the rest of the gtest run, which can be a problem because
-// gtests don't send a shutdown notification so it fires after the main thread
-// is gone.
-TEST_F(VsyncTester, DisableVsync) {
-  VsyncSource::Display& globalDisplay = mVsyncSource->GetGlobalDisplay();
   globalDisplay.DisableVsync();
   ASSERT_FALSE(globalDisplay.IsVsyncEnabled());
 }
--- a/gfx/thebes/gfxFT2FontList.cpp
+++ b/gfx/thebes/gfxFT2FontList.cpp
@@ -49,16 +49,21 @@
 
 #include "mozilla/EndianUtils.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/scache/StartupCache.h"
 #include <fcntl.h>
 #include <sys/mman.h>
 #include <sys/stat.h>
 
+#ifdef MOZ_WIDGET_ANDROID
+#  include "mozilla/jni/Utils.h"
+#  include <dlfcn.h>
+#endif
+
 using namespace mozilla;
 using namespace mozilla::gfx;
 
 static LazyLogModule sFontInfoLog("fontInfoLog");
 
 #undef LOG
 #define LOG(args) MOZ_LOG(sFontInfoLog, mozilla::LogLevel::Debug, args)
 #define LOG_ENABLED() MOZ_LOG_TEST(sFontInfoLog, mozilla::LogLevel::Debug)
@@ -1342,28 +1347,91 @@ void gfxFT2FontList::FindFonts() {
   MOZ_ASSERT(XRE_IsParentProcess());
 
   // Chrome process: get the cached list (if any)
   if (!mFontNameCache) {
     mFontNameCache = MakeUnique<FontNameCache>();
   }
   mFontNameCache->Init();
 
-  // ANDROID_ROOT is the root of the android system, typically /system;
-  // font files are in /$ANDROID_ROOT/fonts/
-  nsCString root;
-  char* androidRoot = PR_GetEnv("ANDROID_ROOT");
-  if (androidRoot) {
-    root = androidRoot;
-  } else {
-    root = NS_LITERAL_CSTRING("/system");
+#if defined(MOZ_WIDGET_ANDROID)
+  // Android API 29+ provides system font and font matcher API for native code.
+  typedef void* (*_ASystemFontIterator_open)();
+  typedef void* (*_ASystemFontIterator_next)(void*);
+  typedef void (*_ASystemFontIterator_close)(void*);
+  typedef const char* (*_AFont_getFontFilePath)(const void*);
+  typedef void (*_AFont_close)(void*);
+
+  static _ASystemFontIterator_open systemFontIterator_open = nullptr;
+  static _ASystemFontIterator_next systemFontIterator_next = nullptr;
+  static _ASystemFontIterator_close systemFontIterator_close = nullptr;
+  static _AFont_getFontFilePath font_getFontFilePath = nullptr;
+  static _AFont_close font_close = nullptr;
+
+  static bool firstTime = true;
+
+  if (firstTime) {
+    if (jni::GetAPIVersion() >= 29) {
+      void* handle = dlopen("libandroid.so", RTLD_LAZY | RTLD_LOCAL);
+      MOZ_ASSERT(handle);
+
+      systemFontIterator_open =
+          (_ASystemFontIterator_open)dlsym(handle, "ASystemFontIterator_open");
+      systemFontIterator_next =
+          (_ASystemFontIterator_next)dlsym(handle, "ASystemFontIterator_next");
+      systemFontIterator_close = (_ASystemFontIterator_close)dlsym(
+          handle, "ASystemFontIterator_close");
+      font_getFontFilePath =
+          (_AFont_getFontFilePath)dlsym(handle, "AFont_getFontFilePath");
+      font_close = (_AFont_close)dlsym(handle, "AFont_close");
+
+      if (NS_WARN_IF(!systemFontIterator_next) ||
+          NS_WARN_IF(!systemFontIterator_close) ||
+          NS_WARN_IF(!font_getFontFilePath) || NS_WARN_IF(!font_close)) {
+        // Since any functions aren't resolved, use old way to enumerate fonts.
+        systemFontIterator_open = nullptr;
+      }
+    }
+    firstTime = false;
   }
-  root.AppendLiteral("/fonts");
+
+  bool useSystemFontAPI = !!systemFontIterator_open;
+  if (useSystemFontAPI) {
+    void* iter = systemFontIterator_open();
+    if (iter) {
+      void* font = systemFontIterator_next(iter);
+      while (font) {
+        nsAutoCString path(font_getFontFilePath(font));
+        AppendFacesFromFontFile(path, mFontNameCache.get(), kStandard);
+        font_close(font);
+        font = systemFontIterator_next(iter);
+      }
 
-  FindFontsInDir(root, mFontNameCache.get());
+      systemFontIterator_close(iter);
+    } else {
+      useSystemFontAPI = false;
+    }
+  }
+
+  if (!useSystemFontAPI)
+#endif
+  {
+    // ANDROID_ROOT is the root of the android system, typically /system;
+    // font files are in /$ANDROID_ROOT/fonts/
+    nsCString root;
+    char* androidRoot = PR_GetEnv("ANDROID_ROOT");
+    if (androidRoot) {
+      root = androidRoot;
+    } else {
+      root = NS_LITERAL_CSTRING("/system");
+    }
+    root.AppendLiteral("/fonts");
+
+    FindFontsInDir(root, mFontNameCache.get());
+  }
 
   // Look for fonts stored in omnijar, unless we're on a low-memory
   // device where we don't want to spend the RAM to decompress them.
   // (Prefs may disable this, or force-enable it even with low memory.)
   bool lowmem;
   nsCOMPtr<nsIMemory> mem = nsMemory::GetGlobalMemoryService();
   if ((NS_SUCCEEDED(mem->IsLowMemoryPlatform(&lowmem)) && !lowmem &&
        Preferences::GetBool("gfx.bundled_fonts.enabled")) ||
--- a/image/imgLoader.cpp
+++ b/image/imgLoader.cpp
@@ -721,21 +721,20 @@ static bool ShouldLoadCachedImage(imgReq
       if (document && document->GetUpgradeInsecureRequests(false)) {
         return false;
       }
     }
 
     if (!aTriggeringPrincipal || !aTriggeringPrincipal->IsSystemPrincipal()) {
       // reset the decision for mixed content blocker check
       decision = nsIContentPolicy::REJECT_REQUEST;
-      rv = nsMixedContentBlocker::ShouldLoad(
-          insecureRedirect, aPolicyType, contentLocation, loadingPrincipal,
-          aTriggeringPrincipal, ToSupports(aLoadingDocument),
-          EmptyCString(),  // mime guess
-          &decision);
+      rv = nsMixedContentBlocker::ShouldLoad(insecureRedirect, contentLocation,
+                                             secCheckLoadInfo,
+                                             EmptyCString(),  // mime guess
+                                             &decision);
       if (NS_FAILED(rv) || !NS_CP_ACCEPTED(decision)) {
         return false;
       }
     }
   }
 
   return true;
 }
--- a/js/public/ArrayBuffer.h
+++ b/js/public/ArrayBuffer.h
@@ -30,21 +30,48 @@ extern JS_PUBLIC_API JSObject* NewArrayB
 
 /**
  * Create a new ArrayBuffer with the given |contents|, which may be null only
  * if |nbytes == 0|.  |contents| must be allocated compatible with deallocation
  * by |JS_free|.
  *
  * If and only if an ArrayBuffer is successfully created and returned,
  * ownership of |contents| is transferred to the new ArrayBuffer.
+ *
+ * Care must be taken that |nbytes| bytes of |content| remain valid for the
+ * duration of this call.  In particular, passing the length/pointer of existing
+ * typed array or ArrayBuffer data is generally unsafe: if a GC occurs during a
+ * call to this function, it could move those contents to a different location
+ * and invalidate the provided pointer.
  */
 extern JS_PUBLIC_API JSObject* NewArrayBufferWithContents(JSContext* cx,
                                                           size_t nbytes,
                                                           void* contents);
 
+/**
+ * Create a new ArrayBuffer, whose bytes are set to the values of the bytes in
+ * the provided ArrayBuffer.
+ *
+ * |maybeArrayBuffer| is asserted to be non-null.  An error is thrown if
+ * |maybeArrayBuffer| would fail the |IsArrayBufferObject| test given further
+ * below or if |maybeArrayBuffer| is detached.
+ *
+ * |maybeArrayBuffer| may store its contents in any fashion (i.e. it doesn't
+ * matter whether |maybeArrayBuffer| was allocated using |JS::NewArrayBuffer|,
+ * |JS::NewExternalArrayBuffer|, or any other ArrayBuffer-allocating function).
+ *
+ * The newly-created ArrayBuffer is effectively creatd as if by
+ * |JS::NewArrayBufferWithContents| passing in |maybeArrayBuffer|'s internal
+ * data pointer and length, in a manner safe against |maybeArrayBuffer|'s data
+ * being moved around by the GC.  In particular, the new ArrayBuffer will not
+ * behave like one created for WASM or asm.js, so it *can* be detached.
+ */
+extern JS_PUBLIC_API JSObject* CopyArrayBuffer(
+    JSContext* cx, JS::Handle<JSObject*> maybeArrayBuffer);
+
 using BufferContentsFreeFunc = void (*)(void* contents, void* userData);
 
 /**
  * Create a new ArrayBuffer with the given contents. The contents must not be
  * modified by any other code, internal or external.
  *
  * When the ArrayBuffer is ready to be disposed of, `freeFunc(contents,
  * freeUserData)` will be called to release the ArrayBuffer's reference on the
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -13,21 +13,23 @@
 #include "mozilla/CheckedInt.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/Likely.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/TaggedAnonymousMemory.h"
 
-#include <algorithm>
+#include <algorithm>  // std::max, std::min
+#include <memory>     // std::uninitialized_copy_n
 #include <string.h>
 #ifndef XP_WIN
 #  include <sys/mman.h>
 #endif
+#include <tuple>  // std::tuple
 #ifdef MOZ_VALGRIND
 #  include <valgrind/memcheck.h>
 #endif
 
 #include "jsapi.h"
 #include "jsfriendapi.h"
 #include "jsnum.h"
 #include "jstypes.h"
@@ -406,31 +408,61 @@ bool ArrayBufferObject::class_constructo
   JSObject* bufobj = createZeroed(cx, uint32_t(byteLength), proto);
   if (!bufobj) {
     return false;
   }
   args.rval().setObject(*bufobj);
   return true;
 }
 
-static uint8_t* AllocateArrayBufferContents(JSContext* cx, uint32_t nbytes) {
-  auto* p =
-      cx->pod_arena_callocCanGC<uint8_t>(js::ArrayBufferContentsArena, nbytes);
-  if (!p) {
-    ReportOutOfMemory(cx);
+using ArrayBufferContents = UniquePtr<uint8_t[], JS::FreePolicy>;
+
+static ArrayBufferContents AllocateUninitializedArrayBufferContents(
+    JSContext* cx, uint32_t nbytes) {
+  // First attempt a normal allocation.
+  uint8_t* p =
+      cx->maybe_pod_arena_malloc<uint8_t>(js::ArrayBufferContentsArena, nbytes);
+  if (MOZ_UNLIKELY(!p)) {
+    // Otherwise attempt a large allocation, calling the
+    // large-allocation-failure callback if necessary.
+    p = static_cast<uint8_t*>(cx->runtime()->onOutOfMemoryCanGC(
+        js::AllocFunction::Malloc, js::ArrayBufferContentsArena, nbytes));
+    if (!p) {
+      ReportOutOfMemory(cx);
+    }
   }
-  return p;
+
+  return ArrayBufferContents(p);
 }
 
-static uint8_t* NewCopiedBufferContents(JSContext* cx,
-                                        Handle<ArrayBufferObject*> buffer) {
-  uint8_t* dataCopy = AllocateArrayBufferContents(cx, buffer->byteLength());
+static ArrayBufferContents AllocateArrayBufferContents(JSContext* cx,
+                                                       uint32_t nbytes) {
+  // First attempt a normal allocation.
+  uint8_t* p =
+      cx->maybe_pod_arena_calloc<uint8_t>(js::ArrayBufferContentsArena, nbytes);
+  if (MOZ_UNLIKELY(!p)) {
+    // Otherwise attempt a large allocation, calling the
+    // large-allocation-failure callback if necessary.
+    p = static_cast<uint8_t*>(cx->runtime()->onOutOfMemoryCanGC(
+        js::AllocFunction::Calloc, js::ArrayBufferContentsArena, nbytes));
+    if (!p) {
+      ReportOutOfMemory(cx);
+    }
+  }
+
+  return ArrayBufferContents(p);
+}
+
+static ArrayBufferContents NewCopiedBufferContents(
+    JSContext* cx, Handle<ArrayBufferObject*> buffer) {
+  ArrayBufferContents dataCopy =
+      AllocateUninitializedArrayBufferContents(cx, buffer->byteLength());
   if (dataCopy) {
     if (auto count = buffer->byteLength()) {
-      memcpy(dataCopy, buffer->dataPointer(), count);
+      memcpy(dataCopy.get(), buffer->dataPointer(), count);
     }
   }
   return dataCopy;
 }
 
 /* static */
 void ArrayBufferObject::detach(JSContext* cx,
                                Handle<ArrayBufferObject*> buffer) {
@@ -1151,67 +1183,104 @@ ArrayBufferObject* ArrayBufferObject::cr
 
   if (contents.kind() == MAPPED || contents.kind() == MALLOCED) {
     AddCellMemory(buffer, nAllocated, MemoryUse::ArrayBufferContents);
   }
 
   return buffer;
 }
 
+template <ArrayBufferObject::FillContents FillType>
+/* static */ std::tuple<ArrayBufferObject*, uint8_t*>
+ArrayBufferObject::createBufferAndData(
+    JSContext* cx, uint32_t nbytes, AutoSetNewObjectMetadata&,
+    JS::Handle<JSObject*> proto /* = nullptr */) {
+  MOZ_ASSERT(nbytes <= ArrayBufferObject::MaxBufferByteLength,
+             "caller must validate the byte count it passes");
+
+  // Try fitting the data inline with the object by repurposing fixed-slot
+  // storage.  Add extra fixed slots if necessary to accomplish this, but don't
+  // exceed the maximum number of fixed slots!
+  size_t nslots = JSCLASS_RESERVED_SLOTS(&class_);
+  ArrayBufferContents data;
+  if (nbytes <= MaxInlineBytes) {
+    int newSlots = HowMany(nbytes, sizeof(Value));
+    MOZ_ASSERT(int(nbytes) <= newSlots * int(sizeof(Value)));
+
+    nslots += newSlots;
+  } else {
+    data = (FillType == FillContents::Uninitialized
+                ? AllocateUninitializedArrayBufferContents
+                : AllocateArrayBufferContents)(cx, nbytes);
+    if (!data) {
+      return {nullptr, nullptr};
+    }
+  }
+
+  MOZ_ASSERT(!(class_.flags & JSCLASS_HAS_PRIVATE));
+  gc::AllocKind allocKind = GetArrayBufferGCObjectKind(nslots);
+
+  ArrayBufferObject* buffer = NewObjectWithClassProto<ArrayBufferObject>(
+      cx, proto, allocKind, GenericObject);
+  if (!buffer) {
+    return {nullptr, nullptr};
+  }
+
+  MOZ_ASSERT(!gc::IsInsideNursery(buffer),
+             "ArrayBufferObject has a finalizer that must be called to not "
+             "leak in some cases, so it can't be nursery-allocated");
+
+  uint8_t* toFill;
+  if (data) {
+    toFill = data.release();
+    buffer->initialize(nbytes, BufferContents::createMalloced(toFill));
+    AddCellMemory(buffer, nbytes, MemoryUse::ArrayBufferContents);
+  } else {
+    toFill = static_cast<uint8_t*>(buffer->initializeToInlineData(nbytes));
+    if constexpr (FillType == FillContents::Zero) {
+      memset(toFill, 0, nbytes);
+    }
+  }
+
+  return {buffer, toFill};
+}
+
+/* static */ ArrayBufferObject* ArrayBufferObject::copy(
+    JSContext* cx, JS::Handle<ArrayBufferObject*> unwrappedArrayBuffer) {
+  if (unwrappedArrayBuffer->isDetached()) {
+    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+                              JSMSG_TYPED_ARRAY_DETACHED);
+    return nullptr;
+  }
+
+  uint32_t nbytes = unwrappedArrayBuffer->byteLength();
+
+  AutoSetNewObjectMetadata metadata(cx);
+  auto [buffer, toFill] = createBufferAndData<FillContents::Uninitialized>(
+      cx, nbytes, metadata, nullptr);
+  if (!buffer) {
+    return nullptr;
+  }
+
+  std::uninitialized_copy_n(unwrappedArrayBuffer->dataPointer(), nbytes,
+                            toFill);
+  return buffer;
+}
+
 ArrayBufferObject* ArrayBufferObject::createZeroed(
     JSContext* cx, uint32_t nbytes, HandleObject proto /* = nullptr */) {
   // 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2).
   if (!CheckArrayBufferTooLarge(cx, nbytes)) {
     return nullptr;
   }
 
-  // Try fitting the data inline with the object by repurposing fixed-slot
-  // storage.  Add extra fixed slots if necessary to accomplish this, but don't
-  // exceed the maximum number of fixed slots!
-  size_t nslots = JSCLASS_RESERVED_SLOTS(&class_);
-  uint8_t* data;
-  if (nbytes <= MaxInlineBytes) {
-    int newSlots = HowMany(nbytes, sizeof(Value));
-    MOZ_ASSERT(int(nbytes) <= newSlots * int(sizeof(Value)));
-
-    nslots += newSlots;
-    data = nullptr;
-  } else {
-    data = AllocateArrayBufferContents(cx, nbytes);
-    if (!data) {
-      return nullptr;
-    }
-  }
-
-  MOZ_ASSERT(!(class_.flags & JSCLASS_HAS_PRIVATE));
-  gc::AllocKind allocKind = GetArrayBufferGCObjectKind(nslots);
-
   AutoSetNewObjectMetadata metadata(cx);
-  Rooted<ArrayBufferObject*> buffer(
-      cx, NewObjectWithClassProto<ArrayBufferObject>(cx, proto, allocKind,
-                                                     GenericObject));
-  if (!buffer) {
-    if (data) {
-      js_free(data);
-    }
-    return nullptr;
-  }
-
-  MOZ_ASSERT(!gc::IsInsideNursery(buffer),
-             "ArrayBufferObject has a finalizer that must be called to not "
-             "leak in some cases, so it can't be nursery-allocated");
-
-  if (data) {
-    buffer->initialize(nbytes, BufferContents::createMalloced(data));
-    AddCellMemory(buffer, nbytes, MemoryUse::ArrayBufferContents);
-  } else {
-    void* inlineData = buffer->initializeToInlineData(nbytes);
-    memset(inlineData, 0, nbytes);
-  }
-
+  auto [buffer, toFill] =
+      createBufferAndData<FillContents::Zero>(cx, nbytes, metadata, proto);
+  Unused << toFill;
   return buffer;
 }
 
 ArrayBufferObject* ArrayBufferObject::createForTypedObject(JSContext* cx,
                                                            uint32_t nbytes) {
   ArrayBufferObject* buffer = createZeroed(cx, nbytes);
   if (buffer) {
     buffer->setHasTypedObjectViews();
@@ -1275,25 +1344,25 @@ ArrayBufferObject* ArrayBufferObject::cr
     }
 
     case INLINE_DATA:
     case NO_DATA:
     case USER_OWNED:
     case MAPPED:
     case EXTERNAL: {
       // We can't use these data types directly.  Make a copy to return.
-      uint8_t* copiedData = NewCopiedBufferContents(cx, buffer);
+      ArrayBufferContents copiedData = NewCopiedBufferContents(cx, buffer);
       if (!copiedData) {
         return nullptr;
       }
 
       // Detach |buffer|.  This immediately releases the currently owned
       // contents, freeing or unmapping data in the MAPPED and EXTERNAL cases.
       ArrayBufferObject::detach(cx, buffer);
-      return copiedData;
+      return copiedData.release();
     }
 
     case WASM:
       MOZ_ASSERT_UNREACHABLE(
           "wasm buffers aren't stealable except by a "
           "memory.grow operation that shouldn't call this "
           "function");
       return nullptr;
@@ -1313,23 +1382,23 @@ ArrayBufferObject::extractStructuredClon
   CheckStealPreconditions(buffer, cx);
 
   BufferContents contents = buffer->contents();
 
   switch (contents.kind()) {
     case INLINE_DATA:
     case NO_DATA:
     case USER_OWNED: {
-      uint8_t* copiedData = NewCopiedBufferContents(cx, buffer);
+      ArrayBufferContents copiedData = NewCopiedBufferContents(cx, buffer);
       if (!copiedData) {
         return BufferContents::createFailed();
       }
 
       ArrayBufferObject::detach(cx, buffer);
-      return BufferContents::createMalloced(copiedData);
+      return BufferContents::createMalloced(copiedData.release());
     }
 
     case MALLOCED:
     case MAPPED: {
       MOZ_ASSERT(contents);
 
       RemoveCellMemory(buffer, buffer->associatedBytes(),
                        MemoryUse::ArrayBufferContents);
@@ -1661,16 +1730,49 @@ JS_PUBLIC_API JSObject* JS::NewArrayBuff
   }
 
   using BufferContents = ArrayBufferObject::BufferContents;
 
   BufferContents contents = BufferContents::createMalloced(data);
   return ArrayBufferObject::createForContents(cx, nbytes, contents);
 }
 
+static ArrayBufferObject* UnwrapArrayBuffer(
+    JSContext* cx, JS::Handle<JSObject*> maybeArrayBuffer) {
+  JSObject* obj = CheckedUnwrapStatic(maybeArrayBuffer);
+  if (!obj) {
+    ReportAccessDenied(cx);
+    return nullptr;
+  }
+
+  if (!obj->is<ArrayBufferObject>()) {
+    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+                              JSMSG_TYPED_ARRAY_BAD_ARGS);
+    return nullptr;
+  }
+
+  return &obj->as<ArrayBufferObject>();
+}
+
+JS_PUBLIC_API JSObject* JS::CopyArrayBuffer(JSContext* cx,
+                                            Handle<JSObject*> arrayBuffer) {
+  AssertHeapIsIdle();
+  CHECK_THREAD(cx);
+
+  MOZ_ASSERT(arrayBuffer != nullptr);
+
+  Rooted<ArrayBufferObject*> unwrappedSource(
+      cx, UnwrapArrayBuffer(cx, arrayBuffer));
+  if (!unwrappedSource) {
+    return nullptr;
+  }
+
+  return ArrayBufferObject::copy(cx, unwrappedSource);
+}
+
 JS_PUBLIC_API JSObject* JS::NewExternalArrayBuffer(
     JSContext* cx, size_t nbytes, void* data,
     JS::BufferContentsFreeFunc freeFunc, void* freeUserData) {
   AssertHeapIsIdle();
   CHECK_THREAD(cx);
 
   MOZ_ASSERT(data);
   MOZ_ASSERT(nbytes > 0);
--- a/js/src/vm/ArrayBufferObject.h
+++ b/js/src/vm/ArrayBufferObject.h
@@ -4,16 +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/. */
 
 #ifndef vm_ArrayBufferObject_h
 #define vm_ArrayBufferObject_h
 
 #include "mozilla/Maybe.h"
 
+#include <tuple>  // std::tuple
+
 #include "builtin/TypedObjectConstants.h"
 #include "gc/Memory.h"
 #include "gc/ZoneAllocator.h"
 #include "js/ArrayBuffer.h"
 #include "js/GCHashTable.h"
 #include "vm/JSObject.h"
 #include "vm/Runtime.h"
 #include "vm/SharedMem.h"
@@ -227,16 +229,23 @@ class ArrayBufferObject : public ArrayBu
     // MALLOCED buffer which *can* be prepared.)
     FOR_ASMJS = 0b10'0000,
   };
 
   static_assert(JS_ARRAYBUFFER_DETACHED_FLAG == DETACHED,
                 "self-hosted code with burned-in constants must use the "
                 "correct DETACHED bit value");
 
+  enum class FillContents { Zero, Uninitialized };
+
+  template <FillContents FillType>
+  static std::tuple<ArrayBufferObject*, uint8_t*> createBufferAndData(
+      JSContext* cx, uint32_t nbytes, AutoSetNewObjectMetadata&,
+      JS::Handle<JSObject*> proto = nullptr);
+
  public:
   class BufferContents {
     uint8_t* data_;
     BufferKind kind_;
     JS::BufferContentsFreeFunc free_;
     void* freeUserData_;
 
     friend class ArrayBufferObject;
@@ -314,16 +323,19 @@ class ArrayBufferObject : public ArrayBu
 
   static bool fun_species(JSContext* cx, unsigned argc, Value* vp);
 
   static bool class_constructor(JSContext* cx, unsigned argc, Value* vp);
 
   static ArrayBufferObject* createForContents(JSContext* cx, uint32_t nbytes,
                                               BufferContents contents);
 
+  static ArrayBufferObject* copy(
+      JSContext* cx, JS::Handle<ArrayBufferObject*> unwrappedArrayBuffer);
+
   static ArrayBufferObject* createZeroed(JSContext* cx, uint32_t nbytes,
                                          HandleObject proto = nullptr);
 
   static ArrayBufferObject* createForTypedObject(JSContext* cx,
                                                  uint32_t nbytes);
 
   // Create an ArrayBufferObject that is safely finalizable and can later be
   // initialize()d to become a real, content-visible ArrayBufferObject.
--- a/js/src/vm/JSContext.h
+++ b/js/src/vm/JSContext.h
@@ -246,39 +246,16 @@ struct JS_PUBLIC_API JSContext : public 
       return nullptr;
     }
     return runtime_->onOutOfMemory(allocFunc, arena, nbytes, reallocPtr, this);
   }
 
   /* Clear the pending exception (if any) due to OOM. */
   void recoverFromOutOfMemory();
 
-  /*
-   * This variation of calloc will call the large-allocation-failure callback
-   * on OOM and retry the allocation.
-   */
-  template <typename T>
-  T* pod_arena_callocCanGC(arena_id_t arena, size_t numElems) {
-    T* p = maybe_pod_arena_calloc<T>(arena, numElems);
-    if (MOZ_LIKELY(!!p)) {
-      return p;
-    }
-    size_t bytes;
-    if (MOZ_UNLIKELY(!js::CalculateAllocSize<T>(numElems, &bytes))) {
-      reportAllocationOverflow();
-      return nullptr;
-    }
-    p = static_cast<T*>(
-        runtime()->onOutOfMemoryCanGC(js::AllocFunction::Calloc, arena, bytes));
-    if (!p) {
-      return nullptr;
-    }
-    return p;
-  }
-
   void reportAllocationOverflow() { js::ReportAllocationOverflow(this); }
 
   void noteTenuredAlloc() { allocsThisZoneSinceMinorGC_++; }
 
   uint32_t* addressOfTenuredAllocCount() {
     return &allocsThisZoneSinceMinorGC_;
   }
 
--- a/js/sub.configure
+++ b/js/sub.configure
@@ -114,17 +114,17 @@ def js_subconfigure(host, target, build_
     objdir = os.path.join(build_env.topobjdir, 'js', 'src')
 
     data_file = os.path.join(objdir, 'configure.pkl')
     previous_args = None
     if os.path.exists(data_file):
         with open(data_file, 'rb') as f:
             previous_args = pickle.load(f)
 
-    cache_file = cache_file or './config.cache'
+    cache_file = cache_file[0] if cache_file else './config.cache'
     cache_file = os.path.join(build_env.topobjdir, cache_file)
 
     try:
         os.makedirs(objdir)
     except OSError as e:
         if e.errno != errno.EEXIST:
             raise
 
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/svg-integration/css-and-svg-filter-01-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<body>
+
+  <svg class="dark" width="400" height="110">
+    <rect width="300" height="100" style="fill:rgb(0,0,0);" />
+  </svg>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/svg-integration/css-and-svg-filter-01.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <style>
+    .dark {
+      filter:  invert(1) url("#balance-color") ;
+    }
+  </style>
+</head>
+<body>
+
+  <svg class="dark" width="400" height="110">
+    <rect width="300" height="100" style="fill:rgb(255,255,255);" />
+  </svg>
+
+  <svg width="10" height="10">
+    <defs>
+      <filter id="balance-color">
+        <feColorMatrix type="matrix"
+        values="1 0 0 0 0   0 1 0 0 0   0 0 1 0 0   0 0 0 1 0">
+        </feColorMatrix>
+      </filter>
+    </defs>
+  </svg>
+
+</body>
+</html>
--- a/layout/reftests/svg/svg-integration/reftest.list
+++ b/layout/reftests/svg/svg-integration/reftest.list
@@ -13,16 +13,17 @@ fuzzy-if(true,0-140,0-70) == clipPath-ht
 fuzzy-if(true,0-140,0-70) == clipPath-html-05-extref.xhtml clipPath-html-05-ref.xhtml # Bug 776089
 fuzzy-if(Android,0-255,0-30) == clipPath-html-06.xhtml clipPath-html-06-ref.xhtml
 fuzzy-if(Android,0-255,0-30) == clipPath-html-06-extref.xhtml clipPath-html-06-ref.xhtml
 == clipPath-html-07.xhtml clipPath-html-07-ref.svg
 == clipPath-html-08.xhtml clipPath-html-07-ref.svg # reuse 07-ref.svg
 == clipPath-html-zoomed-01.xhtml clipPath-html-01-ref.svg
 == clipPath-transformed-html-01.xhtml ../pass.svg
 == clipPath-transformed-html-02.xhtml ../pass.svg
+== css-and-svg-filter-01.html css-and-svg-filter-01-ref.html  
 == conditions-outer-svg-01.xhtml ../pass.svg
 == conditions-outer-svg-02.xhtml ../pass.svg
 == dynamic-conditions-outer-svg-01.xhtml ../pass.svg
 == dynamic-conditions-outer-svg-02.xhtml ../pass.svg
 == dynamic-conditions-outer-svg-03.xhtml ../pass.svg
 == dynamic-conditions-outer-svg-04.xhtml ../pass.svg
 == filter-html-01.xhtml filter-html-01-ref.svg
 == filter-html-dynamic-01.xhtml filter-html-dynamic-01-ref.xhtml
--- a/layout/svg/nsCSSFilterInstance.cpp
+++ b/layout/svg/nsCSSFilterInstance.cpp
@@ -121,18 +121,18 @@ nsresult nsCSSFilterInstance::SetAttribu
 nsresult nsCSSFilterInstance::SetAttributesForBrightness(
     FilterPrimitiveDescription& aDescr) {
   float value = mFilter.AsBrightness();
   float intercept = 0.0f;
   ComponentTransferAttributes atts;
 
   // Set transfer functions for RGB.
   atts.mTypes[kChannelROrRGB] = (uint8_t)SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
-  atts.mTypes[kChannelG] = (uint8_t)SVG_FECOMPONENTTRANSFER_TYPE_UNKNOWN;
-  atts.mTypes[kChannelB] = (uint8_t)SVG_FECOMPONENTTRANSFER_TYPE_UNKNOWN;
+  atts.mTypes[kChannelG] = (uint8_t)SVG_FECOMPONENTTRANSFER_SAME_AS_R;
+  atts.mTypes[kChannelB] = (uint8_t)SVG_FECOMPONENTTRANSFER_SAME_AS_R;
   float slopeIntercept[2];
   slopeIntercept[kComponentTransferSlopeIndex] = value;
   slopeIntercept[kComponentTransferInterceptIndex] = intercept;
   atts.mValues[kChannelROrRGB].AppendElements(slopeIntercept, 2);
 
   atts.mTypes[kChannelA] = (uint8_t)SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY;
 
   aDescr.Attributes() = AsVariant(std::move(atts));
@@ -142,18 +142,18 @@ nsresult nsCSSFilterInstance::SetAttribu
 nsresult nsCSSFilterInstance::SetAttributesForContrast(
     FilterPrimitiveDescription& aDescr) {
   float value = mFilter.AsContrast();
   float intercept = -(0.5 * value) + 0.5;
   ComponentTransferAttributes atts;
 
   // Set transfer functions for RGB.
   atts.mTypes[kChannelROrRGB] = (uint8_t)SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
-  atts.mTypes[kChannelG] = (uint8_t)SVG_FECOMPONENTTRANSFER_TYPE_UNKNOWN;
-  atts.mTypes[kChannelB] = (uint8_t)SVG_FECOMPONENTTRANSFER_TYPE_UNKNOWN;
+  atts.mTypes[kChannelG] = (uint8_t)SVG_FECOMPONENTTRANSFER_SAME_AS_R;
+  atts.mTypes[kChannelB] = (uint8_t)SVG_FECOMPONENTTRANSFER_SAME_AS_R;
   float slopeIntercept[2];
   slopeIntercept[kComponentTransferSlopeIndex] = value;
   slopeIntercept[kComponentTransferInterceptIndex] = intercept;
   atts.mValues[kChannelROrRGB].AppendElements(slopeIntercept, 2);
 
   atts.mTypes[kChannelA] = (uint8_t)SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY;
 
   aDescr.Attributes() = AsVariant(std::move(atts));
@@ -218,18 +218,18 @@ nsresult nsCSSFilterInstance::SetAttribu
 
   // Set transfer functions for RGB.
   float invertTableValues[2];
   invertTableValues[0] = value;
   invertTableValues[1] = 1 - value;
 
   // Set transfer functions for RGB.
   atts.mTypes[kChannelROrRGB] = (uint8_t)SVG_FECOMPONENTTRANSFER_TYPE_TABLE;
-  atts.mTypes[kChannelG] = (uint8_t)SVG_FECOMPONENTTRANSFER_TYPE_UNKNOWN;
-  atts.mTypes[kChannelB] = (uint8_t)SVG_FECOMPONENTTRANSFER_TYPE_UNKNOWN;
+  atts.mTypes[kChannelG] = (uint8_t)SVG_FECOMPONENTTRANSFER_SAME_AS_R;
+  atts.mTypes[kChannelB] = (uint8_t)SVG_FECOMPONENTTRANSFER_SAME_AS_R;
   atts.mValues[kChannelROrRGB].AppendElements(invertTableValues, 2);
 
   atts.mTypes[kChannelA] = (uint8_t)SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY;
 
   aDescr.Attributes() = AsVariant(std::move(atts));
   return NS_OK;
 }
 
--- a/layout/svg/nsFilterInstance.cpp
+++ b/layout/svg/nsFilterInstance.cpp
@@ -93,16 +93,17 @@ void nsFilterInstance::PaintFilteredFram
                             aPaintCallback, scaleMatrixInDevUnits, aDirtyArea,
                             nullptr, nullptr, nullptr);
   if (instance.IsInitialized()) {
     instance.Render(aCtx, aImgParams, aOpacity);
   }
 }
 
 static mozilla::wr::ComponentTransferFuncType FuncTypeToWr(uint8_t aFuncType) {
+  MOZ_ASSERT(aFuncType != SVG_FECOMPONENTTRANSFER_SAME_AS_R);
   switch (aFuncType) {
     case SVG_FECOMPONENTTRANSFER_TYPE_TABLE:
       return mozilla::wr::ComponentTransferFuncType::Table;
     case SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE:
       return mozilla::wr::ComponentTransferFuncType::Discrete;
     case SVG_FECOMPONENTTRANSFER_TYPE_LINEAR:
       return mozilla::wr::ComponentTransferFuncType::Linear;
     case SVG_FECOMPONENTTRANSFER_TYPE_GAMMA:
@@ -283,25 +284,29 @@ bool nsFilterInstance::BuildWebRenderFil
           &aWrFilters.values[aWrFilters.values.Length() - 1];
       values->SetCapacity(numValues);
 
       filterData.funcR_type = FuncTypeToWr(attributes.mTypes[0]);
       size_t R_startindex = values->Length();
       values->AppendElements(attributes.mValues[0]);
       filterData.R_values_count = attributes.mValues[0].Length();
 
-      filterData.funcG_type = FuncTypeToWr(attributes.mTypes[1]);
+      size_t indexToUse =
+          attributes.mTypes[1] == SVG_FECOMPONENTTRANSFER_SAME_AS_R ? 0 : 1;
+      filterData.funcG_type = FuncTypeToWr(attributes.mTypes[indexToUse]);
       size_t G_startindex = values->Length();
-      values->AppendElements(attributes.mValues[1]);
-      filterData.G_values_count = attributes.mValues[1].Length();
+      values->AppendElements(attributes.mValues[indexToUse]);
+      filterData.G_values_count = attributes.mValues[indexToUse].Length();
 
-      filterData.funcB_type = FuncTypeToWr(attributes.mTypes[2]);
+      indexToUse =
+          attributes.mTypes[2] == SVG_FECOMPONENTTRANSFER_SAME_AS_R ? 0 : 2;
+      filterData.funcB_type = FuncTypeToWr(attributes.mTypes[indexToUse]);
       size_t B_startindex = values->Length();
-      values->AppendElements(attributes.mValues[2]);
-      filterData.B_values_count = attributes.mValues[2].Length();
+      values->AppendElements(attributes.mValues[indexToUse]);
+      filterData.B_values_count = attributes.mValues[indexToUse].Length();
 
       filterData.funcA_type = FuncTypeToWr(attributes.mTypes[3]);
       size_t A_startindex = values->Length();
       values->AppendElements(attributes.mValues[3]);
       filterData.A_values_count = attributes.mValues[3].Length();
 
       filterData.R_values =
           filterData.R_values_count > 0 ? &((*values)[R_startindex]) : nullptr;
--- a/mozglue/baseprofiler/moz.build
+++ b/mozglue/baseprofiler/moz.build
@@ -88,16 +88,17 @@ EXPORTS.mozilla += [
     'public/BlocksRingBuffer.h',
     'public/leb128iterator.h',
     'public/ModuloBuffer.h',
     'public/PowerOfTwo.h',
     'public/ProfileBufferChunk.h',
     'public/ProfileBufferChunkManager.h',
     'public/ProfileBufferChunkManagerSingle.h',
     'public/ProfileBufferChunkManagerWithLocalLimit.h',
+    'public/ProfileBufferControlledChunkManager.h',
     'public/ProfileBufferEntrySerialization.h',
     'public/ProfileBufferIndex.h',
     'public/ProfileChunkedBuffer.h',
 ]
 
 if CONFIG['MOZ_VTUNE']:
     DEFINES['MOZ_VTUNE_INSTRUMENTATION'] = True
     UNIFIED_SOURCES += [
--- a/mozglue/baseprofiler/public/ProfileBufferChunkManagerWithLocalLimit.h
+++ b/mozglue/baseprofiler/public/ProfileBufferChunkManagerWithLocalLimit.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef ProfileBufferChunkManagerWithLocalLimit_h
 #define ProfileBufferChunkManagerWithLocalLimit_h
 
 #include "BaseProfiler.h"
 #include "mozilla/BaseProfilerDetail.h"
 #include "mozilla/ProfileBufferChunkManager.h"
+#include "mozilla/ProfileBufferControlledChunkManager.h"
 
 namespace mozilla {
 
 // Manages the Chunks for this process in a thread-safe manner, with a maximum
 // size per process.
 //
 // "Unreleased" chunks are not owned here, only "released" chunks can be
 // destroyed or recycled when reaching the memory limit, so it is theoretically
@@ -22,28 +23,36 @@ namespace mozilla {
 // - The user of this class doesn't release their chunks, AND/OR
 // - The limit is too small (e.g., smaller than 2 or 3 chunks, which should be
 //   the usual number of unreleased chunks in flight).
 // In this case, it just means that we will use more memory than allowed,
 // potentially risking OOMs. Hopefully this shouldn't happen in real code,
 // assuming that the user is doing the right thing and releasing chunks ASAP,
 // and that the memory limit is reasonably large.
 class ProfileBufferChunkManagerWithLocalLimit final
-    : public ProfileBufferChunkManager {
+    : public ProfileBufferChunkManager,
+      public ProfileBufferControlledChunkManager {
  public:
   using Length = ProfileBufferChunk::Length;
 
   // MaxTotalBytes: Maximum number of bytes allocated in all local Chunks.
   // ChunkMinBufferBytes: Minimum number of user-available bytes in each Chunk.
   // Note that Chunks use a bit more memory for their header.
   explicit ProfileBufferChunkManagerWithLocalLimit(size_t aMaxTotalBytes,
                                                    Length aChunkMinBufferBytes)
       : mMaxTotalBytes(aMaxTotalBytes),
         mChunkMinBufferBytes(aChunkMinBufferBytes) {}
 
+  ~ProfileBufferChunkManagerWithLocalLimit() {
+    if (mUpdateCallback) {
+      // Signal the end of this callback.
+      std::move(mUpdateCallback)(Update(nullptr));
+    }
+  }
+
   [[nodiscard]] size_t MaxTotalSize() const final {
     // `mMaxTotalBytes` is `const` so there is no need to lock the mutex.
     return mMaxTotalBytes;
   }
 
   [[nodiscard]] UniquePtr<ProfileBufferChunk> GetChunk() final {
     AUTO_PROFILER_STATS(Local_GetChunk);
     baseprofiler::detail::BaseProfilerAutoLock lock(mMutex);
@@ -89,169 +98,265 @@ class ProfileBufferChunkManagerWithLocal
     // chunks, new/recycled chunks look the same so their order doesn't matter.
     MOZ_ASSERT(!!chunkReceiver, "chunkReceiver shouldn't be empty here");
     std::move(chunkReceiver)(std::move(chunk));
   }
 
   void ReleaseChunks(UniquePtr<ProfileBufferChunk> aChunks) final {
     baseprofiler::detail::BaseProfilerAutoLock lock(mMutex);
     MOZ_ASSERT(mUser, "Not registered yet");
+    // Keep a pointer to the first newly-released chunk, so we can use it to
+    // prepare an update (after `aChunks` is moved-from).
+    const ProfileBufferChunk* const newlyReleasedChunks = aChunks.get();
     // Compute the size of all provided chunks.
     size_t bytes = 0;
-    for (const ProfileBufferChunk* chunk = aChunks.get(); chunk;
+    for (const ProfileBufferChunk* chunk = newlyReleasedChunks; chunk;
          chunk = chunk->GetNext()) {
-      bytes += chunk->ChunkBytes();
+      bytes += chunk->BufferBytes();
+      MOZ_ASSERT(!chunk->ChunkHeader().mDoneTimeStamp.IsNull(),
+                 "All released chunks should have a 'Done' timestamp");
+      MOZ_ASSERT(
+          !chunk->GetNext() || (chunk->ChunkHeader().mDoneTimeStamp <
+                                chunk->GetNext()->ChunkHeader().mDoneTimeStamp),
+          "Released chunk groups must have increasing timestamps");
     }
     // Transfer the chunks size from the unreleased bucket to the released one.
-    mUnreleasedBytes -= bytes;
+    mUnreleasedBufferBytes -= bytes;
     if (!mReleasedChunks) {
       // No other released chunks at the moment, we're starting the list.
-      MOZ_ASSERT(mReleasedBytes == 0);
-      mReleasedBytes = bytes;
+      MOZ_ASSERT(mReleasedBufferBytes == 0);
+      mReleasedBufferBytes = bytes;
       mReleasedChunks = std::move(aChunks);
     } else {
       // Add to the end of the released chunks list (oldest first, most recent
       // last.)
-      mReleasedBytes += bytes;
+      MOZ_ASSERT(mReleasedChunks->Last()->ChunkHeader().mDoneTimeStamp <
+                     aChunks->ChunkHeader().mDoneTimeStamp,
+                 "Chunks must be released in increasing timestamps");
+      mReleasedBufferBytes += bytes;
       mReleasedChunks->SetLast(std::move(aChunks));
     }
+
+    if (mUpdateCallback) {
+      mUpdateCallback(Update(mUnreleasedBufferBytes, mReleasedBufferBytes,
+                             mReleasedChunks.get(), newlyReleasedChunks));
+    }
   }
 
   void SetChunkDestroyedCallback(
       std::function<void(const ProfileBufferChunk&)>&& aChunkDestroyedCallback)
       final {
     baseprofiler::detail::BaseProfilerAutoLock lock(mMutex);
     MOZ_ASSERT(mUser, "Not registered yet");
     mChunkDestroyedCallback = std::move(aChunkDestroyedCallback);
   }
 
   [[nodiscard]] UniquePtr<ProfileBufferChunk> GetExtantReleasedChunks() final {
     baseprofiler::detail::BaseProfilerAutoLock lock(mMutex);
     MOZ_ASSERT(mUser, "Not registered yet");
-    mReleasedBytes = 0;
+    mReleasedBufferBytes = 0;
+    if (mUpdateCallback) {
+      mUpdateCallback(Update(mUnreleasedBufferBytes, 0, nullptr, nullptr));
+    }
     return std::move(mReleasedChunks);
   }
 
   void ForgetUnreleasedChunks() final {
     baseprofiler::detail::BaseProfilerAutoLock lock(mMutex);
     MOZ_ASSERT(mUser, "Not registered yet");
-    mUnreleasedBytes = 0;
+    mUnreleasedBufferBytes = 0;
+    if (mUpdateCallback) {
+      mUpdateCallback(
+          Update(0, mReleasedBufferBytes, mReleasedChunks.get(), nullptr));
+    }
   }
 
   [[nodiscard]] size_t SizeOfExcludingThis(
       MallocSizeOf aMallocSizeOf) const final {
     baseprofiler::detail::BaseProfilerAutoLock lock(mMutex);
     return SizeOfExcludingThis(aMallocSizeOf, lock);
   }
 
   [[nodiscard]] size_t SizeOfIncludingThis(
       MallocSizeOf aMallocSizeOf) const final {
     baseprofiler::detail::BaseProfilerAutoLock lock(mMutex);
     MOZ_ASSERT(mUser, "Not registered yet");
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf, lock);
   }
 
+  void SetUpdateCallback(UpdateCallback&& aUpdateCallback) final {
+    baseprofiler::detail::BaseProfilerAutoLock lock(mMutex);
+    if (mUpdateCallback) {
+      // Signal the end of the previous callback.
+      std::move(mUpdateCallback)(Update(nullptr));
+    }
+    mUpdateCallback = std::move(aUpdateCallback);
+    if (mUpdateCallback) {
+      mUpdateCallback(Update(mUnreleasedBufferBytes, mReleasedBufferBytes,
+                             mReleasedChunks.get(), nullptr));
+    }
+  }
+
+  void DestroyChunksAtOrBefore(TimeStamp aDoneTimeStamp) final {
+    MOZ_ASSERT(!aDoneTimeStamp.IsNull());
+    baseprofiler::detail::BaseProfilerAutoLock lock(mMutex);
+    for (;;) {
+      if (!mReleasedChunks) {
+        // We don't own any released chunks (anymore), we're done.
+        break;
+      }
+      if (mReleasedChunks->ChunkHeader().mDoneTimeStamp > aDoneTimeStamp) {
+        // The current chunk is strictly after the given timestamp, we're done.
+        break;
+      }
+      // We've found a chunk at or before the timestamp, discard it.
+      DiscardOldestReleasedChunk(lock);
+    }
+  }
+
  protected:
   const ProfileBufferChunk* PeekExtantReleasedChunksAndLock() final {
     mMutex.Lock();
     MOZ_ASSERT(mUser, "Not registered yet");
     return mReleasedChunks.get();
   }
   void UnlockAfterPeekExtantReleasedChunks() final { mMutex.Unlock(); }
 
  private:
+  void MaybeRecycleChunk(
+      UniquePtr<ProfileBufferChunk>&& chunk,
+      const baseprofiler::detail::BaseProfilerAutoLock& aLock) {
+    // Try to recycle big-enough chunks. (All chunks should have the same size,
+    // but it's a cheap test and may allow future adjustments based on actual
+    // data rate.)
+    if (chunk->BufferBytes() >= mChunkMinBufferBytes) {
+      // We keep up to two recycled chunks at any time.
+      if (!mRecycledChunks) {
+        mRecycledChunks = std::move(chunk);
+      } else if (!mRecycledChunks->GetNext()) {
+        mRecycledChunks->InsertNext(std::move(chunk));
+      }
+    }
+  }
+
+  UniquePtr<ProfileBufferChunk> TakeRecycledChunk(
+      const baseprofiler::detail::BaseProfilerAutoLock& aLock) {
+    UniquePtr<ProfileBufferChunk> recycled;
+    if (mRecycledChunks) {
+      recycled = std::exchange(mRecycledChunks, mRecycledChunks->ReleaseNext());
+      recycled->MarkRecycled();
+    }
+    return recycled;
+  }
+
+  void DiscardOldestReleasedChunk(
+      const baseprofiler::detail::BaseProfilerAutoLock& aLock) {
+    MOZ_ASSERT(!!mReleasedChunks);
+    UniquePtr<ProfileBufferChunk> oldest =
+        std::exchange(mReleasedChunks, mReleasedChunks->ReleaseNext());
+    mReleasedBufferBytes -= oldest->BufferBytes();
+    if (mChunkDestroyedCallback) {
+      // Inform the user that we're going to destroy this chunk.
+      mChunkDestroyedCallback(*oldest);
+    }
+    MaybeRecycleChunk(std::move(oldest), aLock);
+  }
+
   [[nodiscard]] UniquePtr<ProfileBufferChunk> GetChunk(
-      const baseprofiler::detail::BaseProfilerAutoLock&) {
+      const baseprofiler::detail::BaseProfilerAutoLock& aLock) {
     MOZ_ASSERT(mUser, "Not registered yet");
-    UniquePtr<ProfileBufferChunk> chunk;
     // After this function, the total memory consumption will be the sum of:
     // - Bytes from released (i.e., full) chunks,
     // - Bytes from unreleased (still in use) chunks,
     // - Bytes from the chunk we want to create/recycle. (Note that we don't
     //   count the extra bytes of chunk header, and of extra allocation ability,
     //   for the new chunk, as it's assumed to be negligible compared to the
     //   total memory limit.)
     // If this total is higher than the local limit, we'll want to destroy
-    // the oldest released chunks until we're under the limit; if any, we'll
+    // the oldest released chunks until we're under the limit; if any, we may
     // recycle one of them to avoid a deallocation followed by an allocation.
-    while (mReleasedBytes + mUnreleasedBytes + mChunkMinBufferBytes >=
+    while (mReleasedBufferBytes + mUnreleasedBufferBytes +
+                   mChunkMinBufferBytes >=
                mMaxTotalBytes &&
-           mReleasedBytes != 0) {
-      MOZ_ASSERT(!!mReleasedChunks);
-      // We have reached the local limit, extract the oldest released chunk,
-      // which is the first one at `mReleasedChunks`.
-      UniquePtr<ProfileBufferChunk> oldest =
-          std::exchange(mReleasedChunks, mReleasedChunks->ReleaseNext());
-      // Subtract its size from the "released" number of bytes.
-      mReleasedBytes -= oldest->ChunkBytes();
-      if (mChunkDestroyedCallback) {
-        // Inform the user that we're going to destroy or recycle this chunk.
-        mChunkDestroyedCallback(*oldest);
-      }
-      // Try to recycle at least one big-enough chunk. (All chunks should have
-      // the same size, but it's a cheap test and may allow future adjustments
-      // based on actual data rate.)
-      if (!chunk && oldest->BufferBytes() >= mChunkMinBufferBytes) {
-        // Recycle this chunk.
-        chunk = std::move(oldest);
-        chunk->MarkRecycled();
-      }
+           !!mReleasedChunks) {
+      // We have reached the local limit, discard the oldest released chunk.
+      DiscardOldestReleasedChunk(aLock);
     }
 
+    // Extract the recycled chunk, if any.
+    UniquePtr<ProfileBufferChunk> chunk = TakeRecycledChunk(aLock);
+
     if (!chunk) {
-      // No recycling -> Create a chunk now. (This could still fail.)
+      // No recycled chunk -> Create a chunk now. (This could still fail.)
       chunk = ProfileBufferChunk::Create(mChunkMinBufferBytes);
     }
 
     if (chunk) {
       // We do have a chunk (recycled or new), record its size as "unreleased".
-      mUnreleasedBytes += chunk->ChunkBytes();
+      mUnreleasedBufferBytes += chunk->BufferBytes();
+
+      if (mUpdateCallback) {
+        mUpdateCallback(Update(mUnreleasedBufferBytes, mReleasedBufferBytes,
+                               mReleasedChunks.get(), nullptr));
+      }
     }
 
     return chunk;
   }
 
   [[nodiscard]] size_t SizeOfExcludingThis(
       MallocSizeOf aMallocSizeOf,
       const baseprofiler::detail::BaseProfilerAutoLock&) const {
     MOZ_ASSERT(mUser, "Not registered yet");
+    size_t size = 0;
+    if (mReleasedChunks) {
+      size += mReleasedChunks->SizeOfIncludingThis(aMallocSizeOf);
+    }
+    if (mRecycledChunks) {
+      size += mRecycledChunks->SizeOfIncludingThis(aMallocSizeOf);
+    }
     // Note: Missing size of std::function external resources (if any).
-    return mReleasedChunks ? mReleasedChunks->SizeOfIncludingThis(aMallocSizeOf)
-                           : 0;
+    return size;
   }
 
   // Maxumum number of bytes that should be used by all unreleased and released
   // chunks. Note that only released chunks can be destroyed here, so it is the
   // responsibility of the user to properly release their chunks when possible.
   const size_t mMaxTotalBytes;
 
   // Minimum number of bytes that new chunks should be able to store.
   // Used when calling `ProfileBufferChunk::Create()`.
   const Length mChunkMinBufferBytes;
 
   // Mutex guarding the following members.
   mutable baseprofiler::detail::BaseProfilerMutex mMutex;
 
   // Number of bytes currently held in chunks that have been given away (through
   // `GetChunk` or `RequestChunk`) and not released yet.
-  size_t mUnreleasedBytes = 0;
+  size_t mUnreleasedBufferBytes = 0;
 
   // Number of bytes currently held in chunks that have been released and stored
   // in `mReleasedChunks` below.
-  size_t mReleasedBytes = 0;
+  size_t mReleasedBufferBytes = 0;
 
   // List of all released chunks. The oldest one should be at the start of the
   // list, and may be destroyed or recycled when the memory limit is reached.
   UniquePtr<ProfileBufferChunk> mReleasedChunks;
 
+  // This may hold chunks that were released then slated for destruction, they
+  // will be reused next time an allocation would have been needed.
+  UniquePtr<ProfileBufferChunk> mRecycledChunks;
+
   // Optional callback used to notify the user when a chunk is about to be
   // destroyed or recycled. (The data content is always destroyed, but the chunk
   // container may be reused.)
   std::function<void(const ProfileBufferChunk&)> mChunkDestroyedCallback;
 
   // Callback set from `RequestChunk()`, until it is serviced in
   // `FulfillChunkRequests()`. There can only be one request in flight.
   std::function<void(UniquePtr<ProfileBufferChunk>)> mChunkReceiver;
+
+  UpdateCallback mUpdateCallback;
 };
 
 }  // namespace mozilla
 
 #endif  // ProfileBufferChunkManagerWithLocalLimit_h
new file mode 100644
--- /dev/null
+++ b/mozglue/baseprofiler/public/ProfileBufferControlledChunkManager.h
@@ -0,0 +1,203 @@
+/* -*- Mode: C++; tab-width: 2; 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 ProfileBufferControlledChunkManager_h
+#define ProfileBufferControlledChunkManager_h
+
+#include "mozilla/ProfileBufferChunk.h"
+
+#include <functional>
+#include <vector>
+
+namespace mozilla {
+
+// A "Controlled" chunk manager will provide updates about chunks that it
+// creates, releases, and destroys; and it can destroy released chunks as
+// requested.
+class ProfileBufferControlledChunkManager {
+ public:
+  using Length = ProfileBufferChunk::Length;
+
+  virtual ~ProfileBufferControlledChunkManager() = default;
+
+  // Minimum amount of chunk metadata to be transferred between processes.
+  struct ChunkMetadata {
+    // Timestamp when chunk was marked "done", which is used to:
+    // - determine its age, so the oldest one will be destroyed first,
+    // - uniquely identify this chunk in this process. (The parent process is
+    //   responsible for associating this timestamp to its process id.)
+    TimeStamp mDoneTimeStamp;
+    // Size of this chunk's buffer.
+    Length mBufferBytes;
+
+    ChunkMetadata(TimeStamp aDoneTimeStamp, Length aBufferBytes)
+        : mDoneTimeStamp(aDoneTimeStamp), mBufferBytes(aBufferBytes) {}
+  };
+
+  // Class collecting all information necessary to describe updates that
+  // happened in a chunk manager.
+  // An update can be folded into a previous update.
+  class Update {
+   public:
+    // Construct a "not-an-Update" object, which should only be used after a
+    // real update is folded into it.
+    Update() = default;
+
+    // Construct a "final" Update, which marks the end of all updates from a
+    // chunk manager.
+    explicit Update(decltype(nullptr)) : mUnreleasedBytes(FINAL) {}
+
+    // Construct an Update from the given data and released chunks.
+    // The chunk pointers may be null, and it doesn't matter if
+    // `aNewlyReleasedChunks` is already linked to `aExistingReleasedChunks` or
+    // not.
+    Update(size_t aUnreleasedBytes, size_t aReleasedBytes,
+           const ProfileBufferChunk* aExistingReleasedChunks,
+           const ProfileBufferChunk* aNewlyReleasedChunks)
+        : mUnreleasedBytes(aUnreleasedBytes),
+          mReleasedBytes(aReleasedBytes),
+          mOldestDoneTimeStamp(
+              aExistingReleasedChunks
+                  ? aExistingReleasedChunks->ChunkHeader().mDoneTimeStamp
+                  : TimeStamp{}) {
+      MOZ_RELEASE_ASSERT(
+          !IsNotUpdate(),
+          "Empty update should only be constructed with default constructor");
+      MOZ_RELEASE_ASSERT(
+          !IsFinal(),
+          "Final update should only be constructed with nullptr constructor");
+      for (const ProfileBufferChunk* chunk = aNewlyReleasedChunks; chunk;
+           chunk = chunk->GetNext()) {
+        mNewlyReleasedChunks.emplace_back(ChunkMetadata{
+            chunk->ChunkHeader().mDoneTimeStamp, chunk->BufferBytes()});
+      }
+    }
+
+    // Construct an Update from raw data.
+    // This may be used to re-construct an Update that was previously
+    // serialized.
+    Update(size_t aUnreleasedBytes, size_t aReleasedBytes,
+           TimeStamp aOldestDoneTimeStamp,
+           std::vector<ChunkMetadata>&& aNewlyReleasedChunks)
+        : mUnreleasedBytes(aUnreleasedBytes),
+          mReleasedBytes(aReleasedBytes),
+          mOldestDoneTimeStamp(aOldestDoneTimeStamp),
+          mNewlyReleasedChunks(std::move(aNewlyReleasedChunks)) {}
+
+    // Clear the Update completely and return it to a "not-an-Update" state.
+    void Clear() {
+      mUnreleasedBytes = NO_UPDATE;
+      mReleasedBytes = 0;
+      mOldestDoneTimeStamp = TimeStamp{};
+      mNewlyReleasedChunks.clear();
+    }
+
+    bool IsNotUpdate() const { return mUnreleasedBytes == NO_UPDATE; }
+
+    bool IsFinal() const { return mUnreleasedBytes == FINAL; }
+
+    size_t UnreleasedBytes() const {
+      MOZ_RELEASE_ASSERT(!IsNotUpdate(),
+                         "Cannot access UnreleasedBytes from empty update");
+      MOZ_RELEASE_ASSERT(!IsFinal(),
+                         "Cannot access UnreleasedBytes from final update");
+      return mUnreleasedBytes;
+    }
+
+    size_t ReleasedBytes() const {
+      MOZ_RELEASE_ASSERT(!IsNotUpdate(),
+                         "Cannot access ReleasedBytes from empty update");
+      MOZ_RELEASE_ASSERT(!IsFinal(),
+                         "Cannot access ReleasedBytes from final update");
+      return mReleasedBytes;
+    }
+
+    TimeStamp OldestDoneTimeStamp() const {
+      MOZ_RELEASE_ASSERT(!IsNotUpdate(),
+                         "Cannot access OldestDoneTimeStamp from empty update");
+      MOZ_RELEASE_ASSERT(!IsFinal(),
+                         "Cannot access OldestDoneTimeStamp from final update");
+      return mOldestDoneTimeStamp;
+    }
+
+    const std::vector<ChunkMetadata>& NewlyReleasedChunksRef() const {
+      MOZ_RELEASE_ASSERT(
+          !IsNotUpdate(),
+          "Cannot access NewlyReleasedChunksRef from empty update");
+      MOZ_RELEASE_ASSERT(
+          !IsFinal(), "Cannot access NewlyReleasedChunksRef from final update");
+      return mNewlyReleasedChunks;
+    }
+
+    // Fold a later update into this one.
+    void Fold(Update&& aNewUpdate) {
+      MOZ_ASSERT(
+          !IsFinal() || aNewUpdate.IsFinal(),
+          "There shouldn't be another non-final update after the final update");
+
+      if (IsNotUpdate() || aNewUpdate.IsFinal()) {
+        // We were empty, or the new update is the final update, we just switch
+        // to that new update.
+        *this = std::move(aNewUpdate);
+        return;
+      }
+
+      mUnreleasedBytes = aNewUpdate.mUnreleasedBytes;
+      mReleasedBytes = aNewUpdate.mReleasedBytes;
+      if (!aNewUpdate.mOldestDoneTimeStamp.IsNull()) {
+        MOZ_ASSERT(mOldestDoneTimeStamp.IsNull() ||
+                   mOldestDoneTimeStamp <= aNewUpdate.mOldestDoneTimeStamp);
+        mOldestDoneTimeStamp = aNewUpdate.mOldestDoneTimeStamp;
+        auto it = mNewlyReleasedChunks.begin();
+        while (it != mNewlyReleasedChunks.end() &&
+               it->mDoneTimeStamp < mOldestDoneTimeStamp) {
+          it = mNewlyReleasedChunks.erase(it);
+        }
+      }
+      if (!aNewUpdate.mNewlyReleasedChunks.empty()) {
+        mNewlyReleasedChunks.reserve(mNewlyReleasedChunks.size() +
+                                     aNewUpdate.mNewlyReleasedChunks.size());
+        mNewlyReleasedChunks.insert(mNewlyReleasedChunks.end(),
+                                    aNewUpdate.mNewlyReleasedChunks.begin(),
+                                    aNewUpdate.mNewlyReleasedChunks.end());
+      }
+    }
+
+   private:
+    static const size_t NO_UPDATE = size_t(-1);
+    static const size_t FINAL = size_t(-2);
+
+    size_t mUnreleasedBytes = NO_UPDATE;
+    size_t mReleasedBytes = 0;
+    TimeStamp mOldestDoneTimeStamp;
+    std::vector<ChunkMetadata> mNewlyReleasedChunks;
+  };
+
+  using UpdateCallback = std::function<void(Update&&)>;
+
+  // This *may* be set (or reset) by an object that needs to know about all
+  // chunk updates that happen in this manager. The main use will be to
+  // coordinate the global memory usage of Firefox.
+  // If a non-empty callback is given, it will be immediately invoked with the
+  // current state.
+  // When the callback is about to be destroyed (by overwriting it here, or in
+  // the class destructor), it will be invoked one last time with an empty
+  // update.
+  // Note that the callback (even the first current-state callback) will be
+  // invoked from inside a locked scope, so it should *not* call other functions
+  // of the chunk manager. A side benefit of this locking is that it guarantees
+  // that no two invocations can overlap.
+  virtual void SetUpdateCallback(UpdateCallback&& aUpdateCallback) = 0;
+
+  // This is a request to destroy all chunks before the given timestamp.
+  // This timestamp should be one that was given in a previous UpdateCallback
+  // call. Obviously, only released chunks can be destroyed.
+  virtual void DestroyChunksAtOrBefore(TimeStamp aDoneTimeStamp) = 0;
+};
+
+}  // namespace mozilla
+
+#endif  // ProfileBufferControlledChunkManager_h
--- a/mozglue/tests/TestBaseProfiler.cpp
+++ b/mozglue/tests/TestBaseProfiler.cpp
@@ -8,16 +8,17 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/BlocksRingBuffer.h"
 #include "mozilla/leb128iterator.h"
 #include "mozilla/ModuloBuffer.h"
 #include "mozilla/PowerOfTwo.h"
 #include "mozilla/ProfileBufferChunk.h"
 #include "mozilla/ProfileBufferChunkManagerSingle.h"
 #include "mozilla/ProfileBufferChunkManagerWithLocalLimit.h"
+#include "mozilla/ProfileBufferControlledChunkManager.h"
 #include "mozilla/ProfileChunkedBuffer.h"
 #include "mozilla/Vector.h"
 
 #ifdef MOZ_GECKO_PROFILER
 #  include "BaseProfileJSONWriter.h"
 #  include "BaseProfilerMarkerPayload.h"
 #endif  // MOZ_GECKO_PROFILER
 
@@ -824,16 +825,481 @@ static void TestChunkManagerWithLocalLim
 
 #ifdef DEBUG
   cm.DeregisteredFrom(chunkManagerRegisterer);
 #endif  // DEBUG
 
   printf("TestChunkManagerWithLocalLimit done\n");
 }
 
+static bool IsSameMetadata(
+    const ProfileBufferControlledChunkManager::ChunkMetadata& a1,
+    const ProfileBufferControlledChunkManager::ChunkMetadata& a2) {
+  return a1.mDoneTimeStamp == a2.mDoneTimeStamp &&
+         a1.mBufferBytes == a2.mBufferBytes;
+};
+
+static bool IsSameUpdate(
+    const ProfileBufferControlledChunkManager::Update& a1,
+    const ProfileBufferControlledChunkManager::Update& a2) {
+  // Final and not-an-update don't carry other data, so we can test these two
+  // states first.
+  if (a1.IsFinal() || a2.IsFinal()) {
+    return a1.IsFinal() && a2.IsFinal();
+  }
+  if (a1.IsNotUpdate() || a2.IsNotUpdate()) {
+    return a1.IsNotUpdate() && a2.IsNotUpdate();
+  }
+
+  // Here, both are "normal" udpates, check member variables:
+
+  if (a1.UnreleasedBytes() != a2.UnreleasedBytes()) {
+    return false;
+  }
+  if (a1.ReleasedBytes() != a2.ReleasedBytes()) {
+    return false;
+  }
+  if (a1.OldestDoneTimeStamp() != a2.OldestDoneTimeStamp()) {
+    return false;
+  }
+  if (a1.NewlyReleasedChunksRef().size() !=
+      a2.NewlyReleasedChunksRef().size()) {
+    return false;
+  }
+  for (unsigned i = 0; i < a1.NewlyReleasedChunksRef().size(); ++i) {
+    if (!IsSameMetadata(a1.NewlyReleasedChunksRef()[i],
+                        a2.NewlyReleasedChunksRef()[i])) {
+      return false;
+    }
+  }
+  return true;
+}
+
+static void TestControlledChunkManagerUpdate() {
+  printf("TestControlledChunkManagerUpdate...\n");
+
+  using Update = ProfileBufferControlledChunkManager::Update;
+
+  // Default construction.
+  Update update1;
+  MOZ_RELEASE_ASSERT(update1.IsNotUpdate());
+  MOZ_RELEASE_ASSERT(!update1.IsFinal());
+
+  // Clear an already-cleared update.
+  update1.Clear();
+  MOZ_RELEASE_ASSERT(update1.IsNotUpdate());
+  MOZ_RELEASE_ASSERT(!update1.IsFinal());
+
+  // Final construction with nullptr.
+  const Update final(nullptr);
+  MOZ_RELEASE_ASSERT(final.IsFinal());
+  MOZ_RELEASE_ASSERT(!final.IsNotUpdate());
+
+  // Copy final to cleared.
+  update1 = final;
+  MOZ_RELEASE_ASSERT(update1.IsFinal());
+  MOZ_RELEASE_ASSERT(!update1.IsNotUpdate());
+
+  // Copy final to final.
+  update1 = final;
+  MOZ_RELEASE_ASSERT(update1.IsFinal());
+  MOZ_RELEASE_ASSERT(!update1.IsNotUpdate());
+
+  // Clear a final update.
+  update1.Clear();
+  MOZ_RELEASE_ASSERT(update1.IsNotUpdate());
+  MOZ_RELEASE_ASSERT(!update1.IsFinal());
+
+  // Move final to cleared.
+  update1 = Update(nullptr);
+  MOZ_RELEASE_ASSERT(update1.IsFinal());
+  MOZ_RELEASE_ASSERT(!update1.IsNotUpdate());
+
+  // Move final to final.
+  update1 = Update(nullptr);
+  MOZ_RELEASE_ASSERT(update1.IsFinal());
+  MOZ_RELEASE_ASSERT(!update1.IsNotUpdate());
+
+  // Move from not-an-update (effectively same as Clear).
+  update1 = Update();
+  MOZ_RELEASE_ASSERT(update1.IsNotUpdate());
+  MOZ_RELEASE_ASSERT(!update1.IsFinal());
+
+  auto CreateBiggerChunkAfter = [](const ProfileBufferChunk& aChunkToBeat) {
+    while (TimeStamp::NowUnfuzzed() <=
+           aChunkToBeat.ChunkHeader().mDoneTimeStamp) {
+      ::SleepMilli(1);
+    }
+    auto chunk = ProfileBufferChunk::Create(aChunkToBeat.BufferBytes() * 2);
+    MOZ_RELEASE_ASSERT(!!chunk);
+    MOZ_RELEASE_ASSERT(chunk->BufferBytes() >= aChunkToBeat.BufferBytes() * 2);
+    Unused << chunk->ReserveInitialBlockAsTail(0);
+    chunk->MarkDone();
+    MOZ_RELEASE_ASSERT(chunk->ChunkHeader().mDoneTimeStamp >
+                       aChunkToBeat.ChunkHeader().mDoneTimeStamp);
+    return chunk;
+  };
+
+  update1 = Update(1, 2, nullptr, nullptr);
+
+  // Create initial update with 2 released chunks and 1 unreleased chunk.
+  auto released = ProfileBufferChunk::Create(10);
+  ProfileBufferChunk* c1 = released.get();
+  Unused << c1->ReserveInitialBlockAsTail(0);
+  c1->MarkDone();
+
+  released->SetLast(CreateBiggerChunkAfter(*c1));
+  ProfileBufferChunk* c2 = c1->GetNext();
+
+  auto unreleased = CreateBiggerChunkAfter(*c2);
+  ProfileBufferChunk* c3 = unreleased.get();
+
+  Update update2(c3->BufferBytes(), c1->BufferBytes() + c2->BufferBytes(), c1,
+                 c1);
+  MOZ_RELEASE_ASSERT(IsSameUpdate(
+      update2,
+      Update(c3->BufferBytes(), c1->BufferBytes() + c2->BufferBytes(),
+             c1->ChunkHeader().mDoneTimeStamp,
+             {{c1->ChunkHeader().mDoneTimeStamp, c1->BufferBytes()},
+              {c2->ChunkHeader().mDoneTimeStamp, c2->BufferBytes()}})));
+  // Check every field, this time only, after that we'll trust that the
+  // `SameUpdate` test will be enough.
+  MOZ_RELEASE_ASSERT(!update2.IsNotUpdate());
+  MOZ_RELEASE_ASSERT(!update2.IsFinal());
+  MOZ_RELEASE_ASSERT(update2.UnreleasedBytes() == c3->BufferBytes());
+  MOZ_RELEASE_ASSERT(update2.ReleasedBytes() ==
+                     c1->BufferBytes() + c2->BufferBytes());
+  MOZ_RELEASE_ASSERT(update2.OldestDoneTimeStamp() ==
+                     c1->ChunkHeader().mDoneTimeStamp);
+  MOZ_RELEASE_ASSERT(update2.NewlyReleasedChunksRef().size() == 2);
+  MOZ_RELEASE_ASSERT(
+      IsSameMetadata(update2.NewlyReleasedChunksRef()[0],
+                     {c1->ChunkHeader().mDoneTimeStamp, c1->BufferBytes()}));
+  MOZ_RELEASE_ASSERT(
+      IsSameMetadata(update2.NewlyReleasedChunksRef()[1],
+                     {c2->ChunkHeader().mDoneTimeStamp, c2->BufferBytes()}));
+
+  // Fold into not-an-update.
+  update1.Fold(std::move(update2));
+  MOZ_RELEASE_ASSERT(IsSameUpdate(
+      update1,
+      Update(c3->BufferBytes(), c1->BufferBytes() + c2->BufferBytes(),
+             c1->ChunkHeader().mDoneTimeStamp,
+             {{c1->ChunkHeader().mDoneTimeStamp, c1->BufferBytes()},
+              {c2->ChunkHeader().mDoneTimeStamp, c2->BufferBytes()}})));
+
+  // Pretend nothing happened.
+  update2 = Update(c3->BufferBytes(), c1->BufferBytes() + c2->BufferBytes(), c1,
+                   nullptr);
+  MOZ_RELEASE_ASSERT(IsSameUpdate(
+      update2, Update(c3->BufferBytes(), c1->BufferBytes() + c2->BufferBytes(),
+                      c1->ChunkHeader().mDoneTimeStamp, {})));
+  update1.Fold(std::move(update2));
+  MOZ_RELEASE_ASSERT(IsSameUpdate(
+      update1,
+      Update(c3->BufferBytes(), c1->BufferBytes() + c2->BufferBytes(),
+             c1->ChunkHeader().mDoneTimeStamp,
+             {{c1->ChunkHeader().mDoneTimeStamp, c1->BufferBytes()},
+              {c2->ChunkHeader().mDoneTimeStamp, c2->BufferBytes()}})));
+
+  // Pretend there's a new unreleased chunk.
+  c3->SetLast(CreateBiggerChunkAfter(*c3));
+  ProfileBufferChunk* c4 = c3->GetNext();
+  update2 = Update(c3->BufferBytes() + c4->BufferBytes(),
+                   c1->BufferBytes() + c2->BufferBytes(), c1, nullptr);
+  MOZ_RELEASE_ASSERT(
+      IsSameUpdate(update2, Update(c3->BufferBytes() + c4->BufferBytes(),
+                                   c1->BufferBytes() + c2->BufferBytes(),
+                                   c1->ChunkHeader().mDoneTimeStamp, {})));
+  update1.Fold(std::move(update2));
+  MOZ_RELEASE_ASSERT(IsSameUpdate(
+      update1,
+      Update(c3->BufferBytes() + c4->BufferBytes(),
+             c1->BufferBytes() + c2->BufferBytes(),
+             c1->ChunkHeader().mDoneTimeStamp,
+             {{c1->ChunkHeader().mDoneTimeStamp, c1->BufferBytes()},
+              {c2->ChunkHeader().mDoneTimeStamp, c2->BufferBytes()}})));
+
+  // Pretend the first unreleased chunk c3 has been released.
+  released->SetLast(std::exchange(unreleased, unreleased->ReleaseNext()));
+  update2 =
+      Update(c4->BufferBytes(),
+             c1->BufferBytes() + c2->BufferBytes() + c3->BufferBytes(), c1, c3);
+  MOZ_RELEASE_ASSERT(IsSameUpdate(
+      update2,
+      Update(c4->BufferBytes(),
+             c1->BufferBytes() + c2->BufferBytes() + c3->BufferBytes(),
+             c1->ChunkHeader().mDoneTimeStamp,
+             {{c3->ChunkHeader().mDoneTimeStamp, c3->BufferBytes()}})));
+  update1.Fold(std::move(update2));
+  MOZ_RELEASE_ASSERT(IsSameUpdate(
+      update1,
+      Update(c4->BufferBytes(),
+             c1->BufferBytes() + c2->BufferBytes() + c3->BufferBytes(),
+             c1->ChunkHeader().mDoneTimeStamp,
+             {{c1->ChunkHeader().mDoneTimeStamp, c1->BufferBytes()},
+              {c2->ChunkHeader().mDoneTimeStamp, c2->BufferBytes()},
+              {c3->ChunkHeader().mDoneTimeStamp, c3->BufferBytes()}})));
+
+  // Pretend c1 has been destroyed, so the oldest timestamp is now at c2.
+  released = released->ReleaseNext();
+  c1 = nullptr;
+  update2 = Update(c4->BufferBytes(), c2->BufferBytes() + c3->BufferBytes(), c2,
+                   nullptr);
+  MOZ_RELEASE_ASSERT(IsSameUpdate(
+      update2, Update(c4->BufferBytes(), c2->BufferBytes() + c3->BufferBytes(),
+                      c2->ChunkHeader().mDoneTimeStamp, {})));
+  update1.Fold(std::move(update2));
+  MOZ_RELEASE_ASSERT(IsSameUpdate(
+      update1,
+      Update(c4->BufferBytes(), c2->BufferBytes() + c3->BufferBytes(),
+             c2->ChunkHeader().mDoneTimeStamp,
+             {{c2->ChunkHeader().mDoneTimeStamp, c2->BufferBytes()},
+              {c3->ChunkHeader().mDoneTimeStamp, c3->BufferBytes()}})));
+
+  // Pretend c2 has been recycled to make unreleased c5, and c4 has been
+  // released.
+  auto recycled = std::exchange(released, released->ReleaseNext());
+  recycled->MarkRecycled();
+  Unused << recycled->ReserveInitialBlockAsTail(0);
+  recycled->MarkDone();
+  released->SetLast(std::move(unreleased));
+  unreleased = std::move(recycled);
+  ProfileBufferChunk* c5 = c2;
+  c2 = nullptr;
+  update2 =
+      Update(c5->BufferBytes(), c3->BufferBytes() + c4->BufferBytes(), c3, c4);
+  MOZ_RELEASE_ASSERT(IsSameUpdate(
+      update2,
+      Update(c5->BufferBytes(), c3->BufferBytes() + c4->BufferBytes(),
+             c3->ChunkHeader().mDoneTimeStamp,
+             {{c4->ChunkHeader().mDoneTimeStamp, c4->BufferBytes()}})));
+  update1.Fold(std::move(update2));
+  MOZ_RELEASE_ASSERT(IsSameUpdate(
+      update1,
+      Update(c5->BufferBytes(), c3->BufferBytes() + c4->BufferBytes(),
+             c3->ChunkHeader().mDoneTimeStamp,
+             {{c3->ChunkHeader().mDoneTimeStamp, c3->BufferBytes()},
+              {c4->ChunkHeader().mDoneTimeStamp, c4->BufferBytes()}})));
+
+  // And send a final update.
+  update1.Fold(Update(nullptr));
+  MOZ_RELEASE_ASSERT(update1.IsFinal());
+  MOZ_RELEASE_ASSERT(!update1.IsNotUpdate());
+
+  printf("TestControlledChunkManagerUpdate done\n");
+}
+
+static void TestControlledChunkManagerWithLocalLimit() {
+  printf("TestControlledChunkManagerWithLocalLimit...\n");
+
+  // Construct a ProfileBufferChunkManagerWithLocalLimit with chunk of minimum
+  // size >=100, up to 1000 bytes.
+  constexpr ProfileBufferChunk::Length MaxTotalBytes = 1000;
+  constexpr ProfileBufferChunk::Length ChunkMinBufferBytes = 100;
+  ProfileBufferChunkManagerWithLocalLimit cmll{MaxTotalBytes,
+                                               ChunkMinBufferBytes};
+
+  // Reference to chunk manager base class.
+  ProfileBufferChunkManager& cm = cmll;
+
+  // Reference to controlled chunk manager base class.
+  ProfileBufferControlledChunkManager& ccm = cmll;
+
+#ifdef DEBUG
+  const char* chunkManagerRegisterer =
+      "TestControlledChunkManagerWithLocalLimit";
+  cm.RegisteredWith(chunkManagerRegisterer);
+#endif  // DEBUG
+
+  MOZ_RELEASE_ASSERT(cm.MaxTotalSize() == MaxTotalBytes,
+                     "Max total size should be exactly as given");
+
+  unsigned destroyedChunks = 0;
+  unsigned destroyedBytes = 0;
+  cm.SetChunkDestroyedCallback([&](const ProfileBufferChunk& aChunks) {
+    for (const ProfileBufferChunk* chunk = &aChunks; chunk;
+         chunk = chunk->GetNext()) {
+      destroyedChunks += 1;
+      destroyedBytes += chunk->BufferBytes();
+    }
+  });
+
+  using Update = ProfileBufferControlledChunkManager::Update;
+  unsigned updateCount = 0;
+  ProfileBufferControlledChunkManager::Update update;
+  MOZ_RELEASE_ASSERT(update.IsNotUpdate());
+  auto updateCallback = [&](Update&& aUpdate) {
+    ++updateCount;
+    update.Fold(std::move(aUpdate));
+  };
+  ccm.SetUpdateCallback(updateCallback);
+  MOZ_RELEASE_ASSERT(updateCount == 1,
+                     "SetUpdateCallback should have triggered an update");
+  MOZ_RELEASE_ASSERT(IsSameUpdate(update, Update(0, 0, TimeStamp{}, {})));
+  updateCount = 0;
+  update.Clear();
+
+  UniquePtr<ProfileBufferChunk> extantReleasedChunks =
+      cm.GetExtantReleasedChunks();
+  MOZ_RELEASE_ASSERT(!extantReleasedChunks, "Unexpected released chunk(s)");
+  MOZ_RELEASE_ASSERT(updateCount == 1,
+                     "GetExtantReleasedChunks should have triggered an update");
+  MOZ_RELEASE_ASSERT(IsSameUpdate(update, Update(0, 0, TimeStamp{}, {})));
+  updateCount = 0;
+  update.Clear();
+
+  // First request.
+  UniquePtr<ProfileBufferChunk> chunk = cm.GetChunk();
+  MOZ_RELEASE_ASSERT(!!chunk,
+                     "First chunk immediate request should always work");
+  const auto chunkActualBufferBytes = chunk->BufferBytes();
+  // Keep address, for later checks.
+  const uintptr_t chunk1Address = reinterpret_cast<uintptr_t>(chunk.get());
+  MOZ_RELEASE_ASSERT(updateCount == 1,
+                     "GetChunk should have triggered an update");
+  MOZ_RELEASE_ASSERT(
+      IsSameUpdate(update, Update(chunk->BufferBytes(), 0, TimeStamp{}, {})));
+  updateCount = 0;
+  update.Clear();
+
+  extantReleasedChunks = cm.GetExtantReleasedChunks();
+  MOZ_RELEASE_ASSERT(!extantReleasedChunks, "Unexpected released chunk(s)");
+  MOZ_RELEASE_ASSERT(updateCount == 1,
+                     "GetExtantReleasedChunks should have triggered an update");
+  MOZ_RELEASE_ASSERT(
+      IsSameUpdate(update, Update(chunk->BufferBytes(), 0, TimeStamp{}, {})));
+  updateCount = 0;
+  update.Clear();
+
+  // For this test, we need to be able to get at least 2 chunks without hitting
+  // the limit. (If this failed, it wouldn't necessary be a problem with
+  // ProfileBufferChunkManagerWithLocalLimit, fiddle with constants at the top
+  // of this test.)
+  MOZ_RELEASE_ASSERT(chunkActualBufferBytes < 2 * MaxTotalBytes);
+
+  ProfileBufferChunk::Length previousUnreleasedBytes = chunk->BufferBytes();
+  ProfileBufferChunk::Length previousReleasedBytes = 0;
+  TimeStamp previousOldestDoneTimeStamp;
+
+  unsigned chunk1ReuseCount = 0;
+
+  // We will do enough loops to go through the maximum size a number of times.
+  const unsigned Rollovers = 3;
+  const unsigned Loops = Rollovers * MaxTotalBytes / chunkActualBufferBytes;
+  for (unsigned i = 0; i < Loops; ++i) {
+    // Add some data to the chunk.
+    const ProfileBufferIndex index =
+        ProfileBufferIndex(chunkActualBufferBytes) * i + 1;
+    chunk->SetRangeStart(index);
+    Unused << chunk->ReserveInitialBlockAsTail(1);
+    Unused << chunk->ReserveBlock(2);
+
+    // Request a new chunk.
+    UniquePtr<ProfileBufferChunk> newChunk;
+    cm.RequestChunk([&](UniquePtr<ProfileBufferChunk> aChunk) {
+      newChunk = std::move(aChunk);
+    });
+    MOZ_RELEASE_ASSERT(updateCount == 0,
+                       "RequestChunk() shouldn't have triggered an update");
+    cm.FulfillChunkRequests();
+    MOZ_RELEASE_ASSERT(!!newChunk, "Chunk request should always work");
+    MOZ_RELEASE_ASSERT(newChunk->BufferBytes() == chunkActualBufferBytes,
+                       "Unexpected chunk size");
+    MOZ_RELEASE_ASSERT(!newChunk->GetNext(), "There should only be one chunk");
+
+    MOZ_RELEASE_ASSERT(updateCount == 1,
+                       "FulfillChunkRequests() after a request should have "
+                       "triggered an update");
+    MOZ_RELEASE_ASSERT(!update.IsFinal());
+    MOZ_RELEASE_ASSERT(!update.IsNotUpdate());
+    MOZ_RELEASE_ASSERT(update.UnreleasedBytes() ==
+                       previousUnreleasedBytes + newChunk->BufferBytes());
+    previousUnreleasedBytes = update.UnreleasedBytes();
+    MOZ_RELEASE_ASSERT(update.ReleasedBytes() <= previousReleasedBytes);
+    previousReleasedBytes = update.ReleasedBytes();
+    MOZ_RELEASE_ASSERT(previousOldestDoneTimeStamp.IsNull() ||
+                       update.OldestDoneTimeStamp() >=
+                           previousOldestDoneTimeStamp);
+    previousOldestDoneTimeStamp = update.OldestDoneTimeStamp();
+    MOZ_RELEASE_ASSERT(update.NewlyReleasedChunksRef().empty());
+    updateCount = 0;
+    update.Clear();
+
+    // Make sure the "Done" timestamp below cannot be the same as from the
+    // previous loop.
+    const TimeStamp now = TimeStamp::NowUnfuzzed();
+    while (TimeStamp::NowUnfuzzed() == now) {
+      ::SleepMilli(1);
+    }
+
+    // Mark previous chunk done and release it.
+    chunk->MarkDone();
+    const auto doneTimeStamp = chunk->ChunkHeader().mDoneTimeStamp;
+    const auto bufferBytes = chunk->BufferBytes();
+    cm.ReleaseChunks(std::move(chunk));
+
+    MOZ_RELEASE_ASSERT(updateCount == 1,
+                       "ReleaseChunks() should have triggered an update");
+    MOZ_RELEASE_ASSERT(!update.IsFinal());
+    MOZ_RELEASE_ASSERT(!update.IsNotUpdate());
+    MOZ_RELEASE_ASSERT(update.UnreleasedBytes() ==
+                       previousUnreleasedBytes - bufferBytes);
+    previousUnreleasedBytes = update.UnreleasedBytes();
+    MOZ_RELEASE_ASSERT(update.ReleasedBytes() ==
+                       previousReleasedBytes + bufferBytes);
+    previousReleasedBytes = update.ReleasedBytes();
+    MOZ_RELEASE_ASSERT(previousOldestDoneTimeStamp.IsNull() ||
+                       update.OldestDoneTimeStamp() >=
+                           previousOldestDoneTimeStamp);
+    previousOldestDoneTimeStamp = update.OldestDoneTimeStamp();
+    MOZ_RELEASE_ASSERT(update.OldestDoneTimeStamp() <= doneTimeStamp);
+    MOZ_RELEASE_ASSERT(update.NewlyReleasedChunksRef().size() == 1);
+    MOZ_RELEASE_ASSERT(update.NewlyReleasedChunksRef()[0].mDoneTimeStamp ==
+                       doneTimeStamp);
+    MOZ_RELEASE_ASSERT(update.NewlyReleasedChunksRef()[0].mBufferBytes ==
+                       bufferBytes);
+    updateCount = 0;
+    update.Clear();
+
+    // And cycle to the new chunk.
+    chunk = std::move(newChunk);
+
+    if (reinterpret_cast<uintptr_t>(chunk.get()) == chunk1Address) {
+      ++chunk1ReuseCount;
+    }
+  }
+
+  // Enough testing! Clean-up.
+  Unused << chunk->ReserveInitialBlockAsTail(0);
+  chunk->MarkDone();
+  cm.ForgetUnreleasedChunks();
+  MOZ_RELEASE_ASSERT(
+      updateCount == 1,
+      "ForgetUnreleasedChunks() should have triggered an update");
+  MOZ_RELEASE_ASSERT(!update.IsFinal());
+  MOZ_RELEASE_ASSERT(!update.IsNotUpdate());
+  MOZ_RELEASE_ASSERT(update.UnreleasedBytes() == 0);
+  MOZ_RELEASE_ASSERT(update.ReleasedBytes() == previousReleasedBytes);
+  MOZ_RELEASE_ASSERT(update.NewlyReleasedChunksRef().empty() == 1);
+  updateCount = 0;
+  update.Clear();
+
+  ccm.SetUpdateCallback({});
+  MOZ_RELEASE_ASSERT(updateCount == 1,
+                     "SetUpdateCallback({}) should have triggered an update");
+  MOZ_RELEASE_ASSERT(update.IsFinal());
+
+#ifdef DEBUG
+  cm.DeregisteredFrom(chunkManagerRegisterer);
+#endif  // DEBUG
+
+  printf("TestControlledChunkManagerWithLocalLimit done\n");
+}
+
 static void TestChunkedBuffer() {
   printf("TestChunkedBuffer...\n");
 
   ProfileBufferBlockIndex blockIndex;
   MOZ_RELEASE_ASSERT(!blockIndex);
   MOZ_RELEASE_ASSERT(blockIndex == nullptr);
 
   // Create an out-of-session ProfileChunkedBuffer.
@@ -2516,16 +2982,18 @@ void TestBlocksRingBufferSerialization()
 
 void TestProfilerDependencies() {
   TestPowerOfTwoMask();
   TestPowerOfTwo();
   TestLEB128();
   TestChunk();
   TestChunkManagerSingle();
   TestChunkManagerWithLocalLimit();
+  TestControlledChunkManagerUpdate();
+  TestControlledChunkManagerWithLocalLimit();
   TestChunkedBuffer();
   TestChunkedBufferSingle();
   TestModuloBuffer();
   TestBlocksRingBufferAPI();
   TestBlocksRingBufferUnderlyingBufferChanges();
   TestBlocksRingBufferThreading();
   TestBlocksRingBufferSerialization();
 }
--- a/netwerk/ipc/DocumentChannel.h
+++ b/netwerk/ipc/DocumentChannel.h
@@ -40,25 +40,16 @@ class DocumentChannel : public nsIIdentC
   NS_DECL_ISUPPORTS
   NS_DECL_NSIREQUEST
   NS_DECL_NSICHANNEL
   NS_DECL_NSIIDENTCHANNEL
   NS_DECL_NSITRACEABLECHANNEL
 
   NS_DECLARE_STATIC_IID_ACCESSOR(DOCUMENT_CHANNEL_IID)
 
-  const nsTArray<DocumentChannelRedirect>& GetRedirectChain() const {
-    return mRedirects;
-  }
-
-  void GetLastVisit(nsIURI** aURI, uint32_t* aChannelRedirectFlags) const {
-    *aURI = do_AddRef(mLastVisitInfo.uri()).take();
-    *aChannelRedirectFlags = mLastVisitInfo.previousFlags();
-  }
-
   void SetNavigationTiming(nsDOMNavigationTiming* aTiming) {
     mTiming = aTiming;
   }
 
   void SetInitialClientInfo(const Maybe<dom::ClientInfo>& aInfo) {
     mInitialClientInfo = aInfo;
   }
 
@@ -84,19 +75,16 @@ class DocumentChannel : public nsIIdentC
   void DisconnectChildListeners(const nsresult& aStatus,
                                 const nsresult& aLoadGroupStatus);
   virtual void DeleteIPDL() {}
 
   nsDocShell* GetDocShell();
 
   virtual ~DocumentChannel() = default;
 
-  LastVisitInfo mLastVisitInfo;
-  nsTArray<DocumentChannelRedirect> mRedirects;
-
   const TimeStamp mAsyncOpenTime;
   const RefPtr<nsDocShellLoadState> mLoadState;
   const uint32_t mCacheKey;
 
   nsresult mStatus = NS_OK;
   bool mCanceled = false;
   bool mIsPending = false;
   bool mWasOpened = false;
--- a/netwerk/ipc/DocumentChannelChild.cpp
+++ b/netwerk/ipc/DocumentChannelChild.cpp
@@ -153,18 +153,16 @@ IPCResult DocumentChannelChild::RecvRedi
     nsWeakPtr ctx =
         static_cast<nsCSPContext*>(policy.get())->GetLoadingContext();
     cspToInheritLoadingDocument = do_QueryReferent(ctx);
   }
   nsCOMPtr<nsILoadInfo> loadInfo;
   MOZ_ALWAYS_SUCCEEDS(LoadInfoArgsToLoadInfo(
       aArgs.loadInfo(), cspToInheritLoadingDocument, getter_AddRefs(loadInfo)));
 
-  mLastVisitInfo = std::move(aArgs.lastVisitInfo());
-  mRedirects = std::move(aArgs.redirects());
   mRedirectResolver = std::move(aResolve);
 
   nsCOMPtr<nsIChannel> newChannel;
   MOZ_ASSERT((aArgs.loadStateLoadFlags() &
               nsDocShell::InternalLoad::INTERNAL_LOAD_FLAGS_IS_SRCDOC) ||
              aArgs.srcdocData().IsVoid());
   nsresult rv = nsDocShell::CreateRealChannelForDocument(
       getter_AddRefs(newChannel), aArgs.uri(), loadInfo, nullptr,
--- a/netwerk/ipc/DocumentLoadListener.cpp
+++ b/netwerk/ipc/DocumentLoadListener.cpp
@@ -47,16 +47,27 @@
 mozilla::LazyLogModule gDocumentChannelLog("DocumentChannel");
 #define LOG(fmt) MOZ_LOG(gDocumentChannelLog, mozilla::LogLevel::Verbose, fmt)
 
 using namespace mozilla::dom;
 
 namespace mozilla {
 namespace net {
 
+static void SetNeedToAddURIVisit(nsIChannel* aChannel,
+                                 bool aNeedToAddURIVisit) {
+  nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(aChannel));
+  if (!props) {
+    return;
+  }
+
+  props->SetPropertyAsBool(NS_LITERAL_STRING("docshell.needToAddURIVisit"),
+                           aNeedToAddURIVisit);
+}
+
 /**
  * An extension to nsDocumentOpenInfo that we run in the parent process, so
  * that we can make the decision to retarget to content handlers or the external
  * helper app, before we make process switching decisions.
  *
  * This modifies the behaviour of nsDocumentOpenInfo so that it can do
  * retargeting, but doesn't do stream conversion (but confirms that we will be
  * able to do so later).
@@ -257,22 +268,50 @@ DocumentLoadListener::DocumentLoadListen
       this, aBrowsingContext, aBrowsingContext->UsePrivateBrowsing());
   mPendingDocumentChannelBridgeProcess = Some(aPendingBridgeProcess);
 }
 
 DocumentLoadListener::~DocumentLoadListener() {
   LOG(("DocumentLoadListener dtor [this=%p]", this));
 }
 
-net::LastVisitInfo DocumentLoadListener::LastVisitInfo() const {
+void DocumentLoadListener::AddURIVisit(nsIChannel* aChannel,
+                                       uint32_t aLoadFlags) {
+  if (mLoadStateLoadType == LOAD_ERROR_PAGE ||
+      mLoadStateLoadType == LOAD_BYPASS_HISTORY) {
+    return;
+  }
+
+  nsCOMPtr<nsIURI> uri;
+  NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
+
   nsCOMPtr<nsIURI> previousURI;
   uint32_t previousFlags = 0;
-  nsDocShell::ExtractLastVisit(mChannel, getter_AddRefs(previousURI),
-                               &previousFlags);
-  return net::LastVisitInfo{previousURI, previousFlags};
+  if (mLoadStateLoadType & nsIDocShell::LOAD_CMD_RELOAD) {
+    previousURI = uri;
+  } else {
+    nsDocShell::ExtractLastVisit(aChannel, getter_AddRefs(previousURI),
+                                 &previousFlags);
+  }
+
+  // Get the HTTP response code, if available.
+  uint32_t responseStatus = 0;
+  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
+  if (httpChannel) {
+    Unused << httpChannel->GetResponseStatus(&responseStatus);
+  }
+
+  RefPtr<CanonicalBrowsingContext> browsingContext =
+      mParentChannelListener->GetBrowsingContext();
+  nsCOMPtr<nsIWidget> widget =
+      browsingContext->GetParentProcessWidgetContaining();
+
+  nsDocShell::InternalAddURIVisit(uri, previousURI, previousFlags,
+                                  responseStatus, browsingContext, widget,
+                                  mLoadStateLoadType);
 }
 
 already_AddRefed<LoadInfo> DocumentLoadListener::CreateLoadInfo(
     CanonicalBrowsingContext* aBrowsingContext, nsDocShellLoadState* aLoadState,
     uint64_t aOuterWindowId) {
   // TODO: Block copied from nsDocShell::DoURILoad, refactor out somewhere
   bool inheritPrincipal = false;
 
@@ -1130,22 +1169,22 @@ void DocumentLoadListener::SerializeRedi
   }
 
   nsString contentDispositionFilenameTemp;
   rv = mChannel->GetContentDispositionFilename(contentDispositionFilenameTemp);
   if (NS_SUCCEEDED(rv)) {
     aArgs.contentDispositionFilename() = Some(contentDispositionFilenameTemp);
   }
 
+  SetNeedToAddURIVisit(mChannel, false);
+
   aArgs.newLoadFlags() = aLoadFlags;
   aArgs.redirectFlags() = aRedirectFlags;
-  aArgs.redirects() = mRedirects.Clone();
   aArgs.redirectIdentifier() = mCrossProcessRedirectIdentifier;
   aArgs.properties() = do_QueryObject(mChannel.get());
-  aArgs.lastVisitInfo() = LastVisitInfo();
   aArgs.srcdocData() = mSrcdocData;
   aArgs.baseUri() = mBaseURI;
   aArgs.loadStateLoadFlags() = mLoadStateLoadFlags;
   aArgs.loadStateLoadType() = mLoadStateLoadType;
 }
 
 bool DocumentLoadListener::MaybeTriggerProcessSwitch() {
   MOZ_DIAGNOSTIC_ASSERT(!mDoingProcessSwitch,
@@ -1371,17 +1410,30 @@ auto DocumentLoadListener::EnsureBridge(
   return mBridgePromise.Ensure(__func__);
 }
 
 RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
 DocumentLoadListener::RedirectToRealChannel(
     uint32_t aRedirectFlags, uint32_t aLoadFlags,
     const Maybe<uint64_t>& aDestinationProcess,
     nsTArray<ParentEndpoint>&& aStreamFilterEndpoints) {
-  LOG(("DocumentLoadListener RedirectToRealChannel [this=%p]", this));
+  LOG(
+      ("DocumentLoadListener RedirectToRealChannel [this=%p] "
+       "aRedirectFlags=%" PRIx32 ", aLoadFlags=%" PRIx32,
+       this, aRedirectFlags, aLoadFlags));
+
+  // TODO(djg): Add the last URI visit to history if success. Is there a better
+  // place to handle this? Need access to the updated aLoadFlags.
+  nsresult status = NS_OK;
+  mChannel->GetStatus(&status);
+  bool updateGHistory =
+      nsDocShell::ShouldUpdateGlobalHistory(mLoadStateLoadType);
+  if (NS_SUCCEEDED(status) && updateGHistory && !net::ChannelIsPost(mChannel)) {
+    AddURIVisit(mChannel, aLoadFlags);
+  }
 
   if (aDestinationProcess || OtherPid()) {
     // Register the new channel and obtain id for it
     nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
         RedirectChannelRegistrar::GetOrCreate();
     MOZ_ASSERT(registrar);
     MOZ_ALWAYS_SUCCEEDS(
         registrar->RegisterChannel(mChannel, &mRedirectChannelId));
@@ -1481,17 +1533,17 @@ void DocumentLoadListener::TriggerRedire
     }
   }
 
   // If we didn't have any redirects, then we pass the REDIRECT_INTERNAL flag
   // for this channel switch so that it isn't recorded in session history etc.
   // If there were redirect(s), then we want this switch to be recorded as a
   // real one, since we have a new URI.
   uint32_t redirectFlags = 0;
-  if (mRedirects.IsEmpty()) {
+  if (!mHaveVisibleRedirect) {
     redirectFlags = nsIChannelEventSink::REDIRECT_INTERNAL;
   }
 
   uint32_t newLoadFlags = nsIRequest::LOAD_NORMAL;
   MOZ_ALWAYS_SUCCEEDS(mChannel->GetLoadFlags(&newLoadFlags));
   // We're pulling our flags from the inner channel, which may not have this
   // flag set on it. This is the case when loading a 'view-source' channel.
   newLoadFlags |= nsIChannel::LOAD_DOCUMENT_URI;
@@ -1762,30 +1814,31 @@ DocumentLoadListener::AsyncOnChannelRedi
   // history for them, so just immediately verify and return.
   if (aFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
     LOG(
         ("DocumentLoadListener AsyncOnChannelRedirect [this=%p] "
          "flags=REDIRECT_INTERNAL",
          this));
     aCallback->OnRedirectVerifyCallback(NS_OK);
     return NS_OK;
-  } else {
+  }
+
+  if (!net::ChannelIsPost(aOldChannel)) {
+    AddURIVisit(aOldChannel, 0);
+
     nsCOMPtr<nsIURI> oldURI;
     aOldChannel->GetURI(getter_AddRefs(oldURI));
-    uint32_t responseStatus = 0;
-    if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aOldChannel)) {
-      Unused << httpChannel->GetResponseStatus(&responseStatus);
-    }
-    mRedirects.AppendElement(DocumentChannelRedirect{
-        oldURI, aFlags, responseStatus, net::ChannelIsPost(aOldChannel)});
+    nsDocShell::SaveLastVisit(aNewChannel, oldURI, aFlags);
   }
+  mHaveVisibleRedirect |= true;
+
   LOG(
       ("DocumentLoadListener AsyncOnChannelRedirect [this=%p] "
-       "mRedirects=%" PRIx32,
-       this, uint32_t(mRedirects.Length())));
+       "mHaveVisibleRedirect=%c",
+       this, mHaveVisibleRedirect ? 'T' : 'F'));
 
   // If this is a cross-origin redirect, then we should no longer allow
   // mixed content. The destination docshell checks this in its redirect
   // handling, but if we deliver to a new docshell (with a process switch)
   // then this doesn't happen.
   // Manually remove the allow mixed content flags.
   nsresult rv = nsContentUtils::CheckSameOrigin(aOldChannel, aNewChannel);
   if (NS_FAILED(rv)) {
@@ -1805,38 +1858,16 @@ DocumentLoadListener::AsyncOnChannelRedi
   aNewChannel->GetOriginalURI(getter_AddRefs(mChannelCreationURI));
 
   // Clear out our nsIParentChannel functions, since a normal parent
   // channel would actually redirect and not have those values on the new one.
   // We expect the URI classifier to run on the redirected channel with
   // the new URI and set these again.
   mIParentChannelFunctions.Clear();
 
-  nsCOMPtr<nsILoadInfo> loadInfo = aOldChannel->LoadInfo();
-
-  nsCOMPtr<nsIURI> originalUri;
-  rv = aOldChannel->GetOriginalURI(getter_AddRefs(originalUri));
-  if (NS_FAILED(rv)) {
-    aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
-    return rv;
-  }
-
-  nsCOMPtr<nsIURI> newUri;
-  rv = aNewChannel->GetURI(getter_AddRefs(newUri));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  Maybe<nsresult> cancelCode;
-  rv = CSPService::ConsultCSPForRedirect(originalUri, newUri, loadInfo,
-                                         cancelCode);
-
-  if (cancelCode) {
-    aOldChannel->Cancel(*cancelCode);
-  }
-  NS_ENSURE_SUCCESS(rv, rv);
-
 #ifdef ANDROID
   nsCOMPtr<nsIURI> uriBeingLoaded =
       AntiTrackingUtils::MaybeGetDocumentURIBeingLoaded(mChannel);
   RefPtr<CanonicalBrowsingContext> bc =
       mParentChannelListener->GetBrowsingContext();
 
   RefPtr<MozPromise<bool, bool, false>> promise;
   if (bc->IsTopContent()) {
--- a/netwerk/ipc/DocumentLoadListener.h
+++ b/netwerk/ipc/DocumentLoadListener.h
@@ -198,22 +198,16 @@ class DocumentLoadListener : public nsII
 
   // Serializes all data needed to setup the new replacement channel
   // in the content process into the RedirectToRealChannelArgs struct.
   void SerializeRedirectData(RedirectToRealChannelArgs& aArgs,
                              bool aIsCrossProcess, uint32_t aRedirectFlags,
                              uint32_t aLoadFlags,
                              dom::ContentParent* aParent) const;
 
-  const nsTArray<DocumentChannelRedirect>& Redirects() const {
-    return mRedirects;
-  }
-
-  net::LastVisitInfo LastVisitInfo() const;
-
  protected:
   DocumentLoadListener(dom::CanonicalBrowsingContext* aBrowsingContext,
                        base::ProcessId aPendingBridgeProcess);
   virtual ~DocumentLoadListener();
 
   // Called when we were created without a document channel bridge,
   // and now it has been created and attached.
   void NotifyBridgeConnected(ADocumentChannelBridge* aBridge);
@@ -262,16 +256,17 @@ class DocumentLoadListener : public nsII
 
   // Construct a LoadInfo object to use for the internal channel.
   already_AddRefed<LoadInfo> CreateLoadInfo(
       dom::CanonicalBrowsingContext* aBrowsingContext,
       nsDocShellLoadState* aLoadState, uint64_t aOuterWindowId);
 
   dom::CanonicalBrowsingContext* GetBrowsingContext();
 
+  void AddURIVisit(nsIChannel* aChannel, uint32_t aLoadFlags);
   bool HasCrossOriginOpenerPolicyMismatch() const;
   void ApplyPendingFunctions(nsISupports* aChannel) const;
 
   // This defines a variant that describes all the attribute setters (and their
   // parameters) from nsIParentChannel
   //
   // NotifyFlashPluginStateChanged(nsIHttpChannel::FlashPluginState aState) = 0;
   // SetClassifierMatchedInfo(const nsACString& aList, const nsACString&
@@ -390,17 +385,19 @@ class DocumentLoadListener : public nsII
   nsCOMPtr<nsIURI> mChannelCreationURI;
 
   // The original navigation timing information containing various timestamps
   // such as when the original load started.
   // This will be passed back to the new content process should a process
   // switch occurs.
   RefPtr<nsDOMNavigationTiming> mTiming;
 
-  nsTArray<DocumentChannelRedirect> mRedirects;
+  // Used to identify an internal redirect in redirect chain.
+  // True when we have seen at least one non-interal redirect.
+  bool mHaveVisibleRedirect = false;
 
   nsTArray<StreamFilterRequest> mStreamFilterRequests;
 
   nsString mSrcdocData;
   nsCOMPtr<nsIURI> mBaseURI;
 
   // Flags from nsDocShellLoadState::LoadFlags/Type that we want to make
   // available to the new docshell if we switch processes.
--- a/netwerk/ipc/NeckoChannelParams.ipdlh
+++ b/netwerk/ipc/NeckoChannelParams.ipdlh
@@ -439,44 +439,30 @@ struct DocumentChannelCreationArgs {
   nsDOMNavigationTiming? timing;
   IPCClientInfo? initialClientInfo;
   uint64_t outerWindowId;
   bool hasValidTransientUserAction;
   bool uriModified;
   bool isXFOError;
 };
 
-struct DocumentChannelRedirect {
-  nsIURI uri;
-  uint32_t redirectFlags;
-  uint32_t responseStatus;
-  bool isPost;
-};
-
-struct LastVisitInfo {
-  nsIURI uri;
-  uint32_t previousFlags;
-};
-
 struct RedirectToRealChannelArgs {
   uint32_t registrarId;
   nsIURI uri;
   uint32_t newLoadFlags;
   ReplacementChannelConfigInit? init;
   LoadInfoArgs? loadInfo;
-  DocumentChannelRedirect[] redirects;
   uint64_t channelId;
   nsIURI originalURI;
   uint32_t redirectMode;
   uint32_t redirectFlags;
   uint32_t? contentDisposition;
   nsString? contentDispositionFilename;
   uint64_t redirectIdentifier;
   nsIPropertyBag2 properties;
-  LastVisitInfo lastVisitInfo;
   uint32_t loadStateLoadFlags;
   uint32_t loadStateLoadType;
   nsDOMNavigationTiming? timing;
   nsString srcdocData;
   nsIURI baseUri;
 };
 
 struct TimingStructArgs {
--- a/netwerk/ipc/NeckoChild.cpp
+++ b/netwerk/ipc/NeckoChild.cpp
@@ -122,25 +122,18 @@ PAltDataOutputStreamChild* NeckoChild::A
 bool NeckoChild::DeallocPAltDataOutputStreamChild(
     PAltDataOutputStreamChild* aActor) {
   AltDataOutputStreamChild* child =
       static_cast<AltDataOutputStreamChild*>(aActor);
   child->ReleaseIPDLReference();
   return true;
 }
 
-already_AddRefed<PDocumentChannelChild> NeckoChild::AllocPDocumentChannelChild(
-    const PBrowserOrId& aBrowser, const SerializedLoadContext& aSerialized,
-    const DocumentChannelCreationArgs& args) {
-  MOZ_ASSERT_UNREACHABLE("AllocPDocumentChannelChild should not be called");
-  return nullptr;
-}
-
 PFTPChannelChild* NeckoChild::AllocPFTPChannelChild(
-    const PBrowserOrId& aBrowser, const SerializedLoadContext& aSerialized,
+    PBrowserChild* aBrowser, const SerializedLoadContext& aSerialized,
     const FTPChannelCreationArgs& aOpenArgs) {
   // We don't allocate here: see FTPChannelChild::AsyncOpen()
   MOZ_CRASH("AllocPFTPChannelChild should not be called");
   return nullptr;
 }
 
 bool NeckoChild::DeallocPFTPChannelChild(PFTPChannelChild* channel) {
   MOZ_ASSERT(IsNeckoChild(), "DeallocPFTPChannelChild called by non-child!");
@@ -161,17 +154,17 @@ bool NeckoChild::DeallocPCookieServiceCh
                "DeallocPCookieServiceChild called by non-child!");
 
   CookieServiceChild* p = static_cast<CookieServiceChild*>(cs);
   p->Release();
   return true;
 }
 
 PWebSocketChild* NeckoChild::AllocPWebSocketChild(
-    const PBrowserOrId& browser, const SerializedLoadContext& aSerialized,
+    PBrowserChild* browser, const SerializedLoadContext& aSerialized,
     const uint32_t& aSerial) {
   MOZ_ASSERT_UNREACHABLE("AllocPWebSocketChild should not be called");
   return nullptr;
 }
 
 bool NeckoChild::DeallocPWebSocketChild(PWebSocketChild* child) {
   WebSocketChannelChild* p = static_cast<WebSocketChannelChild*>(child);
   p->ReleaseIPDLReference();
--- a/netwerk/ipc/NeckoChild.h
+++ b/netwerk/ipc/NeckoChild.h
@@ -31,27 +31,23 @@ class NeckoChild : public PNeckoChild {
   PWebrtcTCPSocketChild* AllocPWebrtcTCPSocketChild(const Maybe<TabId>& tabId);
   bool DeallocPWebrtcTCPSocketChild(PWebrtcTCPSocketChild* aActor);
 
   PAltDataOutputStreamChild* AllocPAltDataOutputStreamChild(
       const nsCString& type, const int64_t& predictedSize,
       PHttpChannelChild* channel);
   bool DeallocPAltDataOutputStreamChild(PAltDataOutputStreamChild* aActor);
 
-  already_AddRefed<PDocumentChannelChild> AllocPDocumentChannelChild(
-      const PBrowserOrId& aBrowser, const SerializedLoadContext& aSerialized,
-      const DocumentChannelCreationArgs& args);
-
   PCookieServiceChild* AllocPCookieServiceChild();
   bool DeallocPCookieServiceChild(PCookieServiceChild*);
   PFTPChannelChild* AllocPFTPChannelChild(
-      const PBrowserOrId& aBrowser, const SerializedLoadContext& aSerialized,
+      PBrowserChild* aBrowser, const SerializedLoadContext& aSerialized,
       const FTPChannelCreationArgs& aOpenArgs);
   bool DeallocPFTPChannelChild(PFTPChannelChild*);
-  PWebSocketChild* AllocPWebSocketChild(const PBrowserOrId&,
+  PWebSocketChild* AllocPWebSocketChild(PBrowserChild*,
                                         const SerializedLoadContext&,
                                         const uint32_t&);
   bool DeallocPWebSocketChild(PWebSocketChild*);
   PTCPSocketChild* AllocPTCPSocketChild(const nsString& host,
                                         const uint16_t& port);
   bool DeallocPTCPSocketChild(PTCPSocketChild*);
   PTCPServerSocketChild* AllocPTCPServerSocketChild(
       const uint16_t& aLocalPort, const uint16_t& aBacklog,
--- a/netwerk/ipc/NeckoParent.cpp
+++ b/netwerk/ipc/NeckoParent.cpp
@@ -142,84 +142,73 @@ const char* NeckoParent::GetValidatedOri
     aAttrs = OriginAttributes(false);
   } else {
     aAttrs = aSerialized.mOriginAttributes;
   }
   return nullptr;
 }
 
 const char* NeckoParent::CreateChannelLoadContext(
-    const PBrowserOrId& aBrowser, PContentParent* aContent,
+    PBrowserParent* aBrowser, PContentParent* aContent,
     const SerializedLoadContext& aSerialized,
     nsIPrincipal* aRequestingPrincipal, nsCOMPtr<nsILoadContext>& aResult) {
   OriginAttributes attrs;
   const char* error = GetValidatedOriginAttributes(aSerialized, aContent,
                                                    aRequestingPrincipal, attrs);
   if (error) {
     return error;
   }
 
   // if !UsingNeckoIPCSecurity(), we may not have a LoadContext to set. This is
   // the common case for most xpcshell tests.
   if (aSerialized.IsNotNull()) {
     attrs.SyncAttributesWithPrivateBrowsing(
         aSerialized.mOriginAttributes.mPrivateBrowsingId > 0);
-    switch (aBrowser.type()) {
-      case PBrowserOrId::TPBrowserParent: {
-        RefPtr<BrowserParent> browserParent =
-            BrowserParent::GetFrom(aBrowser.get_PBrowserParent());
-        dom::Element* topFrameElement = nullptr;
-        if (browserParent) {
-          topFrameElement = browserParent->GetOwnerElement();
-        }
-        aResult = new LoadContext(aSerialized, topFrameElement, attrs);
-        break;
-      }
-      case PBrowserOrId::TTabId: {
-        aResult = new LoadContext(aSerialized, nullptr, attrs);
-        break;
-      }
-      default:
-        MOZ_CRASH();
+
+    RefPtr<BrowserParent> browserParent = BrowserParent::GetFrom(aBrowser);
+    dom::Element* topFrameElement = nullptr;
+    if (browserParent) {
+      topFrameElement = browserParent->GetOwnerElement();
     }
+    aResult = new LoadContext(aSerialized, topFrameElement, attrs);
   }
 
   return nullptr;
 }
 
 void NeckoParent::ActorDestroy(ActorDestroyReason aWhy) {
   // Nothing needed here. Called right before destructor since this is a
   // non-refcounted class.
 }
 
 already_AddRefed<PHttpChannelParent> NeckoParent::AllocPHttpChannelParent(
-    const PBrowserOrId& aBrowser, const SerializedLoadContext& aSerialized,
+    PBrowserParent* aBrowser, const SerializedLoadContext& aSerialized,
     const HttpChannelCreationArgs& aOpenArgs) {
   nsCOMPtr<nsIPrincipal> requestingPrincipal =
       GetRequestingPrincipal(aOpenArgs);
 
   nsCOMPtr<nsILoadContext> loadContext;
   const char* error = CreateChannelLoadContext(
       aBrowser, Manager(), aSerialized, requestingPrincipal, loadContext);
   if (error) {
     printf_stderr(
         "NeckoParent::AllocPHttpChannelParent: "
         "FATAL error: %s: KILLING CHILD PROCESS\n",
         error);
     return nullptr;
   }
   PBOverrideStatus overrideStatus =
       PBOverrideStatusFromLoadContext(aSerialized);
-  RefPtr<HttpChannelParent> p =
-      new HttpChannelParent(aBrowser, loadContext, overrideStatus);
+  RefPtr<HttpChannelParent> p = new HttpChannelParent(
+      BrowserParent::GetFrom(aBrowser), loadContext, overrideStatus);
   return p.forget();
 }
 
 mozilla::ipc::IPCResult NeckoParent::RecvPHttpChannelConstructor(
-    PHttpChannelParent* aActor, const PBrowserOrId& aBrowser,
+    PHttpChannelParent* aActor, PBrowserParent* aBrowser,
     const SerializedLoadContext& aSerialized,
     const HttpChannelCreationArgs& aOpenArgs) {
   HttpChannelParent* p = static_cast<HttpChannelParent*>(aActor);
   if (!p->Init(aOpenArgs)) {
     return IPC_FAIL_NO_REASON(this);
   }
   return IPC_OK();
 }
@@ -282,47 +271,47 @@ bool NeckoParent::DeallocPAltDataOutputS
     PAltDataOutputStreamParent* aActor) {
   AltDataOutputStreamParent* parent =
       static_cast<AltDataOutputStreamParent*>(aActor);
   parent->Release();
   return true;
 }
 
 PFTPChannelParent* NeckoParent::AllocPFTPChannelParent(
-    const PBrowserOrId& aBrowser, const SerializedLoadContext& aSerialized,
+    PBrowserParent* aBrowser, const SerializedLoadContext& aSerialized,
     const FTPChannelCreationArgs& aOpenArgs) {
   nsCOMPtr<nsIPrincipal> requestingPrincipal =
       GetRequestingPrincipal(aOpenArgs);
 
   nsCOMPtr<nsILoadContext> loadContext;
   const char* error = CreateChannelLoadContext(
       aBrowser, Manager(), aSerialized, requestingPrincipal, loadContext);
   if (error) {
     printf_stderr(
         "NeckoParent::AllocPFTPChannelParent: "
         "FATAL error: %s: KILLING CHILD PROCESS\n",
         error);
     return nullptr;
   }
   PBOverrideStatus overrideStatus =
       PBOverrideStatusFromLoadContext(aSerialized);
-  FTPChannelParent* p =
-      new FTPChannelParent(aBrowser, loadContext, overrideStatus);
+  FTPChannelParent* p = new FTPChannelParent(BrowserParent::GetFrom(aBrowser),
+                                             loadContext, overrideStatus);
   p->AddRef();
   return p;
 }
 
 bool NeckoParent::DeallocPFTPChannelParent(PFTPChannelParent* channel) {
   FTPChannelParent* p = static_cast<FTPChannelParent*>(channel);
   p->Release();
   return true;
 }
 
 mozilla::ipc::IPCResult NeckoParent::RecvPFTPChannelConstructor(
-    PFTPChannelParent* aActor, const PBrowserOrId& aBrowser,
+    PFTPChannelParent* aActor, PBrowserParent* aBrowser,
     const SerializedLoadContext& aSerialized,
     const FTPChannelCreationArgs& aOpenArgs) {
   FTPChannelParent* p = static_cast<FTPChannelParent*>(aActor);
   if (!p->Init(aOpenArgs)) {
     return IPC_FAIL_NO_REASON(this);
   }
   return IPC_OK();
 }
@@ -357,31 +346,30 @@ PCookieServiceParent* NeckoParent::Alloc
 }
 
 bool NeckoParent::DeallocPCookieServiceParent(PCookieServiceParent* cs) {
   delete cs;
   return true;
 }
 
 PWebSocketParent* NeckoParent::AllocPWebSocketParent(
-    const PBrowserOrId& browser, const SerializedLoadContext& serialized,
+    PBrowserParent* browser, const SerializedLoadContext& serialized,
     const uint32_t& aSerial) {
   nsCOMPtr<nsILoadContext> loadContext;
   const char* error = CreateChannelLoadContext(browser, Manager(), serialized,
                                                nullptr, loadContext);
   if (error) {
     printf_stderr(
         "NeckoParent::AllocPWebSocketParent: "
         "FATAL error: %s: KILLING CHILD PROCESS\n",
         error);
     return nullptr;
   }
 
-  RefPtr<BrowserParent> browserParent =
-      BrowserParent::GetFrom(browser.get_PBrowserParent());
+  RefPtr<BrowserParent> browserParent = BrowserParent::GetFrom(browser);
   PBOverrideStatus overrideStatus = PBOverrideStatusFromLoadContext(serialized);
   WebSocketChannelParent* p = new WebSocketChannelParent(
       browserParent, loadContext, overrideStatus, aSerial);
   p->AddRef();
   return p;
 }
 
 bool NeckoParent::DeallocPWebSocketParent(PWebSocketParent* actor) {
--- a/netwerk/ipc/NeckoParent.h
+++ b/netwerk/ipc/NeckoParent.h
@@ -40,17 +40,17 @@ class NeckoParent : public PNeckoParent 
   /*
    * Creates LoadContext for parent-side of an e10s channel.
    *
    * PContentParent corresponds to the process that is requesting the load.
    *
    * Returns null if successful, or an error string if failed.
    */
   [[nodiscard]] static const char* CreateChannelLoadContext(
-      const PBrowserOrId& aBrowser, PContentParent* aContent,
+      PBrowserParent* aBrowser, PContentParent* aContent,
       const SerializedLoadContext& aSerialized,
       nsIPrincipal* aRequestingPrincipal, nsCOMPtr<nsILoadContext>& aResult);
 
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
   PCookieServiceParent* AllocPCookieServiceParent();
   virtual mozilla::ipc::IPCResult RecvPCookieServiceConstructor(
       PCookieServiceParent* aActor) override {
     return PNeckoParent::RecvPCookieServiceConstructor(aActor);
@@ -85,20 +85,20 @@ class NeckoParent : public PNeckoParent 
     PNeckoParent* mNeckoParent;
     TabId mNestedFrameId;
   };
 
  protected:
   bool mSocketProcessBridgeInited;
 
   already_AddRefed<PHttpChannelParent> AllocPHttpChannelParent(
-      const PBrowserOrId&, const SerializedLoadContext&,
+      PBrowserParent*, const SerializedLoadContext&,
       const HttpChannelCreationArgs& aOpenArgs);
   virtual mozilla::ipc::IPCResult RecvPHttpChannelConstructor(
-      PHttpChannelParent* aActor, const PBrowserOrId& aBrowser,
+      PHttpChannelParent* aActor, PBrowserParent* aBrowser,
       const SerializedLoadContext& aSerialized,
       const HttpChannelCreationArgs& aOpenArgs) override;
 
   PStunAddrsRequestParent* AllocPStunAddrsRequestParent();
   bool DeallocPStunAddrsRequestParent(PStunAddrsRequestParent* aActor);
 
   PWebrtcTCPSocketParent* AllocPWebrtcTCPSocketParent(
       const Maybe<TabId>& aTabId);
@@ -106,25 +106,25 @@ class NeckoParent : public PNeckoParent 
 
   PAltDataOutputStreamParent* AllocPAltDataOutputStreamParent(
       const nsCString& type, const int64_t& predictedSize,
       PHttpChannelParent* channel);
   bool DeallocPAltDataOutputStreamParent(PAltDataOutputStreamParent* aActor);
 
   bool DeallocPCookieServiceParent(PCookieServiceParent*);
   PFTPChannelParent* AllocPFTPChannelParent(
-      const PBrowserOrId& aBrowser, const SerializedLoadContext& aSerialized,
+      PBrowserParent* aBrowser, const SerializedLoadContext& aSerialized,
       const FTPChannelCreationArgs& aOpenArgs);
   virtual mozilla::ipc::IPCResult RecvPFTPChannelConstructor(
-      PFTPChannelParent* aActor, const PBrowserOrId& aBrowser,
+      PFTPChannelParent* aActor, PBrowserParent* aBrowser,
       const SerializedLoadContext& aSerialized,
       const FTPChannelCreationArgs& aOpenArgs) override;
   bool DeallocPFTPChannelParent(PFTPChannelParent*);
   PWebSocketParent* AllocPWebSocketParent(
-      const PBrowserOrId& browser, const SerializedLoadContext& aSerialized,
+      PBrowserParent* browser, const SerializedLoadContext& aSerialized,
       const uint32_t& aSerial);
   bool DeallocPWebSocketParent(PWebSocketParent*);
   PTCPSocketParent* AllocPTCPSocketParent(const nsString& host,
                                           const uint16_t& port);
 
   already_AddRefed<PDocumentChannelParent> AllocPDocumentChannelParent(
       const dom::MaybeDiscarded<dom::BrowsingContext>& aContext,
       const DocumentChannelCreationArgs& args);
--- a/netwerk/ipc/PNecko.ipdl
+++ b/netwerk/ipc/PNecko.ipdl
@@ -27,17 +27,16 @@ include protocol PStunAddrsRequest;
 include protocol PFileChannel;
 include protocol PClassifierDummyChannel;
 include protocol PWebrtcTCPSocket;
 include protocol PSocketProcessBridge;
 include protocol PDocumentChannel;
 
 include IPCStream;
 include NeckoChannelParams;
-include PBrowserOrId;
 include protocol PAltDataOutputStream;
 
 using mozilla::dom::MaybeDiscardedBrowsingContext from "mozilla/dom/BrowsingContext.h";
 using class IPC::SerializedLoadContext from "SerializedLoadContext.h";
 using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h";
 using refcounted class nsIInputStream from "mozilla/ipc/IPCStreamUtils.h";
 using refcounted class nsIURI from "mozilla/ipc/URIUtils.h";
 using refcounted class nsIPrincipal from "mozilla/dom/PermissionMessageUtils.h";
@@ -69,23 +68,23 @@ nested(upto inside_cpow) sync protocol P
   manages PClassifierDummyChannel;
   manages PWebrtcTCPSocket;
   manages PDocumentChannel;
 
 parent:
   async __delete__();
 
   nested(inside_cpow) async PCookieService();
-  async PHttpChannel(PBrowserOrId browser,
+  async PHttpChannel(nullable PBrowser browser,
                      SerializedLoadContext loadContext,
                      HttpChannelCreationArgs args);
-  async PFTPChannel(PBrowserOrId browser, SerializedLoadContext loadContext,
+  async PFTPChannel(nullable PBrowser browser, SerializedLoadContext loadContext,
                     FTPChannelCreationArgs args);
 
-  async PWebSocket(PBrowserOrId browser, SerializedLoadContext loadContext,
+  async PWebSocket(nullable PBrowser browser, SerializedLoadContext loadContext,
                    uint32_t aSerialID);
   async PTCPServerSocket(uint16_t localPort, uint16_t backlog, bool useArrayBuffers);
   async PUDPSocket(nsIPrincipal principal, nsCString filter);
 
   async PDNSRequest(nsCString hostName, nsCString trrServer, uint16_t type,
                     OriginAttributes originAttributes, uint32_t flags);
 
   async PDocumentChannel(MaybeDiscardedBrowsingContext browsingContext,
--- a/netwerk/ipc/PSocketProcess.ipdl
+++ b/netwerk/ipc/PSocketProcess.ipdl
@@ -14,21 +14,21 @@ include protocol PChildToParentStream;
 include protocol PParentToChildStream;
 include protocol PInputChannelThrottleQueue;
 include protocol PBackground;
 include protocol PAltService;
 include protocol PAltSvcTransaction;
 
 include MemoryReportTypes;
 include NeckoChannelParams;
-include PBrowserOrId;
 include PrefsTypes;
 include PSMIPCTypes;
 
 using mozilla::dom::NativeThreadId from "mozilla/dom/TabMessageUtils.h";
+using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h";
 using mozilla::Telemetry::HistogramAccumulation from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::KeyedHistogramAccumulation from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::ScalarAction from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::KeyedScalarAction from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::ChildEventData from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::DiscardedData from "mozilla/TelemetryComms.h";
 using base::ProcessId from "base/process.h";
 using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h";
--- a/netwerk/ipc/ParentProcessDocumentChannel.cpp
+++ b/netwerk/ipc/ParentProcessDocumentChannel.cpp
@@ -40,18 +40,16 @@ ParentProcessDocumentChannel::RedirectTo
   nsCOMPtr<nsIChannel> channel = mDocumentLoadListener->GetChannel();
   channel->SetLoadFlags(aLoadFlags);
   channel->SetNotificationCallbacks(mCallbacks);
 
   if (mLoadGroup) {
     channel->SetLoadGroup(mLoadGroup);
   }
 
-  mLastVisitInfo = mDocumentLoadListener->LastVisitInfo();
-  mRedirects = mDocumentLoadListener->Redirects().Clone();
   mStreamFilterEndpoints = std::move(aStreamFilterEndpoints);
 
   RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise> p =
       mPromise.Ensure(__func__);
 
   nsresult rv =
       gHttpHandler->AsyncOnChannelRedirect(this, channel, aRedirectFlags);
   if (NS_FAILED(rv)) {
--- a/netwerk/protocol/ftp/FTPChannelParent.cpp
+++ b/netwerk/protocol/ftp/FTPChannelParent.cpp
@@ -31,36 +31,32 @@ using namespace mozilla::dom;
 using namespace mozilla::ipc;
 
 #undef LOG
 #define LOG(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Debug, args)
 
 namespace mozilla {
 namespace net {
 
-FTPChannelParent::FTPChannelParent(const PBrowserOrId& aIframeEmbedding,
+FTPChannelParent::FTPChannelParent(dom::BrowserParent* aIframeEmbedding,
                                    nsILoadContext* aLoadContext,
                                    PBOverrideStatus aOverrideStatus)
     : mIPCClosed(false),
       mLoadContext(aLoadContext),
       mPBOverride(aOverrideStatus),
       mStatus(NS_OK),
       mDivertingFromChild(false),
       mDivertedOnStartRequest(false),
       mSuspendedForDiversion(false),
+      mBrowserParent(aIframeEmbedding),
       mUseUTF8(false) {
   nsIProtocolHandler* handler;
   CallGetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "ftp", &handler);
   MOZ_ASSERT(handler, "no ftp handler");
 
-  if (aIframeEmbedding.type() == PBrowserOrId::TPBrowserParent) {
-    mBrowserParent =
-        static_cast<dom::BrowserParent*>(aIframeEmbedding.get_PBrowserParent());
-  }
-
   mEventQ = new ChannelEventQueue(static_cast<nsIParentChannel*>(this));
 }
 
 FTPChannelParent::~FTPChannelParent() { gFtpHandler->Release(); }
 
 void FTPChannelParent::ActorDestroy(ActorDestroyReason why) {
   // We may still have refcount>0 if the channel hasn't called OnStopRequest
   // yet, but we must not send any more msgs to child.
--- a/netwerk/protocol/ftp/FTPChannelParent.h
+++ b/netwerk/protocol/ftp/FTPChannelParent.h
@@ -17,17 +17,16 @@
 #include "nsIFTPChannelParentInternal.h"
 
 class nsILoadContext;
 
 namespace mozilla {
 
 namespace dom {
 class BrowserParent;
-class PBrowserOrId;
 }  // namespace dom
 
 namespace net {
 class ChannelEventQueue;
 
 class FTPChannelParent final : public PFTPChannelParent,
                                public nsIParentChannel,
                                public nsIInterfaceRequestor,
@@ -37,17 +36,17 @@ class FTPChannelParent final : public PF
  public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSISTREAMLISTENER
   NS_DECL_NSIPARENTCHANNEL
   NS_DECL_NSIINTERFACEREQUESTOR
   NS_DECL_NSICHANNELEVENTSINK
 
-  FTPChannelParent(const dom::PBrowserOrId& aIframeEmbedding,
+  FTPChannelParent(dom::BrowserParent* aIframeEmbedding,
                    nsILoadContext* aLoadContext,
                    PBOverrideStatus aOverrideStatus);
 
   bool Init(const FTPChannelCreationArgs& aOpenArgs);
 
   // ADivertableParentChannel functions.
   void DivertTo(nsIStreamListener* aListener) override;
   nsresult SuspendForDiversion() override;
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -2054,20 +2054,18 @@ HttpChannelChild::ConnectParent(uint32_t
   HttpBaseChannel::SetDocshellUserAgentOverride();
 
   // This must happen before the constructor message is sent. Otherwise messages
   // from the parent could arrive quickly and be delivered to the wrong event
   // target.
   SetEventTarget();
 
   HttpChannelConnectArgs connectArgs(registrarId, mShouldParentIntercept);
-  PBrowserOrId browser = static_cast<ContentChild*>(gNeckoChild->Manager())
-                             ->GetBrowserOrId(browserChild);
   if (!gNeckoChild->SendPHttpChannelConstructor(
-          this, browser, IPC::SerializedLoadContext(this), connectArgs)) {
+          this, browserChild, IPC::SerializedLoadContext(this), connectArgs)) {
     return NS_ERROR_FAILURE;
   }
 
   {
     MutexAutoLock lock(mBgChildMutex);
 
     MOZ_ASSERT(!mBgChild);
     MOZ_ASSERT(!mBgInitFailCallback);
@@ -2800,19 +2798,18 @@ nsresult HttpChannelChild::ContinueAsync
   openArgs.navigationStartTimeStamp() = navigationStartTimeStamp;
   openArgs.hasNonEmptySandboxingFlag() = GetHasNonEmptySandboxingFlag();
 
   // This must happen before the constructor message is sent. Otherwise messages
   // from the parent could arrive quickly and be delivered to the wrong event
   // target.
   SetEventTarget();
 
-  PBrowserOrId browser = cc->GetBrowserOrId(browserChild);
   if (!gNeckoChild->SendPHttpChannelConstructor(
-          this, browser, IPC::SerializedLoadContext(this), openArgs)) {
+          this, browserChild, IPC::SerializedLoadContext(this), openArgs)) {
     return NS_ERROR_FAILURE;
   }
 
   {
     MutexAutoLock lock(mBgChildMutex);
 
     MOZ_RELEASE_ASSERT(gSocketTransportService);
 
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -58,21 +58,20 @@
 
 using mozilla::BasePrincipal;
 using namespace mozilla::dom;
 using namespace mozilla::ipc;
 
 namespace mozilla {
 namespace net {
 
-HttpChannelParent::HttpChannelParent(const PBrowserOrId& iframeEmbedding,
+HttpChannelParent::HttpChannelParent(dom::BrowserParent* iframeEmbedding,
                                      nsILoadContext* aLoadContext,
                                      PBOverrideStatus aOverrideStatus)
     : mLoadContext(aLoadContext),
-      mNestedFrameId(0),
       mIPCClosed(false),
       mPBOverride(aOverrideStatus),
       mStatus(NS_OK),
       mIgnoreProgress(false),
       mSentRedirect1BeginFailed(false),
       mReceivedRedirect2Verify(false),
       mHasSuspendedByBackPressure(false),
       mPendingDiversion(false),
@@ -89,22 +88,17 @@ HttpChannelParent::HttpChannelParent(con
 
   // Ensure gHttpHandler is initialized: we need the atom table up and running.
   nsCOMPtr<nsIHttpProtocolHandler> dummyInitializer =
       do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http");
 
   MOZ_ASSERT(gHttpHandler);
   mHttpHandler = gHttpHandler;
 
-  if (iframeEmbedding.type() == PBrowserOrId::TPBrowserParent) {
-    mBrowserParent =
-        static_cast<dom::BrowserParent*>(iframeEmbedding.get_PBrowserParent());
-  } else {
-    mNestedFrameId = iframeEmbedding.get_TabId();
-  }
+  mBrowserParent = iframeEmbedding;
 
   mSendWindowSize = gHttpHandler->SendWindowSize();
 
   mEventQ =
       new ChannelEventQueue(static_cast<nsIParentRedirectingChannel*>(this));
 }
 
 HttpChannelParent::~HttpChannelParent() {
@@ -2453,17 +2447,17 @@ nsresult HttpChannelParent::OpenAlternat
   }
   return rv;
 }
 
 NS_IMETHODIMP
 HttpChannelParent::GetAuthPrompt(uint32_t aPromptReason, const nsIID& iid,
                                  void** aResult) {
   nsCOMPtr<nsIAuthPrompt2> prompt =
-      new NeckoParent::NestedFrameAuthPrompt(Manager(), mNestedFrameId);
+      new NeckoParent::NestedFrameAuthPrompt(Manager(), TabId(0));
   prompt.forget(aResult);
   return NS_OK;
 }
 
 void HttpChannelParent::UpdateAndSerializeSecurityInfo(
     nsACString& aSerializedSecurityInfoOut) {
   nsCOMPtr<nsISupports> secInfoSupp;
   mChannel->GetSecurityInfo(getter_AddRefs(secInfoSupp));
--- a/netwerk/protocol/http/HttpChannelParent.h
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -32,17 +32,16 @@ class nsICacheEntry;
       0xbd, 0x9f, 0x89, 0x74, 0xd7, 0xf0, 0x58, 0xeb \
     }                                                \
   }
 
 namespace mozilla {
 
 namespace dom {
 class BrowserParent;
-class PBrowserOrId;
 }  // namespace dom
 
 namespace net {
 
 class HttpBackgroundChannelParent;
 class ParentChannelListener;
 class ChannelEventQueue;
 
@@ -76,17 +75,17 @@ class HttpChannelParent final : public n
   NS_DECL_NSIDEPRECATIONWARNER
   NS_DECL_NSIASYNCVERIFYREDIRECTREADYCALLBACK
   NS_DECL_NSICHANNELEVENTSINK
   NS_DECL_NSIREDIRECTRESULTLISTENER
   NS_DECL_NSIMULTIPARTCHANNELLISTENER
 
   NS_DECLARE_STATIC_IID_ACCESSOR(HTTP_CHANNEL_PARENT_IID)
 
-  HttpChannelParent(const dom::PBrowserOrId& iframeEmbedding,
+  HttpChannelParent(dom::BrowserParent* iframeEmbedding,
                     nsILoadContext* aLoadContext, PBOverrideStatus aStatus);
 
   [[nodiscard]] bool Init(const HttpChannelCreationArgs& aOpenArgs);
 
   // ADivertableParentChannel functions.
   void DivertTo(nsIStreamListener* aListener) override;
   [[nodiscard]] nsresult SuspendForDiversion() override;
   [[nodiscard]] nsresult SuspendMessageDiversion() override;
@@ -297,18 +296,16 @@ class HttpChannelParent final : public n
 
   RefPtr<ChannelEventQueue> mEventQ;
 
   RefPtr<HttpBackgroundChannelParent> mBgParent;
 
   MozPromiseHolder<GenericNonExclusivePromise> mPromise;
   MozPromiseRequestHolder<GenericNonExclusivePromise> mRequest;
 
-  dom::TabId mNestedFrameId;
-
   // To calculate the delay caused by the e10s back-pressure suspension
   TimeStamp mResumedTimestamp;
 
   Atomic<bool> mIPCClosed;  // PHttpChannel actor has been Closed()
 
   // Corresponding redirect channel registrar Id. 0 means redirection is not
   // started.
   uint32_t mRedirectChannelId = 0;
--- a/taskcluster/ci/source-test/file-metadata.yml
+++ b/taskcluster/ci/source-test/file-metadata.yml
@@ -24,16 +24,17 @@ bugzilla-components:
         symbol: Bugzilla
     index:
         product: source
         job-name: source-bugzilla-info
     run:
         mach: file-info bugzilla-automation /builds/worker/artifacts
     worker:
         max-run-time: 2700
+    run-on-projects: ['mozilla-central']
     when:
         files-changed:
             - "**"
 
 test-info-fission:
     description: Generate test manifest metadata for tests disabled on fission
     treeherder:
         symbol: test-info(fission)
--- a/taskcluster/ci/webrender/kind.yml
+++ b/taskcluster/ci/webrender/kind.yml
@@ -310,17 +310,17 @@ jobs:
         when:
             files-changed:
                 - 'build.gradle'
                 - 'gfx/wr/**'
                 - 'taskcluster/scripts/misc/wrench-android-build.sh'
 
     android-emulator-debug:
         description: Run debug wrench reftests on Android emulator
-        worker-type: terraform-packet/gecko-t-linux  # privileged:true doesn't work on b-linux
+        worker-type: t-linux-metal  # privileged:true doesn't work on b-linux
         worker:
             max-run-time: 5400
             docker-image: {in-tree: ubuntu1804-test}
             privileged: true  # for access to /dev/kvm for hw accel in the emulator
             artifacts:
                 - type: directory
                   name: public/build/logs
                   path: /builds/worker/workspace/build/logs
@@ -352,17 +352,17 @@ jobs:
         when:
             files-changed:
                 - 'gfx/wr/**'
                 - 'testing/mozharness/scripts/android_wrench.py'
                 - 'testing/mozharness/configs/android/wrench.py'
 
     android-emulator-release:
         description: Run release wrench reftests on Android emulator
-        worker-type: terraform-packet/gecko-t-linux  # privileged:true doesn't work on b-linux
+        worker-type: t-linux-metal  # privileged:true doesn't work on b-linux
         worker:
             max-run-time: 5400
             docker-image: {in-tree: ubuntu1804-test}
             privileged: true  # for access to /dev/kvm for hw accel in the emulator
             artifacts:
                 - type: directory
                   name: public/build/logs
                   path: /builds/worker/workspace/build/logs
--- a/taskcluster/taskgraph/transforms/task.py
+++ b/taskcluster/taskgraph/transforms/task.py
@@ -18,17 +18,16 @@ from copy import deepcopy
 import six
 from six import text_type
 
 import attr
 
 from mozbuild.util import memoize
 from taskgraph.util.attributes import TRUNK_PROJECTS
 from taskgraph.util.hash import hash_path
-from taskgraph.util.taskcluster import get_root_url
 from taskgraph.util.treeherder import split_symbol
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.util.keyed_by import evaluate_keyed_by
 from taskgraph.util.schema import (
     validate_schema,
     Schema,
     optionally_keyed_by,
     resolve_keyed_by,
@@ -1995,20 +1994,16 @@ def build_task(config, tasks):
             'generic-worker',
             'docker-worker',
         ):
             payload = task_def.get('payload')
             if payload:
                 env = payload.setdefault('env', {})
                 env['MOZ_AUTOMATION'] = '1'
 
-                # Set TASKCLUSTER_ROOT_URL on workers that don't set it
-                if provisioner_id == 'terraform-packet':
-                    env['TASKCLUSTER_ROOT_URL'] = get_root_url(False)
-
         yield {
             'label': task['label'],
             'task': task_def,
             'dependencies': task.get('dependencies', {}),
             'soft-dependencies': task.get('soft-dependencies', []),
             'attributes': attributes,
             'optimization': task.get('optimization', None),
             'release-artifacts': task.get('release-artifacts', []),
--- a/taskcluster/taskgraph/transforms/tests.py
+++ b/taskcluster/taskgraph/transforms/tests.py
@@ -1507,20 +1507,17 @@ def set_worker_type(config, tasks):
             else:
                 task['worker-type'] = 't-bitbar-gw-perf-g5'
         elif test_platform.startswith('android-hw-p2'):
             if task['suite'] != 'raptor':
                 task['worker-type'] = 't-bitbar-gw-unit-p2'
             else:
                 task['worker-type'] = 't-bitbar-gw-perf-p2'
         elif test_platform.startswith('android-em-7.0-x86'):
-            if task['suite'].startswith('web-platform-tests'):
-                task['worker-type'] = 't-linux-metal'
-            else:
-                task['worker-type'] = 'terraform-packet/gecko-t-linux'
+            task['worker-type'] = 't-linux-metal'
         elif test_platform.startswith('linux') or test_platform.startswith('android'):
             if task.get('suite', '') in ['talos', 'raptor'] and \
                  not task['build-platform'].startswith('linux64-ccov'):
                 task['worker-type'] = 't-linux-talos'
             else:
                 task['worker-type'] = LINUX_WORKER_TYPES[task['instance-size']]
         else:
             raise Exception("unknown test_platform {}".format(test_platform))
--- a/taskcluster/taskgraph/util/workertypes.py
+++ b/taskcluster/taskgraph/util/workertypes.py
@@ -14,17 +14,16 @@ WORKER_TYPES = {
     'gce/gecko-2-b-linux': ('docker-worker', 'linux'),
     'gce/gecko-3-b-linux': ('docker-worker', 'linux'),
     'invalid/invalid': ('invalid', None),
     'invalid/always-optimized': ('always-optimized', None),
     'scriptworker-prov-v1/pushapk-v1': ('push-apk', None),
     "scriptworker-prov-v1/signing-linux-v1": ('scriptworker-signing', None),
     "scriptworker-k8s/gecko-3-shipit": ('shipit', None),
     "scriptworker-k8s/gecko-1-shipit": ('shipit', None),
-    'terraform-packet/gecko-t-linux': ('docker-worker', 'linux'),
 }
 
 
 @memoize
 def _get(graph_config, alias, level, release_level):
     """Get the configuration for this worker_type alias: {provisioner,
     worker-type, implementation, os}"""
     level = str(level)
--- a/testing/specialpowers/content/SpecialPowersParent.jsm
+++ b/testing/specialpowers/content/SpecialPowersParent.jsm
@@ -90,17 +90,17 @@ async function createWindowlessBrowser({
   }
 
   let chromeShell = windowlessBrowser.docShell.QueryInterface(
     Ci.nsIWebNavigation
   );
 
   const system = Services.scriptSecurityManager.getSystemPrincipal();
   chromeShell.createAboutBlankContentViewer(system, system);
-  chromeShell.useGlobalHistory = false;
+  windowlessBrowser.browsingContext.useGlobalHistory = false;
   chromeShell.loadURI("chrome://extensions/content/dummy.xhtml", {
     triggeringPrincipal: system,
   });
 
   await promiseObserved(
     "chrome-document-global-created",
     win => win.document == chromeShell.document
   );
--- a/testing/web-platform/meta/editing/run/inserttext.html.ini
+++ b/testing/web-platform/meta/editing/run/inserttext.html.ini
@@ -1,347 +1,8 @@
-[inserttext.html]
-  prefs: [editor.use_div_for_default_newlines:true]
-  [[["inserttext","\\t"\]\] "foo[\]bar" compare innerHTML]
-    expected: FAIL
-
-  [[["defaultparagraphseparator","div"\],["inserttext","\\n"\]\] "foo[\]bar" compare innerHTML]
-    expected: FAIL
-
-  [[["defaultparagraphseparator","p"\],["inserttext","\\n"\]\] "foo[\]bar" compare innerHTML]
-    expected: FAIL
-
-  [[["defaultparagraphseparator","div"\],["inserttext","abc\\ndef"\]\] "foo[\]bar" compare innerHTML]
-    expected: FAIL
-
-  [[["defaultparagraphseparator","p"\],["inserttext","abc\\ndef"\]\] "foo[\]bar" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "foo[\] &nbsp;bar" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "foo&nbsp; [\]bar" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "foo&nbsp;&nbsp;[\]bar" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "foo[\]&nbsp;&nbsp;bar" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "foo [\]&nbsp;        bar" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "foo  [\]bar" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "foo[\]" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "foo{}" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "foo&nbsp;[\]" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "foo&nbsp;{}" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "foo&nbsp;&nbsp;[\]" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "foo&nbsp;&nbsp;{}" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "foo[\] " compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] " foo   [\]   " compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] " [\]foo" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "   [\]   foo " compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "<span> </span>[\]foo" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] " <span> </span>[\]foo" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "{}<br>" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "<p>{}<br>" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "<p>foo[\]<p>bar" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "<p>foo&nbsp;[\]<p>bar" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "<p>foo[\]<p>&nbsp;bar" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "<div style=white-space:pre-line>foo[\]</div>" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "<div style=white-space:pre-line>foo&nbsp;[\]</div>" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "<div style=white-space:pre-line> foo   [\]   </div>" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "<div style=white-space:nowrap>foo[\]</div>" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "<div style=white-space:nowrap>foo&nbsp;[\]</div>" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "<div style=white-space:nowrap> foo   [\]   </div>" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "http://a[\]" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "ftp://a[\]" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "quasit://a[\]" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] ".x-++-.://a[\]" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "(http://a)[\]" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "&lt;http://a>[\]" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "http://a![\]" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "!\\"#$%&amp;'()*+,-./:;&lt;=>?^_`|~http://a!\\"#$%&amp;'()*+,-./:;&lt;=>?^_`|~[\]" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "http://a!\\"'(),-.:;&lt;>`[\]" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "http://a#$%&amp;*+/=?^_|~[\]" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "mailto:a[\]" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "a@b[\]" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "a@[\]" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "@b[\]" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "#@x[\]" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "a@.[\]" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "!\\"#$%&amp;'()*+,-./:;&lt;=>?^_`|~a@b!\\"#$%&amp;'()*+,-./:;&lt;=>?^_`|~[\]" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "<b>a@b</b>{}" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "<b>a</b><i>@</i><u>b</u>{}" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "a@b<b>[\]c</b>" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext","\\t"\]\] "http://a[\]" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext","\\f"\]\] "http://a[\]" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "http://a[\]" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext","   "\]\] "foo[\]" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext","a"\]\] "{}<br>" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext","a"\]\] "<p>{}<br>" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext","a"\]\] "<p><span>{}<br></span>" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext","a"\]\] "<p>foo{<span style=color:#aBcDeF>bar</span>}baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","true"\],["inserttext","a"\]\] "<p>[foo<span style=color:#aBcDeF>bar\]</span>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","false"\],["inserttext","a"\]\] "<p>[foo<span style=color:#aBcDeF>bar\]</span>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","true"\],["inserttext","a"\]\] "<p>{foo<span style=color:#aBcDeF>bar}</span>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","false"\],["inserttext","a"\]\] "<p>{foo<span style=color:#aBcDeF>bar}</span>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","true"\],["inserttext","a"\]\] "<p>foo<span style=color:#aBcDeF>[bar</span><span style=color:#fEdCbA>baz\]</span>quz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","false"\],["inserttext","a"\]\] "<p>foo<span style=color:#aBcDeF>[bar</span><span style=color:#fEdCbA>baz\]</span>quz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","true"\],["inserttext","a"\]\] "[foo<b>bar\]</b>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","false"\],["inserttext","a"\]\] "[foo<b>bar\]</b>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","true"\],["inserttext","a"\]\] "[foo<i>bar\]</i>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","false"\],["inserttext","a"\]\] "[foo<i>bar\]</i>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","true"\],["inserttext","a"\]\] "[foo<s>bar\]</s>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","false"\],["inserttext","a"\]\] "[foo<s>bar\]</s>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","true"\],["inserttext","a"\]\] "[foo<sub>bar\]</sub>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","false"\],["inserttext","a"\]\] "[foo<sub>bar\]</sub>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","true"\],["inserttext","a"\]\] "[foo<sup>bar\]</sup>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","false"\],["inserttext","a"\]\] "[foo<sup>bar\]</sup>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","true"\],["inserttext","a"\]\] "[foo<u>bar\]</u>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","false"\],["inserttext","a"\]\] "[foo<u>bar\]</u>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext","a"\]\] "[foo<a href=http://www.google.com>bar\]</a>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","true"\],["inserttext","a"\]\] "[foo<font face=sans-serif>bar\]</font>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","false"\],["inserttext","a"\]\] "[foo<font face=sans-serif>bar\]</font>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","true"\],["inserttext","a"\]\] "[foo<font size=4>bar\]</font>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","false"\],["inserttext","a"\]\] "[foo<font size=4>bar\]</font>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","true"\],["inserttext","a"\]\] "[foo<font color=#0000FF>bar\]</font>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","false"\],["inserttext","a"\]\] "[foo<font color=#0000FF>bar\]</font>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","true"\],["inserttext","a"\]\] "[foo<span style=background-color:#00FFFF>bar\]</span>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","false"\],["inserttext","a"\]\] "[foo<span style=background-color:#00FFFF>bar\]</span>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","true"\],["inserttext","a"\]\] "[foo<a href=http://www.google.com><font color=blue>bar\]</font></a>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","false"\],["inserttext","a"\]\] "[foo<a href=http://www.google.com><font color=blue>bar\]</font></a>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","true"\],["inserttext","a"\]\] "[foo<font color=blue><a href=http://www.google.com>bar\]</a></font>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","false"\],["inserttext","a"\]\] "[foo<font color=blue><a href=http://www.google.com>bar\]</a></font>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","true"\],["inserttext","a"\]\] "[foo<a href=http://www.google.com><font color=brown>bar\]</font></a>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","false"\],["inserttext","a"\]\] "[foo<a href=http://www.google.com><font color=brown>bar\]</font></a>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","true"\],["inserttext","a"\]\] "[foo<font color=brown><a href=http://www.google.com>bar\]</a></font>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","false"\],["inserttext","a"\]\] "[foo<font color=brown><a href=http://www.google.com>bar\]</a></font>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","true"\],["inserttext","a"\]\] "[foo<a href=http://www.google.com><font color=black>bar\]</font></a>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","false"\],["inserttext","a"\]\] "[foo<a href=http://www.google.com><font color=black>bar\]</font></a>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","true"\],["inserttext","a"\]\] "[foo<a href=http://www.google.com><u>bar\]</u></a>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","false"\],["inserttext","a"\]\] "[foo<a href=http://www.google.com><u>bar\]</u></a>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","true"\],["inserttext","a"\]\] "[foo<u><a href=http://www.google.com>bar\]</a></u>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","false"\],["inserttext","a"\]\] "[foo<u><a href=http://www.google.com>bar\]</a></u>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","true"\],["inserttext","a"\]\] "[foo<sub><font size=2>bar\]</font></sub>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","false"\],["inserttext","a"\]\] "[foo<sub><font size=2>bar\]</font></sub>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","true"\],["inserttext","a"\]\] "[foo<font size=2><sub>bar\]</sub></font>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","false"\],["inserttext","a"\]\] "[foo<font size=2><sub>bar\]</sub></font>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","true"\],["inserttext","a"\]\] "[foo<sub><font size=3>bar\]</font></sub>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","false"\],["inserttext","a"\]\] "[foo<sub><font size=3>bar\]</font></sub>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","true"\],["inserttext","a"\]\] "[foo<font size=3><sub>bar\]</sub></font>baz" compare innerHTML]
-    expected: FAIL
-
-  [[["stylewithcss","false"\],["inserttext","a"\]\] "[foo<font size=3><sub>bar\]</sub></font>baz" compare innerHTML]
-    expected: FAIL
-
-  [inserttext - HTML editing conformance tests]
-    expected: FAIL
-
-
 [inserttext.html?2001-last]
   [[["stylewithcss","true"\],["inserttext","a"\]\] "[foo<font color=blue><a href=http://www.google.com>bar\]</a></font>baz" compare innerHTML]
     expected: FAIL
 
   [[["stylewithcss","false"\],["inserttext","a"\]\] "[foo<font color=blue><a href=http://www.google.com>bar\]</a></font>baz" compare innerHTML]
     expected: FAIL
 
   [[["stylewithcss","true"\],["inserttext","a"\]\] "[foo<a href=http://www.google.com><font color=brown>bar\]</font></a>baz" compare innerHTML]
@@ -464,22 +125,16 @@
     expected: FAIL
 
   [[["inserttext"," "\]\] "<span> </span>[\]foo" compare innerHTML]
     expected: FAIL
 
   [[["inserttext"," "\]\] " <span> </span>[\]foo" compare innerHTML]
     expected: FAIL
 
-  [[["inserttext"," "\]\] "{}<br>" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext"," "\]\] "<p>{}<br>" compare innerHTML]
-    expected: FAIL
-
   [[["inserttext"," "\]\] "<p>foo[\]<p>bar" compare innerHTML]
     expected: FAIL
 
   [[["inserttext"," "\]\] "<p>foo&nbsp;[\]<p>bar" compare innerHTML]
     expected: FAIL
 
   [[["inserttext"," "\]\] "<p>foo[\]<p>&nbsp;bar" compare innerHTML]
     expected: FAIL
@@ -574,25 +229,16 @@
     expected: FAIL
 
   [[["inserttext","   "\]\] "foo[\]" compare innerHTML]
     expected: FAIL
 
   [[["defaultparagraphseparator","div"\],["inserttext","a"\]\] "<p>fo[o<p>b\]ar" queryCommandValue("defaultparagraphseparator") before]
     expected: FAIL
 
-  [[["inserttext","a"\]\] "{}<br>" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext","a"\]\] "<p>{}<br>" compare innerHTML]
-    expected: FAIL
-
-  [[["inserttext","a"\]\] "<p><span>{}<br></span>" compare innerHTML]
-    expected: FAIL
-
   [[["inserttext","a"\]\] "<p>foo{<span style=color:#aBcDeF>bar</span>}baz" compare innerHTML]
     expected: FAIL
 
   [[["stylewithcss","true"\],["inserttext","a"\]\] "<p>[foo<span style=color:#aBcDeF>bar\]</span>baz" compare innerHTML]
     expected: FAIL
 
   [[["stylewithcss","false"\],["inserttext","a"\]\] "<p>[foo<span style=color:#aBcDeF>bar\]</span>baz" compare innerHTML]
     expected: FAIL
--- a/toolkit/components/browser/nsWebBrowser.cpp
+++ b/toolkit/components/browser/nsWebBrowser.cpp
@@ -92,20 +92,18 @@ nsIWidget* nsWebBrowser::EnsureWidget() 
                     nullptr);
 
   return mInternalWidget;
 }
 
 /* static */
 already_AddRefed<nsWebBrowser> nsWebBrowser::Create(
     nsIWebBrowserChrome* aContainerWindow, nsIWidget* aParentWidget,
-    const OriginAttributes& aOriginAttributes,
     dom::BrowsingContext* aBrowsingContext,
-    dom::WindowGlobalChild* aInitialWindowChild,
-    bool aDisableHistory /* = false */) {
+    dom::WindowGlobalChild* aInitialWindowChild) {
   MOZ_ASSERT_IF(aInitialWindowChild,
                 aInitialWindowChild->BrowsingContext() == aBrowsingContext);
 
   RefPtr<nsWebBrowser> browser = new nsWebBrowser(
       aBrowsingContext->IsContent() ? typeContentWrapper : typeChromeWrapper);
 
   // nsWebBrowser::SetContainer also calls nsWebBrowser::EnsureDocShellTreeOwner
   NS_ENSURE_SUCCESS(browser->SetContainerWindow(aContainerWindow), nullptr);
@@ -119,17 +117,16 @@ already_AddRefed<nsWebBrowser> nsWebBrow
   uint64_t outerWindowId =
       aInitialWindowChild ? aInitialWindowChild->OuterWindowId() : 0;
 
   RefPtr<nsDocShell> docShell =
       nsDocShell::Create(aBrowsingContext, outerWindowId);
   if (NS_WARN_IF(!docShell)) {
     return nullptr;
   }
-  MOZ_ASSERT(aBrowsingContext->OriginAttributesRef() == aOriginAttributes);
   browser->SetDocShell(docShell);
 
   // get the system default window background colour
   LookAndFeel::GetColor(LookAndFeel::ColorID::WindowBackground,
                         &browser->mBackgroundColor);
 
   // HACK ALERT - this registration registers the nsDocShellTreeOwner as a
   // nsIWebBrowserListener so it can setup its MouseListener in one of the
@@ -154,23 +151,16 @@ already_AddRefed<nsWebBrowser> nsWebBrow
 
   // If the webbrowser is a content docshell item then we won't hear any
   // events from subframes. To solve that we install our own chrome event
   // handler that always gets called (even for subframes) for any bubbling
   // event.
 
   docShell->InitSessionHistory();
 
-  if (XRE_IsParentProcess() && !aDisableHistory) {
-    // Hook up global history. Do not fail if we can't - just warn.
-    DebugOnly<nsresult> rv =
-        browser->EnableGlobalHistory(browser->mShouldEnableHistory);
-    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EnableGlobalHistory() failed");
-  }
-
   NS_ENSURE_SUCCESS(docShellAsWin->Create(), nullptr);
 
   // Hook into the OnSecurityChange() notification for lock/unlock icon
   // updates
   // this works because the implementation of nsISecureBrowserUI
   // (nsSecureBrowserUIImpl) calls docShell->SetSecurityUI(this);
   nsCOMPtr<nsISecureBrowserUI> securityUI =
       do_CreateInstance(NS_SECURE_BROWSER_UI_CONTRACTID);
@@ -261,23 +251,16 @@ nsWebBrowser::GetInterface(const nsIID& 
   return NS_NOINTERFACE;
 }
 
 //*****************************************************************************
 // nsWebBrowser::nsIWebBrowser
 //*****************************************************************************
 
 NS_IMETHODIMP
-nsWebBrowser::EnableGlobalHistory(bool aEnable) {
-  NS_ENSURE_STATE(mDocShell);
-
-  return mDocShell->SetUseGlobalHistory(aEnable);
-}
-
-NS_IMETHODIMP
 nsWebBrowser::GetContainerWindow(nsIWebBrowserChrome** aTopWindow) {
   NS_ENSURE_ARG_POINTER(aTopWindow);
 
   nsCOMPtr<nsIWebBrowserChrome> top;
   if (mDocShellTreeOwner) {
     top = mDocShellTreeOwner->GetWebBrowserChrome();
   }
 
--- a/toolkit/components/browser/nsWebBrowser.h
+++ b/toolkit/components/browser/nsWebBrowser.h
@@ -103,29 +103,26 @@ class nsWebBrowser final : public nsIWeb
 
   void SetAllowDNSPrefetch(bool aAllowPrefetch);
   void FocusActivate();
   void FocusDeactivate();
   void SetWillChangeProcess();
 
   static already_AddRefed<nsWebBrowser> Create(
       nsIWebBrowserChrome* aContainerWindow, nsIWidget* aParentWidget,
-      const mozilla::OriginAttributes& aOriginAttributes,
       mozilla::dom::BrowsingContext* aBrowsingContext,
-      mozilla::dom::WindowGlobalChild* aInitialWindowChild,
-      bool aDisableHistory = false);
+      mozilla::dom::WindowGlobalChild* aInitialWindowChild);
 
  protected:
   virtual ~nsWebBrowser();
   NS_IMETHOD InternalDestroy();
 
   // XXXbz why are these NS_IMETHOD?  They're not interface methods!
   NS_IMETHOD SetDocShell(nsIDocShell* aDocShell);
   NS_IMETHOD EnsureDocShellTreeOwner();
-  NS_IMETHOD EnableGlobalHistory(bool aEnable);
 
   nsIWidget* EnsureWidget();
 
   // nsIWidgetListener methods for WidgetListenerDelegate.
   MOZ_CAN_RUN_SCRIPT void WindowActivated();
   MOZ_CAN_RUN_SCRIPT void WindowDeactivated();
   MOZ_CAN_RUN_SCRIPT bool PaintWindow(nsIWidget* aWidget,
                                       mozilla::LayoutDeviceIntRegion aRegion);
--- a/toolkit/components/extensions/ExtensionParent.jsm
+++ b/toolkit/components/extensions/ExtensionParent.jsm
@@ -1182,17 +1182,17 @@ class HiddenXULWindow {
     chromeShell.QueryInterface(Ci.nsIWebNavigation);
 
     if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
       let attrs = chromeShell.getOriginAttributes();
       attrs.privateBrowsingId = 1;
       chromeShell.setOriginAttributes(attrs);
     }
 
-    chromeShell.useGlobalHistory = false;
+    windowlessBrowser.browsingContext.useGlobalHistory = false;
     chromeShell.loadURI("chrome://extensions/content/dummy.xhtml", {
       triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
     });
 
     await promiseObserved(
       "chrome-document-global-created",
       win => win.document == chromeShell.document
     );
--- a/toolkit/components/extensions/ExtensionXPCShellUtils.jsm
+++ b/toolkit/components/extensions/ExtensionXPCShellUtils.jsm
@@ -202,17 +202,17 @@ class ContentPage {
 
     let system = Services.scriptSecurityManager.getSystemPrincipal();
 
     let chromeShell = this.windowlessBrowser.docShell.QueryInterface(
       Ci.nsIWebNavigation
     );
 
     chromeShell.createAboutBlankContentViewer(system, system);
-    chromeShell.useGlobalHistory = false;
+    this.windowlessBrowser.browsingContext.useGlobalHistory = false;
     let loadURIOptions = {
       triggeringPrincipal: system,
     };
     chromeShell.loadURI(
       "chrome://extensions/content/dummy.xhtml",
       loadURIOptions
     );
 
--- a/toolkit/components/places/tests/chrome/browser_disableglobalhistory.xhtml
+++ b/toolkit/components/places/tests/chrome/browser_disableglobalhistory.xhtml
@@ -16,17 +16,17 @@
 
   Cu.import("resource://testing-common/ContentTask.jsm");
   ContentTask.setTestScope(window.arguments[0].wrappedJSObject);
 
   function expectUseGlobalHistory(id, expected) {
     let browser = document.getElementById(id);
     /* eslint-disable-next-line no-shadow */
     return ContentTask.spawn(browser, {id, expected}, function({id, expected}) {
-      Assert.equal(docShell.useGlobalHistory, expected,
+      Assert.equal(docShell.browsingContext.useGlobalHistory, expected,
                    "Got the right useGlobalHistory state in the docShell of " + id);
     });
   }
 
   async function run_test() {
     await expectUseGlobalHistory("inprocess_disabled", false);
     await expectUseGlobalHistory("inprocess_enabled", true);
 
--- a/toolkit/components/windowwatcher/nsWindowWatcher.cpp
+++ b/toolkit/components/windowwatcher/nsWindowWatcher.cpp
@@ -826,20 +826,16 @@ nsresult nsWindowWatcher::OpenWindowInte
         !(chromeFlags & (nsIWebBrowserChrome::CHROME_MODAL |
                          nsIWebBrowserChrome::CHROME_OPENAS_DIALOG |
                          nsIWebBrowserChrome::CHROME_OPENAS_CHROME))) {
       MOZ_ASSERT(openWindowInfo);
 
       nsCOMPtr<nsIWindowProvider> provider;
       if (parentTreeOwner) {
         provider = do_GetInterface(parentTreeOwner);
-      } else if (XRE_IsContentProcess()) {
-        // we're in a content process but we don't have a tabchild we can
-        // use.
-        provider = nsContentUtils::GetWindowProviderForContentProcess();
       }
 
       if (provider) {
         rv = provider->ProvideWindow(openWindowInfo, chromeFlags, aCalledFromJS,
                                      sizeSpec.WidthSpecified(), uriToLoad, name,
                                      featuresStr, aForceNoOpener,
                                      aForceNoReferrer, aLoadState, &windowIsNew,
                                      getter_AddRefs(newBC));
--- a/toolkit/content/widgets/browser-custom-element.js
+++ b/toolkit/content/widgets/browser-custom-element.js
@@ -1269,17 +1269,17 @@
           );
 
           // enable global history if we weren't told otherwise
           if (
             !this.hasAttribute("disableglobalhistory") &&
             !this.isRemoteBrowser
           ) {
             try {
-              this.docShell.useGlobalHistory = true;
+              this.docShell.browsingContext.useGlobalHistory = true;
             } catch (ex) {
               // This can occur if the Places database is locked
               Cu.reportError("Error enabling browser global history: " + ex);
             }
           }
         }
       } catch (e) {
         Cu.reportError(e);
--- a/toolkit/modules/HiddenFrame.jsm
+++ b/toolkit/modules/HiddenFrame.jsm
@@ -108,15 +108,16 @@ HiddenFrame.prototype = {
     };
     this._webProgress.addProgressListener(
       this._listener,
       Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT
     );
     let docShell = this._browser.docShell;
     let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
     docShell.createAboutBlankContentViewer(systemPrincipal, systemPrincipal);
-    docShell.useGlobalHistory = false;
+    let browsingContext = this._browser.browsingContext;
+    browsingContext.useGlobalHistory = false;
     let loadURIOptions = {
       triggeringPrincipal: systemPrincipal,
     };
     this._browser.loadURI(XUL_PAGE, loadURIOptions);
   },
 };
--- a/tools/profiler/core/platform.cpp
+++ b/tools/profiler/core/platform.cpp
@@ -238,17 +238,18 @@ static uint32_t AvailableFeatures() {
   }
 
   return features;
 }
 
 // Default features common to all contexts (even if not available).
 static uint32_t DefaultFeatures() {
   return ProfilerFeature::Java | ProfilerFeature::JS | ProfilerFeature::Leaf |
-         ProfilerFeature::StackWalk | ProfilerFeature::Threads;
+         ProfilerFeature::StackWalk | ProfilerFeature::Threads |
+         ProfilerFeature::Screenshots;
 }
 
 // Extra default features when MOZ_PROFILER_STARTUP is set (even if not
 // available).
 static uint32_t StartupExtraDefaultFeatures() {
   // Enable mainthreadio by default for startup profiles as startup is heavy on
   // I/O operations, and main thread I/O is really important to see there.
   return ProfilerFeature::MainThreadIO;
@@ -955,16 +956,24 @@ class ActivePS {
     Flags |= FeatureJSAllocations(aLock)
                  ? uint32_t(JSInstrumentationFlags::Allocations)
                  : 0;
     return Flags;
   }
 
   PS_GET(const Vector<std::string>&, Filters)
 
+  // Not using PS_GET, because only the "Controlled" interface of
+  // `mProfileBufferChunkManager` should be exposed here.
+  static ProfileBufferControlledChunkManager& ControlledChunkManager(
+      PSLockRef) {
+    MOZ_ASSERT(sInstance);
+    return sInstance->mProfileBufferChunkManager;
+  }
+
   static void FulfillChunkRequests(PSLockRef) {
     MOZ_ASSERT(sInstance);
     sInstance->mProfileBufferChunkManager.FulfillChunkRequests();
   }
 
   static ProfileBuffer& Buffer(PSLockRef) {
     MOZ_ASSERT(sInstance);
     return sInstance->mProfileBuffer;
@@ -3770,16 +3779,18 @@ static SamplerThread* locked_profiler_st
 void profiler_shutdown(IsFastShutdown aIsFastShutdown) {
   LOG("profiler_shutdown");
 
   VTUNE_SHUTDOWN();
 
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(CorePS::Exists());
 
+  ProfilerParent::ProfilerWillStopIfStarted();
+
   // If the profiler is active we must get a handle to the SamplerThread before
   // ActivePS is destroyed, in order to delete it.
   SamplerThread* samplerThread = nullptr;
   {
     PSAutoLock lock(gPSMutex);
 
     // Save the profile on shutdown if requested.
     if (ActivePS::Exists(lock)) {
@@ -3913,16 +3924,25 @@ void profiler_get_start_params(int* aCap
 
   const Vector<std::string>& filters = ActivePS::Filters(lock);
   MOZ_ALWAYS_TRUE(aFilters->resize(filters.length()));
   for (uint32_t i = 0; i < filters.length(); ++i) {
     (*aFilters)[i] = filters[i].c_str();
   }
 }
 
+ProfileBufferControlledChunkManager* profiler_get_controlled_chunk_manager() {
+  MOZ_RELEASE_ASSERT(CorePS::Exists());
+  PSAutoLock lock(gPSMutex);
+  if (NS_WARN_IF(!ActivePS::Exists(lock))) {
+    return nullptr;
+  }
+  return &ActivePS::ControlledChunkManager(lock);
+}
+
 namespace mozilla {
 
 void GetProfilerEnvVarsForChildProcess(
     std::function<void(const char* key, const char* value)>&& aSetEnv) {
   MOZ_RELEASE_ASSERT(CorePS::Exists());
 
   PSAutoLock lock(gPSMutex);
 
@@ -4270,16 +4290,18 @@ static void locked_profiler_start(PSLock
 }
 
 void profiler_start(PowerOfTwo32 aCapacity, double aInterval,
                     uint32_t aFeatures, const char** aFilters,
                     uint32_t aFilterCount, uint64_t aActiveBrowsingContextID,
                     const Maybe<double>& aDuration) {
   LOG("profiler_start");
 
+  ProfilerParent::ProfilerWillStopIfStarted();
+
   SamplerThread* samplerThread = nullptr;
   {
     PSAutoLock lock(gPSMutex);
 
     // Initialize if necessary.
     if (!CorePS::Exists()) {
       profiler_init(nullptr);
     }
@@ -4312,16 +4334,18 @@ void profiler_start(PowerOfTwo32 aCapaci
 
 void profiler_ensure_started(PowerOfTwo32 aCapacity, double aInterval,
                              uint32_t aFeatures, const char** aFilters,
                              uint32_t aFilterCount,
                              uint64_t aActiveBrowsingContextID,
                              const Maybe<double>& aDuration) {
   LOG("profiler_ensure_started");
 
+  ProfilerParent::ProfilerWillStopIfStarted();
+
   bool startedProfiler = false;
   SamplerThread* samplerThread = nullptr;
   {
     PSAutoLock lock(gPSMutex);
 
     // Initialize if necessary.
     if (!CorePS::Exists()) {
       profiler_init(nullptr);
@@ -4421,16 +4445,18 @@ void profiler_ensure_started(PowerOfTwo3
   return samplerThread;
 }
 
 void profiler_stop() {
   LOG("profiler_stop");
 
   MOZ_RELEASE_ASSERT(CorePS::Exists());
 
+  ProfilerParent::ProfilerWillStopIfStarted();
+
 #if defined(MOZ_REPLACE_MALLOC) && defined(MOZ_PROFILER_MEMORY)
   // Remove the hooks early, as native allocations (if they are on) can be
   // quite expensive.
   mozilla::profiler::remove_memory_hooks();
 #endif
 
   SamplerThread* samplerThread;
   {
--- a/tools/profiler/gecko/PProfiler.ipdl
+++ b/tools/profiler/gecko/PProfiler.ipdl
@@ -18,15 +18,18 @@ async protocol PProfiler
 {
 child:
   async Start(ProfilerInitParams params);
   async EnsureStarted(ProfilerInitParams params);
   async Stop();
   async Pause();
   async Resume();
 
+  async AwaitNextChunkManagerUpdate() returns (ProfileBufferChunkManagerUpdate update);
+  async DestroyReleasedChunksAtOrBefore(TimeStamp timeStamp);
+
   async GatherProfile() returns (Shmem profile);
 
   async ClearAllPages();
 };
 
 } // namespace mozilla
 
--- a/tools/profiler/gecko/ProfilerChild.cpp
+++ b/tools/profiler/gecko/ProfilerChild.cpp
@@ -3,59 +3,184 @@
 /* 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 "ProfilerChild.h"
 
 #include "GeckoProfiler.h"
 #include "platform.h"
+#include "ProfilerParent.h"
 
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 
 ProfilerChild::ProfilerChild()
     : mThread(NS_GetCurrentThread()), mDestroyed(false) {
   MOZ_COUNT_CTOR(ProfilerChild);
 }
 
 ProfilerChild::~ProfilerChild() { MOZ_COUNT_DTOR(ProfilerChild); }
 
+void ProfilerChild::ResolveChunkUpdate(
+    PProfilerChild::AwaitNextChunkManagerUpdateResolver& aResolve) {
+  MOZ_ASSERT(!!aResolve,
+             "ResolveChunkUpdate should only be called when there's a pending "
+             "resolver");
+  MOZ_ASSERT(
+      !mChunkManagerUpdate.IsNotUpdate(),
+      "ResolveChunkUpdate should only be called with a real or final update");
+  MOZ_ASSERT(
+      !mDestroyed,
+      "ResolveChunkUpdate should not be called if the actor was destroyed");
+  if (mChunkManagerUpdate.IsFinal()) {
+    // Final update, send a special "unreleased value", but don't clear the
+    // local copy so we know we got the final update.
+    std::move(aResolve)(ProfilerParent::MakeFinalUpdate());
+  } else {
+    // Optimization note: The ProfileBufferChunkManagerUpdate constructor takes
+    // the newly-released chunks nsTArray by reference-to-const, therefore
+    // constructing and then moving the array here would make a copy. So instead
+    // we first give it an empty array, and then we can write the data directly
+    // into the update's array.
+    ProfileBufferChunkManagerUpdate update{
+        mChunkManagerUpdate.UnreleasedBytes(),
+        mChunkManagerUpdate.ReleasedBytes(),
+        mChunkManagerUpdate.OldestDoneTimeStamp(),
+        {}};
+    update.newlyReleasedChunks().SetCapacity(
+        mChunkManagerUpdate.NewlyReleasedChunksRef().size());
+    for (const ProfileBufferControlledChunkManager::ChunkMetadata& chunk :
+         mChunkManagerUpdate.NewlyReleasedChunksRef()) {
+      update.newlyReleasedChunks().EmplaceBack(chunk.mDoneTimeStamp,
+                                               chunk.mBufferBytes);
+    }
+
+    std::move(aResolve)(update);
+
+    // Clear the update we just sent, so it's ready for later updates to be
+    // folded into it.
+    mChunkManagerUpdate.Clear();
+  }
+
+  // Discard the resolver, so it's empty next time there's a new request.
+  aResolve = nullptr;
+}
+
+void ProfilerChild::ChunkManagerUpdateCallback(
+    ProfileBufferControlledChunkManager::Update&& aUpdate) {
+  if (mDestroyed) {
+    return;
+  }
+  // Always store the data, it could be the final update.
+  mChunkManagerUpdate.Fold(std::move(aUpdate));
+  if (mAwaitNextChunkManagerUpdateResolver) {
+    // There is already a pending resolver, give it the info now.
+    ResolveChunkUpdate(mAwaitNextChunkManagerUpdateResolver);
+  }
+}
+
+void ProfilerChild::SetupChunkManager() {
+  mChunkManager = profiler_get_controlled_chunk_manager();
+  if (NS_WARN_IF(!mChunkManager)) {
+    return;
+  }
+
+  // The update may be in any state from a previous profiling session.
+  // In case there is already a task in-flight with an update from that previous
+  // session, we need to dispatch a task to clear the update afterwards, but
+  // before the first update which will be dispatched from SetUpdateCallback()
+  // below.
+  mThread->Dispatch(NS_NewRunnableFunction(
+      "ChunkManagerUpdate Callback", [profilerChild = RefPtr(this)]() mutable {
+        profilerChild->mChunkManagerUpdate.Clear();
+      }));
+
+  // `ProfilerChild` should only be used on its `mThread`.
+  // But the chunk manager update callback may happen on any thread, so we need
+  // to manually keep the `ProfilerChild` alive until after the final update has
+  // been handled on `mThread`.
+  // Using manual AddRef/Release, because ref-counting is single-threaded, so we
+  // cannot have a RefPtr stored in the callback which will be destroyed in
+  // another thread. The callback (where `Release()` happens) is guaranteed to
+  // always be called, at the latest when the calback is reset during shutdown.
+  AddRef();
+  mChunkManager->SetUpdateCallback(
+      // Cast to `void*` to evade refcounted security!
+      [profilerChildPtr = static_cast<void*>(this)](
+          ProfileBufferControlledChunkManager::Update&& aUpdate) {
+        // Always dispatch, even if we're already on the `mThread`, to avoid
+        // reentrancy issues.
+        ProfilerChild* profilerChild =
+            static_cast<ProfilerChild*>(profilerChildPtr);
+        profilerChild->mThread->Dispatch(NS_NewRunnableFunction(
+            "ChunkManagerUpdate Callback",
+            [profilerChildPtr, update = std::move(aUpdate)]() mutable {
+              ProfilerChild* profilerChild =
+                  static_cast<ProfilerChild*>(profilerChildPtr);
+              const bool isFinal = update.IsFinal();
+              profilerChild->ChunkManagerUpdateCallback(std::move(update));