Merge mozilla-central to mozilla-inbound. a=merge CLOSED TREE
authorBogdan Tara <btara@mozilla.com>
Wed, 02 Jan 2019 23:41:59 +0200
changeset 509418 d3d32e312a50622e2a0cdb7ac1afc1170ac60154
parent 509417 fcf60924da90d2242cefc64d5ae83a85db395749 (current diff)
parent 509404 aa4130cac64d20bdb83805984b51037c13b4fbbd (diff)
child 509419 5ce8a04d9025b6dbbfd1506e9713f59269d55c13
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound. a=merge CLOSED TREE
devtools/client/framework/toolbox-highlighter-utils.js
dom/ipc/ContentChild.cpp
dom/tests/mochitest/bugs/file2_bug504862.html
dom/tests/mochitest/bugs/file_bug406375.html
testing/web-platform/meta/html/browsers/browsing-the-web/history-traversal/window-name-after-cross-origin-aux-frame-navigation.sub.html.ini
--- a/accessible/base/nsAccUtils.cpp
+++ b/accessible/base/nsAccUtils.cpp
@@ -434,8 +434,74 @@ bool nsAccUtils::PersistentPropertiesToA
     rv = propElem->GetValue(value);
     NS_ENSURE_SUCCESS(rv, false);
 
     aAttributes->AppendElement(Attribute(name, value));
   }
 
   return true;
 }
+
+bool nsAccUtils::IsARIALive(const Accessible* aAccessible) {
+  // Get computed aria-live property based on the closest container with the
+  // attribute. Inner nodes override outer nodes within the same
+  // document, but nodes in outer documents override nodes in inner documents.
+  // This should be the same as the container-live attribute, but we don't need
+  // the other container-* attributes, so we can't use the same function.
+  nsAutoString live;
+  nsIContent* startContent = aAccessible->GetContent();
+  while (startContent) {
+    nsIDocument* doc = startContent->GetComposedDoc();
+    if (!doc) {
+      break;
+    }
+
+    dom::Element* aTopEl = doc->GetRootElement();
+    nsIContent* ancestor = startContent;
+    while (ancestor) {
+      nsAutoString docLive;
+      const nsRoleMapEntry* role = nullptr;
+      if (ancestor->IsElement()) {
+        role = aria::GetRoleMap(ancestor->AsElement());
+      }
+      if (HasDefinedARIAToken(ancestor, nsGkAtoms::aria_live)) {
+        ancestor->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_live,
+                                       docLive);
+      } else if (role) {
+        GetLiveAttrValue(role->liveAttRule, docLive);
+      }
+      if (!docLive.IsEmpty()) {
+        live = docLive;
+        break;
+      }
+
+      if (ancestor == aTopEl) {
+        break;
+      }
+
+      ancestor = ancestor->GetParent();
+      if (!ancestor) {
+        ancestor = aTopEl;  // Use <body>/<frameset>
+      }
+    }
+
+    // Allow ARIA live region markup from outer documents to override.
+    nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem = doc->GetDocShell();
+    if (!docShellTreeItem) {
+      break;
+    }
+
+    nsCOMPtr<nsIDocShellTreeItem> sameTypeParent;
+    docShellTreeItem->GetSameTypeParent(getter_AddRefs(sameTypeParent));
+    if (!sameTypeParent || sameTypeParent == docShellTreeItem) {
+      break;
+    }
+
+    nsIDocument* parentDoc = doc->GetParentDocument();
+    if (!parentDoc) {
+      break;
+    }
+
+    startContent = parentDoc->FindContentForSubDocument(doc);
+  }
+
+  return !live.IsEmpty() && !live.EqualsLiteral("off");
+}
--- a/accessible/base/nsAccUtils.h
+++ b/accessible/base/nsAccUtils.h
@@ -250,14 +250,20 @@ class nsAccUtils {
   /**
    * Return true if the given accessible can't have children. Used when exposing
    * to platform accessibility APIs, should the children be pruned off?
    */
   static bool MustPrune(Accessible* aAccessible);
 
   static bool PersistentPropertiesToArray(nsIPersistentProperties* aProps,
                                           nsTArray<Attribute>* aAttributes);
+
+  /**
+   * Return true if the given accessible is within an ARIA live region; i.e.
+   * the container-live attribute would be something other than "off" or empty.
+   */
+  static bool IsARIALive(const Accessible* aAccessible);
 };
 
 }  // namespace a11y
 }  // namespace mozilla
 
 #endif
--- a/accessible/generic/Accessible.cpp
+++ b/accessible/generic/Accessible.cpp
@@ -841,20 +841,33 @@ nsresult Accessible::HandleAccEvent(AccE
         case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
           AccCaretMoveEvent* event = downcast_accEvent(aEvent);
           ipcDoc->SendCaretMoveEvent(id, event->GetCaretOffset());
           break;
         }
         case nsIAccessibleEvent::EVENT_TEXT_INSERTED:
         case nsIAccessibleEvent::EVENT_TEXT_REMOVED: {
           AccTextChangeEvent* event = downcast_accEvent(aEvent);
-          ipcDoc->SendTextChangeEvent(
-              id, event->ModifiedText(), event->GetStartOffset(),
-              event->GetLength(), event->IsTextInserted(),
-              event->IsFromUserInput());
+          const nsString& text = event->ModifiedText();
+#if defined(XP_WIN)
+          // On Windows, events for live region updates containing embedded
+          // objects require us to dispatch synchronous events.
+          bool sync = text.Contains(L'\xfffc') &&
+                      nsAccUtils::IsARIALive(aEvent->GetAccessible());
+#endif
+          ipcDoc->SendTextChangeEvent(id, text, event->GetStartOffset(),
+                                      event->GetLength(),
+                                      event->IsTextInserted(),
+                                      event->IsFromUserInput()
+#if defined(XP_WIN)
+                                      // This parameter only exists on Windows.
+                                      ,
+                                      sync
+#endif
+          );
           break;
         }
         case nsIAccessibleEvent::EVENT_SELECTION:
         case nsIAccessibleEvent::EVENT_SELECTION_ADD:
         case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: {
           AccSelChangeEvent* selEvent = downcast_accEvent(aEvent);
           uint64_t widgetID =
               selEvent->Widget()->IsDoc()
--- a/accessible/ipc/win/DocAccessibleChild.cpp
+++ b/accessible/ipc/win/DocAccessibleChild.cpp
@@ -192,19 +192,19 @@ bool DocAccessibleChild::SendCaretMoveEv
   PushDeferredEvent(
       MakeUnique<SerializedCaretMove>(this, aID, aCaretRect, aOffset));
   return true;
 }
 
 bool DocAccessibleChild::SendTextChangeEvent(
     const uint64_t& aID, const nsString& aStr, const int32_t& aStart,
     const uint32_t& aLen, const bool& aIsInsert, const bool& aFromUser,
-    const bool aDoSyncCheck) {
+    const bool aDoSync) {
   if (IsConstructedInParentProcess()) {
-    if (aDoSyncCheck && aStr.Contains(L'\xfffc')) {
+    if (aDoSync) {
       // The AT is going to need to reenter content while the event is being
       // dispatched synchronously.
       return PDocAccessibleChild::SendSyncTextChangeEvent(
           aID, aStr, aStart, aLen, aIsInsert, aFromUser);
     }
     return PDocAccessibleChild::SendTextChangeEvent(aID, aStr, aStart, aLen,
                                                     aIsInsert, aFromUser);
   }
--- a/accessible/ipc/win/DocAccessibleChild.h
+++ b/accessible/ipc/win/DocAccessibleChild.h
@@ -49,17 +49,17 @@ class DocAccessibleChild : public DocAcc
                           const LayoutDeviceIntRect& aCaretRect,
                           const int32_t& aOffset);
   bool SendFocusEvent(const uint64_t& aID);
   bool SendFocusEvent(const uint64_t& aID,
                       const LayoutDeviceIntRect& aCaretRect);
   bool SendTextChangeEvent(const uint64_t& aID, const nsString& aStr,
                            const int32_t& aStart, const uint32_t& aLen,
                            const bool& aIsInsert, const bool& aFromUser,
-                           const bool aDoSyncCheck = true);
+                           const bool aDoSync = false);
   bool SendSelectionEvent(const uint64_t& aID, const uint64_t& aWidgetID,
                           const uint32_t& aType);
   bool SendRoleChangedEvent(const a11y::role& aRole);
   bool SendScrollingEvent(const uint64_t& aID, const uint64_t& aType,
                           const uint32_t& aScrollX, const uint32_t& aScrollY,
                           const uint32_t& aMaxScrollX,
                           const uint32_t& aMaxScrollY);
 
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1530,17 +1530,17 @@ pref("dom.storage_access.enabled", true)
 
 pref("dom.storage_access.auto_grants", true);
 pref("dom.storage_access.max_concurrent_auto_grants", 5);
 
 // Define a set of default features for the Content Blocking UI.
 pref("browser.contentblocking.trackingprotection.control-center.ui.enabled", true);
 pref("browser.contentblocking.rejecttrackers.control-center.ui.enabled", true);
 
-pref("browser.contentblocking.control-center.ui.showBlockedLabels", false);
+pref("browser.contentblocking.control-center.ui.showBlockedLabels", true);
 pref("browser.contentblocking.control-center.ui.showAllowedLabels", false);
 
 // Enable the Report Breakage UI on Nightly and Beta but not on Release yet.
 #ifdef EARLY_BETA_OR_EARLIER
 pref("browser.contentblocking.reportBreakage.enabled", true);
 #else
 pref("browser.contentblocking.reportBreakage.enabled", false);
 #endif
--- a/browser/themes/shared/controlcenter/panel.inc.css
+++ b/browser/themes/shared/controlcenter/panel.inc.css
@@ -697,16 +697,20 @@ description#identity-popup-content-verif
   text-align: end;
 }
 
 .identity-popup-content-blocking-category-state-label,
 .identity-popup-permission-state-label {
   color: var(--panel-disabled-color);
 }
 
+#identity-popup-content-blocking-content[hasException] .identity-popup-content-blocking-category-state-label {
+  visibility: hidden;
+}
+
 .identity-popup-permission-remove-button {
   -moz-appearance: none;
   margin: 0;
   border-width: 0;
   border-radius: 50%;
   min-width: 0;
   padding: 2px;
   background-color: transparent;
--- a/devtools/client/definitions.js
+++ b/devtools/client/definitions.js
@@ -80,17 +80,17 @@ Tools.inspector = {
     }
 
     return l10n("inspector.tooltip2", "Ctrl+Shift+") + l10n("inspector.commandkey");
   },
   inMenu: true,
 
   preventClosingOnKey: true,
   onkey: function(panel, toolbox) {
-    toolbox.highlighterUtils.togglePicker();
+    toolbox.inspector.nodePicker.togglePicker();
   },
 
   isTargetSupported: function(target) {
     return target.hasActor("inspector");
   },
 
   build: function(iframeWindow, toolbox) {
     return new InspectorPanel(iframeWindow, toolbox);
--- a/devtools/client/framework/moz.build
+++ b/devtools/client/framework/moz.build
@@ -23,17 +23,16 @@ DevToolsModules(
     'menu-item.js',
     'menu.js',
     'selection.js',
     'sidebar.js',
     'source-map-url-service.js',
     'target-from-url.js',
     'target.js',
     'toolbox-context-menu.js',
-    'toolbox-highlighter-utils.js',
     'toolbox-host-manager.js',
     'toolbox-hosts.js',
     'toolbox-options.js',
     'toolbox-tabs-order-manager.js',
     'toolbox.js',
     'ToolboxProcess.jsm',
 )
 
--- a/devtools/client/framework/test/browser_keybindings_01.js
+++ b/devtools/client/framework/test/browser_keybindings_01.js
@@ -93,22 +93,22 @@ add_task(async function() {
     await inspectorShouldBeOpenAndHighlighting(inspectorKeys[1]);
   }
 
   gBrowser.removeCurrentTab();
 
   async function inspectorShouldBeOpenAndHighlighting(inspector) {
     is(toolbox.currentToolId, "inspector", "Correct tool has been loaded");
 
-    await toolbox.once("picker-started");
+    await toolbox.inspector.nodePicker.once("picker-started");
 
     ok(true, "picker-started event received, highlighter started");
     inspector.synthesizeKey();
 
-    await toolbox.once("picker-stopped");
+    await toolbox.inspector.nodePicker.once("picker-stopped");
     ok(true, "picker-stopped event received, highlighter stopped");
   }
 
   function webconsoleShouldBeSelected() {
     is(toolbox.currentToolId, "webconsole", "webconsole should be selected.");
   }
 
   function netmonitorShouldBeSelected() {
deleted file mode 100644
--- a/devtools/client/framework/toolbox-highlighter-utils.js
+++ /dev/null
@@ -1,176 +0,0 @@
-/* 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";
-
-/**
- * Client-side highlighter shared module.
- * To be used by toolbox panels that need to highlight DOM elements.
- *
- * Highlighting and selecting elements is common enough that it needs to be at
- * toolbox level, accessible by any panel that needs it.
- * That's why the toolbox is the one that initializes the inspector and
- * highlighter. It's also why the API returned by this module needs a reference
- * to the toolbox which should be set once only.
- */
-
-/**
- * Get the highighterUtils instance for a given toolbox.
- * This should be done once only by the toolbox itself and stored there so that
- * panels can get it from there. That's because the API returned has a stateful
- * scope that would be different for another instance returned by this function.
- *
- * @param {Toolbox} toolbox
- * @return {Object} the highlighterUtils public API
- */
-exports.getHighlighterUtils = function(toolbox) {
-  if (!toolbox) {
-    throw new Error("Missing or invalid toolbox passed to getHighlighterUtils");
-  }
-
-  // Exported API properties will go here
-  const exported = {};
-
-  // Is the highlighter currently in pick mode
-  let isPicking = false;
-
-  /**
-   * Release this utils, nullifying the references to the toolbox
-   */
-  exported.release = function() {
-    toolbox = null;
-  };
-
-  /**
-   * Make a function that initializes the inspector before it runs.
-   * Since the init of the inspector is asynchronous, the return value will be
-   * produced by Task.async and the argument should be a generator
-   * @param {Function*} generator A generator function
-   * @return {Function} A function
-   */
-  let isInspectorInitialized = false;
-  const requireInspector = generator => {
-    return async function(...args) {
-      if (!isInspectorInitialized) {
-        await toolbox.initInspector();
-        isInspectorInitialized = true;
-      }
-      return generator.apply(null, args);
-    };
-  };
-
-  /**
-   * Start/stop the element picker on the debuggee target.
-   * @param {Boolean} doFocus - Optionally focus the content area once the picker is
-   *                            activated.
-   * @return A promise that resolves when done
-   */
-  exported.togglePicker = function(doFocus) {
-    if (isPicking) {
-      return cancelPicker();
-    }
-    return startPicker(doFocus);
-  };
-
-  /**
-   * Start the element picker on the debuggee target.
-   * This will request the inspector actor to start listening for mouse events
-   * on the target page to highlight the hovered/picked element.
-   * Depending on the server-side capabilities, this may fire events when nodes
-   * are hovered.
-   * @param {Boolean} doFocus - Optionally focus the content area once the picker is
-   *                            activated.
-   * @return A promise that resolves when the picker has started or immediately
-   * if it is already started
-   */
-  const startPicker = exported.startPicker =
-    requireInspector(async function(doFocus = false) {
-      if (isPicking) {
-        return;
-      }
-      isPicking = true;
-
-      toolbox.pickerButton.isChecked = true;
-      await toolbox.selectTool("inspector", "inspect_dom");
-      toolbox.on("select", cancelPicker);
-
-      toolbox.walker.on("picker-node-hovered", onPickerNodeHovered);
-      toolbox.walker.on("picker-node-picked", onPickerNodePicked);
-      toolbox.walker.on("picker-node-previewed", onPickerNodePreviewed);
-      toolbox.walker.on("picker-node-canceled", onPickerNodeCanceled);
-
-      await toolbox.highlighter.pick(doFocus);
-      toolbox.emit("picker-started");
-    });
-
-  /**
-   * Stop the element picker. Note that the picker is automatically stopped when
-   * an element is picked
-   * @return A promise that resolves when the picker has stopped or immediately
-   * if it is already stopped
-   */
-  const stopPicker = exported.stopPicker = requireInspector(async function() {
-    if (!isPicking) {
-      return;
-    }
-    isPicking = false;
-
-    toolbox.pickerButton.isChecked = false;
-
-    await toolbox.highlighter.cancelPick();
-    toolbox.walker.off("picker-node-hovered", onPickerNodeHovered);
-    toolbox.walker.off("picker-node-picked", onPickerNodePicked);
-    toolbox.walker.off("picker-node-previewed", onPickerNodePreviewed);
-    toolbox.walker.off("picker-node-canceled", onPickerNodeCanceled);
-
-    toolbox.off("select", cancelPicker);
-    toolbox.emit("picker-stopped");
-  });
-
-  /**
-   * Stop the picker, but also emit an event that the picker was canceled.
-   */
-  const cancelPicker = exported.cancelPicker = async function() {
-    await stopPicker();
-    toolbox.emit("picker-canceled");
-  };
-
-  /**
-   * When a node is hovered by the mouse when the highlighter is in picker mode
-   * @param {Object} data Information about the node being hovered
-   */
-  function onPickerNodeHovered(data) {
-    toolbox.emit("picker-node-hovered", data.node);
-  }
-
-  /**
-   * When a node has been picked while the highlighter is in picker mode
-   * @param {Object} data Information about the picked node
-   */
-  function onPickerNodePicked(data) {
-    toolbox.selection.setNodeFront(data.node, { reason: "picker-node-picked" });
-    stopPicker();
-  }
-
-  /**
-   * When a node has been shift-clicked (previewed) while the highlighter is in
-   * picker mode
-   * @param {Object} data Information about the picked node
-   */
-  function onPickerNodePreviewed(data) {
-    toolbox.selection.setNodeFront(data.node, { reason: "picker-node-previewed" });
-  }
-
-  /**
-   * When the picker is canceled, stop the picker, and make sure the toolbox
-   * gets the focus.
-   */
-  function onPickerNodeCanceled() {
-    cancelPicker();
-    toolbox.win.focus();
-  }
-
-  // Return the public API
-  return exported;
-};
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -33,18 +33,16 @@ var Startup = Cc["@mozilla.org/devtools/
 const { BrowserLoader } =
   ChromeUtils.import("resource://devtools/client/shared/browser-loader.js", {});
 
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const L10N = new LocalizationHelper("devtools/client/locales/toolbox.properties");
 
 loader.lazyRequireGetter(this, "AppConstants",
   "resource://gre/modules/AppConstants.jsm", true);
-loader.lazyRequireGetter(this, "getHighlighterUtils",
-  "devtools/client/framework/toolbox-highlighter-utils", true);
 loader.lazyRequireGetter(this, "flags",
   "devtools/shared/flags");
 loader.lazyRequireGetter(this, "KeyShortcuts",
   "devtools/client/shared/key-shortcuts");
 loader.lazyRequireGetter(this, "ZoomKeys",
   "devtools/client/shared/zoom-keys");
 loader.lazyRequireGetter(this, "settleAll",
   "devtools/shared/ThreadSafeDevToolsUtils", true);
@@ -131,34 +129,35 @@ function Toolbox(target, selectedTool, h
   this._toolUnregistered = this._toolUnregistered.bind(this);
   this._onWillNavigate = this._onWillNavigate.bind(this);
   this._refreshHostTitle = this._refreshHostTitle.bind(this);
   this.toggleNoAutohide = this.toggleNoAutohide.bind(this);
   this._updateFrames = this._updateFrames.bind(this);
   this._splitConsoleOnKeypress = this._splitConsoleOnKeypress.bind(this);
   this.closeToolbox = this.closeToolbox.bind(this);
   this.destroy = this.destroy.bind(this);
-  this.highlighterUtils = getHighlighterUtils(this);
   this._highlighterReady = this._highlighterReady.bind(this);
   this._highlighterHidden = this._highlighterHidden.bind(this);
   this._applyCacheSettings = this._applyCacheSettings.bind(this);
   this._applyServiceWorkersTestingSettings =
     this._applyServiceWorkersTestingSettings.bind(this);
   this._saveSplitConsoleHeight = this._saveSplitConsoleHeight.bind(this);
   this._onFocus = this._onFocus.bind(this);
   this._onBrowserMessage = this._onBrowserMessage.bind(this);
   this._updateTextBoxMenuItems = this._updateTextBoxMenuItems.bind(this);
   this._onPerformanceFrontEvent = this._onPerformanceFrontEvent.bind(this);
   this._onTabsOrderUpdated = this._onTabsOrderUpdated.bind(this);
   this._onToolbarFocus = this._onToolbarFocus.bind(this);
   this._onToolbarArrowKeypress = this._onToolbarArrowKeypress.bind(this);
   this._onPickerClick = this._onPickerClick.bind(this);
   this._onPickerKeypress = this._onPickerKeypress.bind(this);
+  this._onPickerStarting = this._onPickerStarting.bind(this);
   this._onPickerStarted = this._onPickerStarted.bind(this);
   this._onPickerStopped = this._onPickerStopped.bind(this);
+  this._onPickerCanceled = this._onPickerCanceled.bind(this);
   this._onInspectObject = this._onInspectObject.bind(this);
   this._onNewSelectedNodeFront = this._onNewSelectedNodeFront.bind(this);
   this._onToolSelected = this._onToolSelected.bind(this);
   this.updateToolboxButtonsVisibility = this.updateToolboxButtonsVisibility.bind(this);
   this.updateToolboxButtons = this.updateToolboxButtons.bind(this);
   this.selectTool = this.selectTool.bind(this);
   this._pingTelemetrySelectTool = this._pingTelemetrySelectTool.bind(this);
   this.toggleSplitConsole = this.toggleSplitConsole.bind(this);
@@ -188,19 +187,16 @@ function Toolbox(target, selectedTool, h
   this._target.on("inspect-object", this._onInspectObject);
 
   this.on("host-changed", this._refreshHostTitle);
   this.on("select", this._onToolSelected);
 
   gDevTools.on("tool-registered", this._toolRegistered);
   gDevTools.on("tool-unregistered", this._toolUnregistered);
 
-  this.on("picker-started", this._onPickerStarted);
-  this.on("picker-stopped", this._onPickerStopped);
-
   /**
    * Get text direction for the current locale direction.
    *
    * `getComputedStyle` forces a synchronous reflow, so use a lazy getter in order to
    * call it only once.
    */
   loader.lazyGetter(this, "direction", () => {
     // Get the direction from browser.xul document
@@ -370,18 +366,16 @@ Toolbox.prototype = {
    */
   get doc() {
     return this.win.document;
   },
 
   /**
    * Get the toolbox highlighter front. Note that it may not always have been
    * initialized first. Use `initInspector()` if needed.
-   * Consider using highlighterUtils instead, it exposes the highlighter API in
-   * a useful way for the toolbox panels
    */
   get highlighter() {
     return this._highlighter;
   },
 
   /**
    * Get the toolbox's inspector front. Note that it may not always have been
    * initialized first. Use `initInspector()` if needed.
@@ -1268,51 +1262,70 @@ Toolbox.prototype = {
    * Toggle the picker, but also decide whether or not the highlighter should
    * focus the window. This is only desirable when the toolbox is mounted to the
    * window. When devtools is free floating, then the target window should not
    * pop in front of the viewer when the picker is clicked.
    *
    * Note: Toggle picker can be overwritten by panel other than the inspector to
    * allow for custom picker behaviour.
    */
-  _onPickerClick: function() {
+  _onPickerClick: async function() {
     const focus = this.hostType === Toolbox.HostType.BOTTOM ||
                   this.hostType === Toolbox.HostType.LEFT ||
                   this.hostType === Toolbox.HostType.RIGHT;
     const currentPanel = this.getCurrentPanel();
     if (currentPanel.togglePicker) {
       currentPanel.togglePicker(focus);
     } else {
-      this.highlighterUtils.togglePicker(focus);
+      if (!this.inspector) {
+        await this.initInspector();
+      }
+      this.inspector.nodePicker.togglePicker(focus);
     }
   },
 
   /**
    * If the picker is activated, then allow the Escape key to deactivate the
    * functionality instead of the default behavior of toggling the console.
    */
   _onPickerKeypress: function(event) {
     if (event.keyCode === KeyCodes.DOM_VK_ESCAPE) {
       const currentPanel = this.getCurrentPanel();
       if (currentPanel.cancelPicker) {
         currentPanel.cancelPicker();
       } else {
-        this.highlighterUtils.cancelPicker();
+        this.inspector.nodePicker.cancel();
       }
       // Stop the console from toggling.
       event.stopImmediatePropagation();
     }
   },
 
-  _onPickerStarted: function() {
+  _onPickerStarting: async function() {
+    this.pickerButton.isChecked = true;
+    await this.selectTool("inspector", "inspect_dom");
+    this.on("select", this.inspector.nodePicker.stop);
+  },
+
+  _onPickerStarted: async function() {
     this.doc.addEventListener("keypress", this._onPickerKeypress, true);
   },
 
   _onPickerStopped: function() {
+    this.off("select", this.inspector.nodePicker.stop);
     this.doc.removeEventListener("keypress", this._onPickerKeypress, true);
+    this.pickerButton.isChecked = false;
+  },
+
+  /**
+   * When the picker is canceled, make sure the toolbox
+   * gets the focus.
+   */
+  _onPickerCanceled: function() {
+    this.win.focus();
   },
 
   /**
    * The element picker button enables the ability to select a DOM node by clicking
    * it on the page.
    */
   _buildPickerButton() {
     this.pickerButton = this._createButtonState({
@@ -2703,16 +2716,20 @@ Toolbox.prototype = {
         // than most other fronts because it is closely related to the toolbox.
         // TODO: replace with getFront once inspector is separated from the toolbox
         // TODO: remove these bindings
         this._inspector = await this.target.getInspector();
         this._walker = this.inspector.walker;
         this._highlighter = this.inspector.highlighter;
         this._selection = this.inspector.selection;
 
+        this.inspector.nodePicker.on("picker-starting", this._onPickerStarting);
+        this.inspector.nodePicker.on("picker-started", this._onPickerStarted);
+        this.inspector.nodePicker.on("picker-stopped", this._onPickerStopped);
+        this.inspector.nodePicker.on("picker-node-canceled", this._onPickerCanceled);
         this.walker.on("highlighter-ready", this._highlighterReady);
         this.walker.on("highlighter-hide", this._highlighterHidden);
         this._selection.on("new-node-front", this._onNewSelectedNodeFront);
       }.bind(this))();
     }
     return this._initInspector;
   },
 
@@ -2955,17 +2972,16 @@ Toolbox.prototype = {
           // This is done after other destruction tasks since it may tear down
           // fronts and the debugger transport which earlier destroy methods may
           // require to complete.
           if (!this._target) {
             return null;
           }
           const target = this._target;
           this._target = null;
-          this.highlighterUtils.release();
           target.off("close", this.destroy);
           return target.destroy();
         }, console.error).then(() => {
           this.emit("destroyed");
 
           // Free _host after the call to destroyed in order to let a chance
           // to destroyed listeners to still query toolbox attributes
           this._host = null;
--- a/devtools/client/inspector/animation/animation.js
+++ b/devtools/client/inspector/animation/animation.js
@@ -136,18 +136,18 @@ class AnimationInspector {
           simulateAnimationForKeyframesProgressBar,
           toggleElementPicker,
         }
       )
     );
     this.provider = provider;
 
     this.inspector.sidebar.on("select", this.onSidebarSelectionChanged);
-    this.inspector.toolbox.on("picker-started", this.onElementPickerStarted);
-    this.inspector.toolbox.on("picker-stopped", this.onElementPickerStopped);
+    this.inspector.inspector.nodePicker.on("picker-started", this.onElementPickerStarted);
+    this.inspector.inspector.nodePicker.on("picker-stopped", this.onElementPickerStopped);
     this.inspector.toolbox.on("select", this.onSidebarSelectionChanged);
   }
 
   _getAnimationsFront() {
     if (this.animationsFrontPromise) {
       return this.animationsFrontPromise;
     }
     this.animationsFrontPromise = new Promise(async resolve => {
@@ -160,18 +160,22 @@ class AnimationInspector {
   }
 
   destroy() {
     this.setAnimationStateChangedListenerEnabled(false);
     this.inspector.off("new-root", this.onNavigate);
     this.inspector.selection.off("new-node-front", this.update);
     this.inspector.sidebar.off("select", this.onSidebarSelectionChanged);
     this.inspector.toolbox.off("inspector-sidebar-resized", this.onSidebarResized);
-    this.inspector.toolbox.off("picker-started", this.onElementPickerStarted);
-    this.inspector.toolbox.off("picker-stopped", this.onElementPickerStopped);
+    this.inspector.inspector.nodePicker.off(
+      "picker-started", this.onElementPickerStarted
+    );
+    this.inspector.inspector.nodePicker.off(
+      "picker-stopped", this.onElementPickerStopped
+    );
     this.inspector.toolbox.off("select", this.onSidebarSelectionChanged);
 
     this.animationsFrontPromise.then(front => {
       front.off("mutations", this.onAnimationsMutation);
     });
 
     if (this.simulatedAnimation) {
       this.simulatedAnimation.cancel();
@@ -643,17 +647,17 @@ class AnimationInspector {
     const currentTimeTimer =
       new CurrentTimeTimer(timeScale, shouldStopAfterEndTime,
                            this.win, this.onCurrentTimeTimerUpdated);
     currentTimeTimer.start();
     this.currentTimeTimer = currentTimeTimer;
   }
 
   toggleElementPicker() {
-    this.inspector.toolbox.highlighterUtils.togglePicker();
+    this.inspector.inspector.nodePicker.togglePicker();
   }
 
   async update() {
     const done = this.inspector.updating("animationinspector");
 
     const selection = this.inspector.selection;
     const animationsFront = await this.animationsFrontPromise;
     const animations =
--- a/devtools/client/inspector/boxmodel/box-model.js
+++ b/devtools/client/inspector/boxmodel/box-model.js
@@ -202,22 +202,22 @@ BoxModel.prototype = {
     this.inspector.highlighter.unhighlight();
   },
 
   /**
    * Hides the geometry editor and updates the box moodel store with the new
    * geometry editor enabled state.
    */
   onHideGeometryEditor() {
-    const { markup, selection, toolbox } = this.inspector;
+    const { markup, selection, inspector } = this.inspector;
 
     this.highlighters.hideGeometryEditor();
     this.store.dispatch(updateGeometryEditorEnabled(false));
 
-    toolbox.off("picker-started", this.onHideGeometryEditor);
+    inspector.nodePicker.off("picker-started", this.onHideGeometryEditor);
     selection.off("new-node-front", this.onHideGeometryEditor);
     markup.off("leave", this.onMarkupViewLeave);
     markup.off("node-hover", this.onMarkupViewNodeHover);
   },
 
   /**
    * Handler function that re-shows the geometry editor for an element that already
    * had the geometry editor enabled. This handler function is called on a "leave" event
@@ -360,33 +360,33 @@ BoxModel.prototype = {
     this.updateBoxModel();
   },
 
   /**
    * Toggles on/off the geometry editor for the current element when the geometry editor
    * toggle button is clicked.
    */
   onToggleGeometryEditor() {
-    const { markup, selection, toolbox } = this.inspector;
+    const { markup, selection, inspector } = this.inspector;
     const nodeFront = this.inspector.selection.nodeFront;
     const state = this.store.getState();
     const enabled = !state.boxModel.geometryEditorEnabled;
 
     this.highlighters.toggleGeometryHighlighter(nodeFront);
     this.store.dispatch(updateGeometryEditorEnabled(enabled));
 
     if (enabled) {
       // Hide completely the geometry editor if the picker is clicked or a new node front
-      toolbox.on("picker-started", this.onHideGeometryEditor);
+      inspector.nodePicker.on("picker-started", this.onHideGeometryEditor);
       selection.on("new-node-front", this.onHideGeometryEditor);
       // Temporary hide the geometry editor
       markup.on("leave", this.onMarkupViewLeave);
       markup.on("node-hover", this.onMarkupViewNodeHover);
     } else {
-      toolbox.off("picker-started", this.onHideGeometryEditor);
+      inspector.nodePicker.off("picker-started", this.onHideGeometryEditor);
       selection.off("new-node-front", this.onHideGeometryEditor);
       markup.off("leave", this.onMarkupViewLeave);
       markup.off("node-hover", this.onMarkupViewNodeHover);
     }
   },
 
 };
 
--- a/devtools/client/inspector/markup/markup.js
+++ b/devtools/client/inspector/markup/markup.js
@@ -116,18 +116,22 @@ function MarkupView(inspector, frame, co
   this._elt.addEventListener("mousemove", this._onMouseMove);
   this._elt.addEventListener("mouseout", this._onMouseOut);
   this._frame.addEventListener("focus", this._onFocus);
   this.inspector.selection.on("new-node-front", this._onNewSelection);
   this.walker.on("display-change", this._onDisplayChange);
   this.walker.on("mutations", this._mutationObserver);
   this.win.addEventListener("copy", this._onCopy);
   this.win.addEventListener("mouseup", this._onMouseUp);
-  this.toolbox.on("picker-canceled", this._onToolboxPickerCanceled);
-  this.toolbox.on("picker-node-hovered", this._onToolboxPickerHover);
+  this.inspector.inspector.nodePicker.on(
+    "picker-node-canceled", this._onToolboxPickerCanceled
+  );
+  this.inspector.inspector.nodePicker.on(
+    "picker-node-hovered", this._onToolboxPickerHover
+  );
 
   if (flags.testing) {
     // In tests, we start listening immediately to avoid having to simulate a mousemove.
     this._initTooltips();
   } else {
     this._elt.addEventListener("mousemove", () => {
       this._initTooltips();
     }, { once: true });
@@ -1944,17 +1948,19 @@ MarkupView.prototype = {
     this.popup = null;
 
     this._elt.removeEventListener("blur", this._onBlur, true);
     this._elt.removeEventListener("click", this._onMouseClick);
     this._elt.removeEventListener("mousemove", this._onMouseMove);
     this._elt.removeEventListener("mouseout", this._onMouseOut);
     this._frame.removeEventListener("focus", this._onFocus);
     this.inspector.selection.off("new-node-front", this._onNewSelection);
-    this.toolbox.off("picker-node-hovered", this._onToolboxPickerHover);
+    this.inspector.inspector.nodePicker.off(
+      "picker-node-hovered", this._onToolboxPickerHover
+    );
     this.walker.off("display-change", this._onDisplayChange);
     this.walker.off("mutations", this._mutationObserver);
     this.win.removeEventListener("copy", this._onCopy);
     this.win.removeEventListener("mouseup", this._onMouseUp);
 
     this._prefObserver.off(ATTR_COLLAPSE_ENABLED_PREF,
                            this._onCollapseAttributesPrefChange);
     this._prefObserver.off(ATTR_COLLAPSE_LENGTH_PREF,
--- a/devtools/client/inspector/rules/test/browser_rules_colorpicker-hides-element-picker.js
+++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-hides-element-picker.js
@@ -7,17 +7,17 @@
 // if the picker is already selected.
 
 const TEST_URI = `<style>body{background:red}</style>`;
 
 add_task(async function() {
   await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
 
   const {view, toolbox} = await openRuleView();
-  const pickerStopped = toolbox.once("picker-stopped");
+  const pickerStopped = toolbox.inspector.nodePicker.once("picker-stopped");
 
   await startPicker(toolbox);
 
   info("Get the background property from the rule-view");
   const property = getRuleViewProperty(view, "body", "background");
   const swatch = property.valueSpan.querySelector(".ruleview-colorswatch");
   ok(swatch, "Color swatch is displayed for the background property");
 
--- a/devtools/client/inspector/test/browser_inspector_highlighter-03.js
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-03.js
@@ -55,16 +55,16 @@ add_task(async function() {
   info("Moving mouse over iframe body");
   await moveMouseOver("iframe", 40, 40);
 
   ok((await testActor.assertHighlightedNode(iframeBodySelector)),
      "highlighter shows the right node");
   await testActor.isNodeCorrectlyHighlighted(iframeBodySelector, is);
 
   info("Waiting for the element picker to deactivate.");
-  await inspector.toolbox.highlighterUtils.stopPicker();
+  await inspector.inspector.nodePicker.stop();
 
   function moveMouseOver(selector, x, y) {
     info("Waiting for element " + selector + " to be highlighted");
     testActor.synthesizeMouse({selector, x, y, options: {type: "mousemove"}});
-    return inspector.toolbox.once("picker-node-hovered");
+    return inspector.inspector.nodePicker.once("picker-node-hovered");
   }
 });
--- a/devtools/client/inspector/test/browser_inspector_highlighter-cancel.js
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-cancel.js
@@ -26,17 +26,17 @@ add_task(async function() {
 
   await cancelPickerByShortcut();
   ok(isSelectedMarkupNodeInView(),
      "The currently selected node is focused back on the screen.");
 
   function cancelPickerByShortcut() {
     info("Key pressed. Waiting for picker to be canceled.");
     testActor.synthesizeKey({key: "VK_ESCAPE", options: {}});
-    return inspector.toolbox.once("picker-canceled");
+    return inspector.inspector.nodePicker.once("picker-node-canceled");
   }
 
   function moveMouseOver(selector) {
     info(`Waiting for element ${selector} to be hovered in the markup view`);
     testActor.synthesizeMouse({
       options: {type: "mousemove"},
       center: true,
       selector: selector,
--- a/devtools/client/inspector/test/browser_inspector_highlighter-iframes_01.js
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-iframes_01.js
@@ -46,19 +46,19 @@ add_task(async function() {
   const innerFrameDivFront = await getNodeFrontInFrame("div", innerFrameFront,
                                                      inspector);
   await selectNode(innerFrameDivFront, inspector);
 
   is(inspector.breadcrumbs.nodeHierarchy.length, 9,
      "Breadcrumbs have 9 items.");
 
   info("Waiting for element picker to deactivate.");
-  await inspector.toolbox.highlighterUtils.stopPicker();
+  await inspector.inspector.nodePicker.stop();
 
   function moveMouseOver(selector) {
     info("Waiting for element " + selector + " to be highlighted");
     testActor.synthesizeMouse({
       selector: selector,
       options: {type: "mousemove"},
       center: true,
-    }).then(() => inspector.toolbox.once("picker-node-hovered"));
+    }).then(() => inspector.inspector.nodePicker.once("picker-node-hovered"));
   }
 });
--- a/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_01.js
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_01.js
@@ -4,17 +4,17 @@
 
 "use strict";
 
 // Test that the keybindings for Picker work alright
 
 const TEST_URL = URL_ROOT + "doc_inspector_highlighter_dom.html";
 
 add_task(async function() {
-  const {inspector, toolbox, testActor} = await openInspectorForURL(TEST_URL);
+  const {toolbox, testActor} = await openInspectorForURL(TEST_URL);
 
   await startPicker(toolbox);
 
   info("Selecting the simple-div1 DIV");
   await moveMouseOver("#simple-div1");
 
   ok((await testActor.assertHighlightedNode("#simple-div1")),
      "The highlighter shows #simple-div1. OK.");
@@ -39,26 +39,26 @@ add_task(async function() {
   await doKeyHover({key: "VK_LEFT", options: {}});
   await doKeyHover({key: "VK_LEFT", options: {}});
   ok((await testActor.assertHighlightedNode("#simple-div1")),
      "The highlighter shows #simple-div1. OK.");
 
   info("First child selection test Passed.");
 
   info("Stopping the picker");
-  await toolbox.highlighterUtils.stopPicker();
+  await toolbox.inspector.nodePicker.stop();
 
   function doKeyHover(args) {
     info("Key pressed. Waiting for element to be highlighted/hovered");
     testActor.synthesizeKey(args);
-    return inspector.toolbox.once("picker-node-hovered");
+    return toolbox.inspector.nodePicker.once("picker-node-hovered");
   }
 
   function moveMouseOver(selector) {
     info("Waiting for element " + selector + " to be highlighted");
     testActor.synthesizeMouse({
       options: {type: "mousemove"},
       center: true,
       selector: selector,
     });
-    return inspector.toolbox.once("picker-node-hovered");
+    return toolbox.inspector.nodePicker.once("picker-node-hovered");
   }
 });
--- a/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_02.js
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_02.js
@@ -4,17 +4,17 @@
 
 "use strict";
 
 // Test that the keybindings for Picker work alright
 
 const TEST_URL = URL_ROOT + "doc_inspector_highlighter_dom.html";
 
 add_task(async function() {
-  const {inspector, toolbox, testActor} = await openInspectorForURL(TEST_URL);
+  const {toolbox, testActor} = await openInspectorForURL(TEST_URL);
 
   await startPicker(toolbox);
 
   // Previously chosen child memory
   info("Testing whether previously chosen child is remembered");
 
   info("Selecting the ahoy paragraph DIV");
   await moveMouseOver("#ahoy");
@@ -35,30 +35,30 @@ add_task(async function() {
 
   await doKeyHover({key: "VK_RIGHT", options: {}});
   ok((await testActor.assertHighlightedNode("#simple-div2")),
      "The highlighter shows #simple-div2. OK.");
 
   info("Previously chosen child is remembered. Passed.");
 
   info("Stopping the picker");
-  await toolbox.highlighterUtils.stopPicker();
+  await toolbox.inspector.nodePicker.stop();
 
   function doKeyHover(args) {
     info("Key pressed. Waiting for element to be highlighted/hovered");
     const onHighlighterReady = toolbox.once("highlighter-ready");
-    const onPickerNodeHovered = inspector.toolbox.once("picker-node-hovered");
+    const onPickerNodeHovered = toolbox.inspector.nodePicker.once("picker-node-hovered");
     testActor.synthesizeKey(args);
     return promise.all([onHighlighterReady, onPickerNodeHovered]);
   }
 
   function moveMouseOver(selector) {
     info("Waiting for element " + selector + " to be highlighted");
     const onHighlighterReady = toolbox.once("highlighter-ready");
-    const onPickerNodeHovered = inspector.toolbox.once("picker-node-hovered");
+    const onPickerNodeHovered = toolbox.inspector.nodePicker.once("picker-node-hovered");
     testActor.synthesizeMouse({
       options: {type: "mousemove"},
       center: true,
       selector: selector,
     });
     return promise.all([onHighlighterReady, onPickerNodeHovered]);
   }
 });
--- a/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_03.js
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_03.js
@@ -43,30 +43,31 @@ add_task(async function() {
      "The #another DIV is still selected. Passed.");
 
   function doKeyPick(args) {
     info("Key pressed. Waiting for element to be picked");
     testActor.synthesizeKey(args);
     return promise.all([
       inspector.selection.once("new-node-front"),
       inspector.once("inspector-updated"),
-      inspector.toolbox.once("picker-stopped"),
+      inspector.inspector.nodePicker.once("picker-stopped"),
     ]);
   }
 
   function doKeyStop(args) {
     info("Key pressed. Waiting for picker to be canceled");
     testActor.synthesizeKey(args);
-    return inspector.toolbox.once("picker-stopped");
+    return inspector.inspector.nodePicker.once("picker-stopped");
   }
 
   function moveMouseOver(selector) {
     info("Waiting for element " + selector + " to be highlighted");
     const onHighlighterReady = toolbox.once("highlighter-ready");
-    const onPickerNodeHovered = inspector.toolbox.once("picker-node-hovered");
+    const onPickerNodeHovered =
+      inspector.inspector.nodePicker.once("picker-node-hovered");
     testActor.synthesizeMouse({
       options: {type: "mousemove"},
       center: true,
       selector: selector,
     });
     return promise.all([onHighlighterReady, onPickerNodeHovered]);
   }
 });
--- a/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_04.js
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_04.js
@@ -10,26 +10,26 @@
 const TEST_URL = "data:text/html;charset=utf8,<div></div>";
 
 add_task(async function() {
   const {inspector, toolbox, testActor} = await openInspectorForURL(TEST_URL);
 
   await startPicker(toolbox);
 
   info("Start using the picker by hovering over nodes");
-  const onHover = toolbox.once("picker-node-hovered");
+  const onHover = toolbox.inspector.nodePicker.once("picker-node-hovered");
   testActor.synthesizeMouse({
     options: {type: "mousemove"},
     center: true,
     selector: "div",
   });
   await onHover;
 
   info("Press escape and wait for the picker to stop");
-  const onPickerStopped = toolbox.once("picker-stopped");
+  const onPickerStopped = toolbox.inspector.nodePicker.once("picker-node-canceled");
   testActor.synthesizeKey({
     key: "VK_ESCAPE",
     options: {},
   });
   await onPickerStopped;
 
   info("Press escape again and wait for the split console to open");
   const onSplitConsole = toolbox.once("split-console");
--- a/devtools/client/inspector/test/browser_inspector_highlighter-xbl.js
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-xbl.js
@@ -29,11 +29,11 @@ add_task(async function() {
 
   function moveMouseOver(selector) {
     info("Waiting for element " + selector + " to be highlighted");
     testActor.synthesizeMouse({
       options: {type: "mousemove"},
       center: true,
       selector: selector,
     });
-    return inspector.toolbox.once("picker-node-hovered");
+    return inspector.inspector.nodePicker.once("picker-node-hovered");
   }
 });
--- a/devtools/client/inspector/test/browser_inspector_iframe-navigation.js
+++ b/devtools/client/inspector/test/browser_inspector_iframe-navigation.js
@@ -34,10 +34,10 @@ add_task(async function() {
 
   await testActor.reloadFrame("iframe");
   info("Frame reloaded twice.");
 
   isVisible = await testActor.isHighlighting();
   ok(isVisible, "Inspector is highlighting after iframe nav.");
 
   info("Stopping element picker.");
-  await toolbox.highlighterUtils.stopPicker();
+  await toolbox.inspector.nodePicker.stop();
 });
--- a/devtools/client/inspector/test/browser_inspector_picker-stop-on-tool-change.js
+++ b/devtools/client/inspector/test/browser_inspector_picker-stop-on-tool-change.js
@@ -7,17 +7,17 @@
 // Test that the highlighter's picker is stopped when a different tool is
 // selected
 
 const TEST_URI = "data:text/html;charset=UTF-8," +
   "testing the highlighter goes away on tool selection";
 
 add_task(async function() {
   const { toolbox } = await openInspectorForURL(TEST_URI);
-  const pickerStopped = toolbox.once("picker-stopped");
+  const pickerStopped = toolbox.inspector.nodePicker.once("picker-stopped");
 
   info("Starting the inspector picker");
   await startPicker(toolbox);
 
   info("Selecting another tool than the inspector in the toolbox");
   await toolbox.selectNextTool();
 
   info("Waiting for the picker-stopped event to be fired");
--- a/devtools/client/inspector/test/browser_inspector_switch-to-inspector-on-pick.js
+++ b/devtools/client/inspector/test/browser_inspector_switch-to-inspector-on-pick.js
@@ -62,17 +62,17 @@ add_task(async function() {
   ok(!snapshot.parent, "No events have been logged for the main process");
 
   const tab = await addTab(TEST_URI);
   const toolbox = await openToolbox(tab);
 
   await startPickerAndAssertSwitchToInspector(toolbox);
 
   info("Stoppping element picker.");
-  await toolbox.highlighterUtils.stopPicker();
+  await toolbox.inspector.nodePicker.stop();
 
   checkResults();
 });
 
 async function openToolbox(tab) {
   info("Opening webconsole.");
   const target = await TargetFactory.forTab(tab);
   return gDevTools.showToolbox(target, "webconsole");
--- a/devtools/client/inspector/test/head.js
+++ b/devtools/client/inspector/test/head.js
@@ -63,17 +63,17 @@ var navigateTo = async function(inspecto
 /**
  * Start the element picker and focus the content window.
  * @param {Toolbox} toolbox
  * @param {Boolean} skipFocus - Allow tests to bypass the focus event.
  */
 var startPicker = async function(toolbox, skipFocus) {
   info("Start the element picker");
   toolbox.win.focus();
-  await toolbox.highlighterUtils.startPicker();
+  await toolbox.inspector.nodePicker.start();
   if (!skipFocus) {
     // By default make sure the content window is focused since the picker may not focus
     // the content window by default.
     await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
       content.focus();
     });
   }
 };
@@ -116,17 +116,17 @@ function pickElement(inspector, testActo
  *        X-offset from the top-left corner of the element matching the provided selector
  * @param {Number} y
  *        Y-offset from the top-left corner of the element matching the provided selector
  * @return {Promise} promise that resolves when the "picker-node-hovered" event is
  *         emitted.
  */
 function hoverElement(inspector, testActor, selector, x, y) {
   info("Waiting for element " + selector + " to be hovered");
-  const onHovered = inspector.toolbox.once("picker-node-hovered");
+  const onHovered = inspector.inspector.nodePicker.once("picker-node-hovered");
   testActor.synthesizeMouse({selector, x, y, options: {type: "mousemove"}});
   return onHovered;
 }
 
 /**
  * Highlight a node and set the inspector's current selection to the node or
  * the first match of the given css selector.
  * @param {String|NodeFront} selector
--- a/devtools/client/shared/widgets/tooltip/SwatchColorPickerTooltip.js
+++ b/devtools/client/shared/widgets/tooltip/SwatchColorPickerTooltip.js
@@ -164,17 +164,17 @@ class SwatchColorPickerTooltip extends S
   }
 
   _openEyeDropper() {
     const {inspector, toolbox, telemetry} = this.inspector;
 
     telemetry.getHistogramById(TELEMETRY_PICKER_EYEDROPPER_OPEN_COUNT).add(true);
 
     // cancelling picker(if it is already selected) on opening eye-dropper
-    toolbox.highlighterUtils.cancelPicker();
+    inspector.nodePicker.cancel();
 
     // pickColorFromPage will focus the content document. If the devtools are in a
     // separate window, the colorpicker tooltip will be closed before pickColorFromPage
     // resolves. Flip the flag early to avoid issues with onTooltipHidden().
     this.eyedropperOpen = true;
 
     inspector.pickColorFromPage({copyOnSelect: false}).then(() => {
       // close the colorpicker tooltip so that only the eyedropper is open.
--- a/devtools/client/webconsole/test/mochitest/head.js
+++ b/devtools/client/webconsole/test/mochitest/head.js
@@ -958,23 +958,24 @@ function isReverseSearchInputFocused(hud
  *
  * @param {Object} toolbox
  * @param {Object} testActor: A test actor registered on the target. Needed to click on
  *                            the content element.
  * @param {String} selector: The selector for the node we want to select.
  */
 async function selectNodeWithPicker(toolbox, testActor, selector) {
   const inspector = toolbox.getPanel("inspector");
+  const inspectorFront = inspector.inspector;
 
-  const onPickerStarted = inspector.toolbox.once("picker-started");
-  inspector.toolbox.highlighterUtils.startPicker();
+  const onPickerStarted = inspectorFront.nodePicker.once("picker-started");
+  inspectorFront.nodePicker.start();
   await onPickerStarted;
 
   info(`Picker mode started, now clicking on "${selector}" to select that node`);
-  const onPickerStopped = toolbox.once("picker-stopped");
+  const onPickerStopped = inspectorFront.nodePicker.once("picker-stopped");
   const onInspectorUpdated = inspector.once("inspector-updated");
 
   testActor.synthesizeMouse({
     selector,
     center: true,
     options: {},
   });
 
--- a/devtools/docs/tools/highlighters.md
+++ b/devtools/docs/tools/highlighters.md
@@ -15,43 +15,43 @@ But there can be a wide variety of highl
 * where are the color stops of a css gradient,
 * which are all the elements that match a given selector,
 * ...
 
 ## Using highlighters
 
 Highlighters run on the debuggee side, not on the toolbox side. This is so that it's possible to highlight elements on a remote device for instance. This means you need to go through the [Remote Debugging Protocol](protocol.md) to use a highlighter.
 
-### The highlighter utils
-
-The easiest way to access the highlighters from toolbox-side DevTools code is by using the highlighter utils, which is conveniently available on the toolbox object. Here is how you can access the utils:
-
-```js
-let hUtils = toolbox.highlighterUtils;
-```
-
-Since the box-model highlighter is the most used type of highlighter (for instance it's displayed when you move your mouse over nodes in the inspector), the utils provides a set of methods to interact with it:
+Since the box-model highlighter (HighlighterFront) is the most used type of highlighter (for instance it's displayed when you move your mouse over nodes in the inspector), the HighlighterFront provides a custom set of methods to interact with it:
 
 | Method                             | Description                                                                                                                                                                                                                   |
 |------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
 | `startPicker()`                    | Starts the node picker mode which will highlight every node you hover over in the page, and will change the current node selection in the inspector on click. “picker-node-hovered” and “picker-node-picked” events are sent. |
 | `stopPicker()`                     | Stops the node picker mode.                                                                                                                                                                                                   |
 | `highlightNodeFront(nodeFront)`    | Display the box-model highlighter on a given node. NodeFront objects are what the WalkerActor return.                                                                                                                         |
 | `highlightDomValueGrip(valueGrip)` | Display the box-model highlighter on a given node, represented by a debugger object value grip.                                                                                                                               |
 | `unhighlight()`                    | Hide the box-model highlighter.                                                                                                                                                                                               |
 
-But the box-model highlighter isn't the only type of highlighter, so the highlighter utils provides the following method:
+Not all methods that are related to highlighters are present on the HighlighterFront. The
+`highlightDomValueGrip` method also requires the WalkerFront in order to transform a Grip into a
+NodeFront. Therefore, methods that access other Fronts are available on the InspectorFront.
+
+| Method                             | Description                                                                                                                                                                                                                   |
+|------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `highlightDomValueGrip(valueGrip)` | Display the box-model highlighter on a given node, represented by a debugger object value grip.                                                                                                                               |
+
+But the box-model highlighter isn't the only type of highlighter, so the InspectorFront also provides the following method:
 
 | Method                           | Description                                                                                                                                                                                                                                                                                                   |
 |----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
 | `getHighlighterByType(typeName)` | Instantiate a new highlighter, given its type (as a String). At the time of writing, the available types of highlighters are: `BoxModelHighlighter`, `CssTransformHighlighter`, `SelectorHighlighter` and `RectHighlighter`. This returns a promise that resolves to the new instance of [protocol.js](https://wiki.mozilla.org/DevTools/protocol.js) actor. |
 
 ### The highlighter API
 
-When getting a highlighter via `toolbox.highlighterUtils.getHighlighterByType(typeName)`, the right type of highlighter will be instantiated on the server-side and will be wrapped into a `CustomHighlighterActor` and that's what will be returned to the caller. This means that all types of highlighters share the same following API:
+When getting a highlighter via `toolbox.inspector.getHighlighterByType(typeName)`, the right type of highlighter will be instantiated on the server-side and will be wrapped into a `CustomHighlighterActor` and that's what will be returned to the caller. This means that all types of highlighters share the same following API:
 
 | Method                                   | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
 |------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
 | `show(NodeActor node[, Object options])` | Highlighters are hidden by default. Calling this method is what makes them visible. The first, mandatory, parameter should be a NodeActor. NodeActors are what the WalkerActor return. It's easy to get a NodeActor for an existing DOM node. For example `toolbox.walker.querySelector(toolbox.walker.rootNode, "css selector")` resolves to a NodeFront (the client-side version of the NodeActor) which can be used as the first parameter. The second, optional, parameter depends on the type of highlighter being used. |
 | `hide()`                                 | Hides the highlighter.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
 | `finalize()`                             | Destroys the highlighter.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
 
 ## Creating new highlighters
--- a/devtools/docs/tools/inspector-panel.md
+++ b/devtools/docs/tools/inspector-panel.md
@@ -86,13 +86,8 @@ This tab-level actor is the one the insp
 
 One of the important aspects of the inspector is the highlighters.
 You can find a lot more [documentation about highlighters here](highlighters.md).
 
 We don't just have 1 highlighter, we have a framework for highlighters:
 - a (chrome-only) platform API to inject markup in a native-anonymous node in content (that works on all targets)
 - a number of specific highlighter implementations (css transform, rect, selector, geometry, rulers, ...)
 - a CustomHighlighterActor to get instances of specific highlighters
-
-The entry point is toolbox-highlighter-utils.js:
-- get it with toolbox.highlighterUtils
-- use this to easily highlight any node in the page (with the usual box model highlighter),
-- also use this to instantiate an other specific highlighter
--- a/devtools/shared/fronts/highlighters.js
+++ b/devtools/shared/fronts/highlighters.js
@@ -10,33 +10,58 @@ const {
   highlighterSpec,
 } = require("devtools/shared/specs/highlighters");
 
 class HighlighterFront extends FrontClassWithSpec(highlighterSpec) {
   constructor(client, form) {
     super(client, form);
 
     this.isNodeFrontHighlighted = false;
+    this.isPicking = false;
   }
 
   // Update the object given a form representation off the wire.
   form(json) {
     this.actorID = json.actor;
     // FF42+ HighlighterActors starts exposing custom form, with traits object
     this.traits = json.traits || {};
   }
 
+  /**
+   * Start the element picker on the debuggee target.
+   * @param {Boolean} doFocus - Optionally focus the content area once the picker is
+   *                            activated.
+   * @return promise that resolves when the picker has started or immediately
+   * if it is already started
+   */
   pick(doFocus) {
+    if (this.isPicking) {
+      return null;
+    }
+    this.isPicking = true;
     if (doFocus && super.pickAndFocus) {
       return super.pickAndFocus();
     }
     return super.pick();
   }
 
   /**
+   * Stop the element picker.
+   * @return promise that resolves when the picker has stopped or immediately
+   * if it is already stopped
+   */
+  cancelPick() {
+    if (!this.isPicking) {
+      return Promise.resolve();
+    }
+    this.isPicking = false;
+    return super.cancelPick();
+  }
+
+  /**
    * Show the box model highlighter on a node in the content page.
    * The node needs to be a NodeFront, as defined by the inspector actor
    * @see devtools/server/actors/inspector/inspector.js
    * @param {NodeFront} nodeFront The node to highlight
    * @param {Object} options
    * @return A promise that resolves when the node has been highlighted
    */
   async highlight(nodeFront, options = {}) {
--- a/devtools/shared/fronts/inspector.js
+++ b/devtools/shared/fronts/inspector.js
@@ -1,15 +1,16 @@
 /* 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 Telemetry = require("devtools/client/shared/telemetry");
 const telemetry = new Telemetry();
+const { NodePicker } = require("devtools/shared/fronts/inspector/node-picker");
 const TELEMETRY_EYEDROPPER_OPENED = "DEVTOOLS_EYEDROPPER_OPENED_COUNT";
 const TELEMETRY_EYEDROPPER_OPENED_MENU = "DEVTOOLS_MENU_EYEDROPPER_OPENED_COUNT";
 const SHOW_ALL_ANONYMOUS_CONTENT_PREF = "devtools.inspector.showAllAnonymousContent";
 const SHOW_UA_SHADOW_ROOTS_PREF = "devtools.inspector.showUserAgentShadowRoots";
 
 const {
   FrontClassWithSpec,
   types,
@@ -452,16 +453,17 @@ class InspectorFront extends FrontClassW
   // async initialization
   async initialize() {
     await Promise.all([
       this._getWalker(),
       this._getHighlighter(),
     ]);
 
     this.selection = new Selection(this.walker);
+    this.nodePicker = new NodePicker(this.highlighter, this.walker, this.selection);
   }
 
   async _getWalker() {
     const showAllAnonymousContent = Services.prefs.getBoolPref(
       SHOW_ALL_ANONYMOUS_CONTENT_PREF);
     const showUserAgentShadowRoots = Services.prefs.getBoolPref(
       SHOW_UA_SHADOW_ROOTS_PREF);
     this.walker = await this.getWalker({
new file mode 100644
--- /dev/null
+++ b/devtools/shared/fronts/inspector/moz.build
@@ -0,0 +1,10 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DevToolsModules(
+    'node-picker.js',
+)
+
new file mode 100644
--- /dev/null
+++ b/devtools/shared/fronts/inspector/node-picker.js
@@ -0,0 +1,142 @@
+/* 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";
+loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
+
+/**
+ * Client-side NodePicker module.
+ * To be used by inspector front when it needs to select DOM elements.
+ */
+
+/**
+ * Get the NodePicker instance for an inspector front.
+ * The NodePicker wraps the highlighter so that it can interact with the
+ * walkerFront and selection api. The nodeFront is stateless, with the
+ * HighlighterFront managing it's own state.
+ *
+ * @param {highlighter} highlighterFront
+ * @param {walker} walkerFront
+ * @param {selection} selection api
+ * @return {Object} the NodePicker public API
+ */
+class NodePicker extends EventEmitter {
+  constructor(highlighter, walker, selection) {
+    super();
+    this.highlighter = highlighter;
+    this.walker = walker;
+    this.selection = selection;
+
+    this.cancel = this.cancel.bind(this);
+    this.start = this.start.bind(this);
+    this.stop = this.stop.bind(this);
+    this.togglePicker = this.togglePicker.bind(this);
+
+    this._onHovered = this._onHovered.bind(this);
+    this._onPicked = this._onPicked.bind(this);
+    this._onPreviewed = this._onPreviewed.bind(this);
+    this._onCanceled = this._onCanceled.bind(this);
+  }
+
+  /**
+   * Start/stop the element picker on the debuggee target.
+   * @param {Boolean} doFocus - Optionally focus the content area once the picker is
+   *                            activated.
+   * @return Promise that resolves when done
+   */
+  togglePicker(doFocus) {
+    if (this.highlighter.isPicking) {
+      return this.stop();
+    }
+    return this.start(doFocus);
+  }
+
+  /**
+   * Start the element picker on the debuggee target.
+   * This will request the inspector actor to start listening for mouse events
+   * on the target page to highlight the hovered/picked element.
+   * Depending on the server-side capabilities, this may fire events when nodes
+   * are hovered.
+   * @param {Boolean} doFocus - Optionally focus the content area once the picker is
+   *                            activated.
+   * @return Promise that resolves when the picker has started or immediately
+   * if it is already started
+   */
+  async start(doFocus) {
+    if (this.highlighter.isPicking) {
+      return null;
+    }
+    this.emit("picker-starting");
+    this.walker.on("picker-node-hovered", this._onHovered);
+    this.walker.on("picker-node-picked", this._onPicked);
+    this.walker.on("picker-node-previewed", this._onPreviewed);
+    this.walker.on("picker-node-canceled", this._onCanceled);
+
+    const picked = await this.highlighter.pick(doFocus);
+    this.emit("picker-started");
+    return picked;
+  }
+
+  /**
+   * Stop the element picker. Note that the picker is automatically stopped when
+   * an element is picked
+   * @return Promise that resolves when the picker has stopped or immediately
+   * if it is already stopped
+   */
+  async stop() {
+    if (!this.highlighter.isPicking) {
+      return;
+    }
+    await this.highlighter.cancelPick();
+    this.walker.off("picker-node-hovered", this._onHovered);
+    this.walker.off("picker-node-picked", this._onPicked);
+    this.walker.off("picker-node-previewed", this._onPreviewed);
+    this.walker.off("picker-node-canceled", this._onCanceled);
+    this.emit("picker-stopped");
+  }
+
+  /**
+   * Stop the picker, but also emit an event that the picker was canceled.
+   */
+  async cancel() {
+    await this.stop();
+    this.emit("picker-node-canceled");
+  }
+
+  /**
+   * When a node is hovered by the mouse when the highlighter is in picker mode
+   * @param {Object} data Information about the node being hovered
+   */
+  _onHovered(data) {
+    this.emit("picker-node-hovered", data.node);
+  }
+
+  /**
+   * When a node has been picked while the highlighter is in picker mode
+   * @param {Object} data Information about the picked node
+   */
+  _onPicked(data) {
+    this.selection.setNodeFront(data.node, { reason: "picker-node-picked" });
+    return this.stop();
+  }
+
+  /**
+   * When a node has been shift-clicked (previewed) while the highlighter is in
+   * picker mode
+   * @param {Object} data Information about the picked node
+   */
+  _onPreviewed(data) {
+    this.selection.setNodeFront(data.node, { reason: "picker-node-previewed" });
+  }
+
+  /**
+   * When the picker is canceled, stop the picker, and make sure the toolbox
+   * gets the focus.
+   */
+  _onCanceled() {
+    return this.cancel();
+  }
+}
+
+exports.NodePicker = NodePicker;
--- a/devtools/shared/fronts/moz.build
+++ b/devtools/shared/fronts/moz.build
@@ -1,16 +1,17 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 DIRS += [
     'addon',
+    'inspector',
     'targets',
 ]
 
 DevToolsModules(
     'accessibility.js',
     'actor-registry.js',
     'animation.js',
     'canvas.js',
--- a/devtools/shared/webconsole/test/test_jsterm_autocomplete.html
+++ b/devtools/shared/webconsole/test/test_jsterm_autocomplete.html
@@ -123,16 +123,21 @@
         doAutocompleteBracketSurroundedBySpaces,
       );
     }
 
     for (const test of tests) {
       await test(state.client);
     }
 
+    // Null out proxy1 and proxy2: the proxy handlers use scripted functions
+    // that can keep the debugger sandbox alive longer than necessary via their
+    // environment chain (due to the webconsole helper functions defined there).
+    await state.client.evaluateJSAsync(`this.proxy1 = null; this.proxy2 = null;`);
+
     await closeDebugger(state);
   }
 
   async function doAutocomplete1(client) {
     info("test autocomplete for 'window.foo'");
     let response = await client.autocomplete("window.foo");
     let matches = response.matches;
 
--- a/docshell/base/BrowsingContext.cpp
+++ b/docshell/base/BrowsingContext.cpp
@@ -5,24 +5,29 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/BrowsingContext.h"
 
 #include "mozilla/dom/ChromeBrowsingContext.h"
 #include "mozilla/dom/BrowsingContextBinding.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/Location.h"
+#include "mozilla/dom/LocationBinding.h"
+#include "mozilla/dom/WindowBinding.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/HashTable.h"
 #include "mozilla/Logging.h"
 #include "mozilla/StaticPtr.h"
 
 #include "nsDocShell.h"
+#include "nsGlobalWindowOuter.h"
 #include "nsContentUtils.h"
+#include "nsScriptError.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 static LazyLogModule gBrowsingContextLog("BrowsingContext");
 
 static StaticAutoPtr<BrowsingContext::Children> sRootBrowsingContexts;
@@ -45,24 +50,22 @@ static void Register(BrowsingContext* aB
 
 static void Sync(BrowsingContext* aBrowsingContext) {
   if (!XRE_IsContentProcess()) {
     return;
   }
 
   auto cc = ContentChild::GetSingleton();
   MOZ_DIAGNOSTIC_ASSERT(cc);
-  nsAutoString name;
-  aBrowsingContext->GetName(name);
   RefPtr<BrowsingContext> parent = aBrowsingContext->GetParent();
   BrowsingContext* opener = aBrowsingContext->GetOpener();
   cc->SendAttachBrowsingContext(BrowsingContextId(parent ? parent->Id() : 0),
                                 BrowsingContextId(opener ? opener->Id() : 0),
                                 BrowsingContextId(aBrowsingContext->Id()),
-                                name);
+                                aBrowsingContext->Name());
 }
 
 /* static */ void BrowsingContext::Init() {
   if (!sRootBrowsingContexts) {
     sRootBrowsingContexts = new BrowsingContext::Children();
     ClearOnShutdown(&sRootBrowsingContexts);
   }
 
@@ -146,17 +149,18 @@ static void Sync(BrowsingContext* aBrows
 BrowsingContext::BrowsingContext(BrowsingContext* aParent,
                                  BrowsingContext* aOpener,
                                  const nsAString& aName,
                                  uint64_t aBrowsingContextId, Type aType)
     : mType(aType),
       mBrowsingContextId(aBrowsingContextId),
       mParent(aParent),
       mOpener(aOpener),
-      mName(aName) {
+      mName(aName),
+      mClosed(false) {
   if (mParent) {
     mBrowsingContextGroup = mParent->mBrowsingContextGroup;
   } else if (mOpener) {
     mBrowsingContextGroup = mOpener->mBrowsingContextGroup;
   } else {
     mBrowsingContextGroup = new BrowsingContextGroup();
   }
 
@@ -320,10 +324,142 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
   }
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(BrowsingContext)
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(BrowsingContext, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(BrowsingContext, Release)
 
+void BrowsingContext::Location(JSContext* aCx,
+                               JS::MutableHandle<JSObject*> aLocation,
+                               OOMReporter& aError) {}
+
+void BrowsingContext::Close(CallerType aCallerType, ErrorResult& aError) {
+  // FIXME We need to set mClosed, but only once we're sending the
+  //       DOMWindowClose event (which happens in the process where the
+  //       document for this browsing context is loaded).
+  //       See https://bugzilla.mozilla.org/show_bug.cgi?id=1516343.
+  ContentChild* cc = ContentChild::GetSingleton();
+  cc->SendWindowClose(BrowsingContextId(mBrowsingContextId),
+                      aCallerType == CallerType::System);
+}
+
+void BrowsingContext::Focus(ErrorResult& aError) {
+  ContentChild* cc = ContentChild::GetSingleton();
+  cc->SendWindowFocus(BrowsingContextId(mBrowsingContextId));
+}
+
+void BrowsingContext::Blur(ErrorResult& aError) {
+  ContentChild* cc = ContentChild::GetSingleton();
+  cc->SendWindowBlur(BrowsingContextId(mBrowsingContextId));
+}
+
+Nullable<WindowProxyHolder> BrowsingContext::GetTop(ErrorResult& aError) {
+  // We never return null or throw an error, but the implementation in
+  // nsGlobalWindow does and we need to use the same signature.
+  BrowsingContext* bc = this;
+  BrowsingContext* parent;
+  while ((parent = bc->mParent)) {
+    bc = parent;
+  }
+  return WindowProxyHolder(bc);
+}
+
+void BrowsingContext::GetOpener(JSContext* aCx,
+                                JS::MutableHandle<JS::Value> aOpener,
+                                ErrorResult& aError) const {
+  auto* opener = GetOpener();
+  if (!opener) {
+    aOpener.setNull();
+    return;
+  }
+
+  if (!ToJSValue(aCx, WindowProxyHolder(opener), aOpener)) {
+    aError.NoteJSContextException(aCx);
+  }
+}
+
+Nullable<WindowProxyHolder> BrowsingContext::GetParent(
+    ErrorResult& aError) const {
+  // We never throw an error, but the implementation in nsGlobalWindow does and
+  // we need to use the same signature.
+  if (!mParent) {
+    return nullptr;
+  }
+  return WindowProxyHolder(mParent.get());
+}
+
+void BrowsingContext::PostMessageMoz(JSContext* aCx,
+                                     JS::Handle<JS::Value> aMessage,
+                                     const nsAString& aTargetOrigin,
+                                     const Sequence<JSObject*>& aTransfer,
+                                     nsIPrincipal& aSubjectPrincipal,
+                                     ErrorResult& aError) {
+  RefPtr<BrowsingContext> sourceBc;
+  PostMessageData data;
+  data.targetOrigin() = aTargetOrigin;
+  data.subjectPrincipal() = &aSubjectPrincipal;
+  RefPtr<nsGlobalWindowInner> callerInnerWindow;
+  if (!nsGlobalWindowOuter::GatherPostMessageData(
+          aCx, aTargetOrigin, getter_AddRefs(sourceBc), data.origin(),
+          getter_AddRefs(data.targetOriginURI()),
+          getter_AddRefs(data.callerPrincipal()),
+          getter_AddRefs(callerInnerWindow),
+          getter_AddRefs(data.callerDocumentURI()), aError)) {
+    return;
+  }
+  data.source() = BrowsingContextId(sourceBc->Id());
+  data.isFromPrivateWindow() =
+      callerInnerWindow &&
+      nsScriptErrorBase::ComputeIsFromPrivateWindow(callerInnerWindow);
+
+  JS::Rooted<JS::Value> transferArray(aCx);
+  aError = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransfer,
+                                                             &transferArray);
+  if (NS_WARN_IF(aError.Failed())) {
+    return;
+  }
+
+  ipc::StructuredCloneData message;
+  message.Write(aCx, aMessage, transferArray, aError);
+  if (NS_WARN_IF(aError.Failed())) {
+    return;
+  }
+
+  ContentChild* cc = ContentChild::GetSingleton();
+  ClonedMessageData messageData;
+  if (!message.BuildClonedMessageDataForChild(cc, messageData)) {
+    aError.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  cc->SendWindowPostMessage(BrowsingContextId(mBrowsingContextId), messageData,
+                            data);
+}
+
+void BrowsingContext::PostMessageMoz(JSContext* aCx,
+                                     JS::Handle<JS::Value> aMessage,
+                                     const WindowPostMessageOptions& aOptions,
+                                     nsIPrincipal& aSubjectPrincipal,
+                                     ErrorResult& aError) {
+  PostMessageMoz(aCx, aMessage, aOptions.mTargetOrigin, aOptions.mTransfer,
+                 aSubjectPrincipal, aError);
+}
+
+already_AddRefed<BrowsingContext> BrowsingContext::FindChildWithName(
+    const nsAString& aName) {
+  // FIXME https://bugzilla.mozilla.org/show_bug.cgi?id=1515646 will reimplement
+  //       this on top of the BC tree.
+  MOZ_ASSERT(mDocShell);
+  nsCOMPtr<nsIDocShellTreeItem> child;
+  mDocShell->FindChildWithName(aName, false, true, nullptr, nullptr,
+                               getter_AddRefs(child));
+  nsCOMPtr<nsIDocShell> childDS = do_QueryInterface(child);
+  RefPtr<BrowsingContext> bc;
+  if (childDS) {
+    childDS->GetBrowsingContext(getter_AddRefs(bc));
+  }
+  return bc.forget();
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/docshell/base/BrowsingContext.h
+++ b/docshell/base/BrowsingContext.h
@@ -5,32 +5,43 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_BrowsingContext_h
 #define mozilla_dom_BrowsingContext_h
 
 #include "mozilla/LinkedList.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/WeakPtr.h"
+#include "mozilla/dom/BindingDeclarations.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
+#include "nsIDocShell.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "nsWrapperCache.h"
 
-class nsIDocShell;
+class nsGlobalWindowOuter;
+class nsOuterWindowProxy;
 
 namespace mozilla {
 
+class ErrorResult;
 class LogModule;
+class OOMReporter;
 
 namespace dom {
 
 class BrowsingContext;
 class ContentParent;
+template <typename>
+struct Nullable;
+template <typename T>
+class Sequence;
+struct WindowPostMessageOptions;
+class WindowProxyHolder;
 
 // List of top-level or auxiliary BrowsingContexts
 class BrowsingContextGroup : public nsTArray<WeakPtr<BrowsingContext>> {
  public:
   NS_INLINE_DECL_REFCOUNTING(BrowsingContextGroup)
  private:
   ~BrowsingContextGroup() {}
 };
@@ -75,16 +86,22 @@ class BrowsingContext : public nsWrapper
       BrowsingContext* aParent, BrowsingContext* aOpener,
       const nsAString& aName, uint64_t aId, ContentParent* aOriginProcess);
 
   // Get the DocShell for this BrowsingContext if it is in-process, or
   // null if it's not.
   nsIDocShell* GetDocShell() { return mDocShell; }
   void SetDocShell(nsIDocShell* aDocShell);
 
+  // Get the outer window object for this BrowsingContext if it is in-process
+  // and still has a docshell, or null otherwise.
+  nsPIDOMWindowOuter* GetDOMWindow() const {
+    return mDocShell ? mDocShell->GetWindow() : nullptr;
+  }
+
   // Attach the current BrowsingContext to its parent, in both the child and the
   // parent process. BrowsingContext objects are created attached by default, so
   // this method need only be called when restoring cached BrowsingContext
   // objects.
   void Attach();
 
   // Detach the current BrowsingContext from its parent, in both the
   // child and the parent process.
@@ -96,61 +113,130 @@ class BrowsingContext : public nsWrapper
 
   // Determine if the current BrowsingContext was 'cached' by the logic in
   // CacheChildren.
   bool IsCached();
 
   // TODO(farre): We should sync changes from SetName to the parent
   // process. [Bug 1490303]
   void SetName(const nsAString& aName) { mName = aName; }
-  void GetName(nsAString& aName) { aName = mName; }
+  const nsString& Name() const { return mName; }
   bool NameEquals(const nsAString& aName) { return mName.Equals(aName); }
 
   bool IsContent() const { return mType == Type::Content; }
 
   uint64_t Id() const { return mBrowsingContextId; }
 
   BrowsingContext* GetParent() { return mParent; }
 
   void GetChildren(nsTArray<RefPtr<BrowsingContext>>& aChildren);
 
-  BrowsingContext* GetOpener() { return mOpener; }
+  BrowsingContext* GetOpener() const { return mOpener; }
 
   void SetOpener(BrowsingContext* aOpener);
 
   static void GetRootBrowsingContexts(
       nsTArray<RefPtr<BrowsingContext>>& aBrowsingContexts);
 
   nsISupports* GetParentObject() const;
   JSObject* WrapObject(JSContext* aCx,
                        JS::Handle<JSObject*> aGivenProto) override;
 
+  // Return the window proxy object that corresponds to this browsing context.
+  inline JSObject* GetWindowProxy() const { return mWindowProxy; }
+  // Set the window proxy object that corresponds to this browsing context.
+  void SetWindowProxy(JS::Handle<JSObject*> aWindowProxy) {
+    mWindowProxy = aWindowProxy;
+  }
+
   MOZ_DECLARE_WEAKREFERENCE_TYPENAME(BrowsingContext)
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(BrowsingContext)
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(BrowsingContext)
 
   using Children = nsTArray<RefPtr<BrowsingContext>>;
+  const Children& GetChildren() { return mChildren; }
+
+  // Window APIs that are cross-origin-accessible (from the HTML spec).
+  BrowsingContext* Window() { return Self(); }
+  BrowsingContext* Self() { return this; }
+  void Location(JSContext* aCx, JS::MutableHandle<JSObject*> aLocation,
+                OOMReporter& aError);
+  void Close(CallerType aCallerType, ErrorResult& aError);
+  bool GetClosed(ErrorResult&) { return mClosed; }
+  void Focus(ErrorResult& aError);
+  void Blur(ErrorResult& aError);
+  BrowsingContext* GetFrames(ErrorResult& aError) { return Self(); }
+  int32_t Length() const { return mChildren.Length(); }
+  Nullable<WindowProxyHolder> GetTop(ErrorResult& aError);
+  void GetOpener(JSContext* aCx, JS::MutableHandle<JS::Value> aOpener,
+                 ErrorResult& aError) const;
+  Nullable<WindowProxyHolder> GetParent(ErrorResult& aError) const;
+  void PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+                      const nsAString& aTargetOrigin,
+                      const Sequence<JSObject*>& aTransfer,
+                      nsIPrincipal& aSubjectPrincipal, ErrorResult& aError);
+  void PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+                      const WindowPostMessageOptions& aOptions,
+                      nsIPrincipal& aSubjectPrincipal, ErrorResult& aError);
+
+  already_AddRefed<BrowsingContext> FindChildWithName(const nsAString& aName);
+
+  JSObject* WrapObject(JSContext* aCx);
 
  protected:
   virtual ~BrowsingContext();
   BrowsingContext(BrowsingContext* aParent, BrowsingContext* aOpener,
                   const nsAString& aName, uint64_t aBrowsingContextId,
                   Type aType);
 
  private:
+  friend class ::nsOuterWindowProxy;
+  friend class ::nsGlobalWindowOuter;
+  // 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) {
+    if (mWindowProxy) {
+      MOZ_ASSERT(mWindowProxy == old);
+      mWindowProxy = obj;
+    }
+  }
+  // Clear the window proxy object that corresponds to this browsing context.
+  // This should be called if the window proxy object is finalized, or it can't
+  // reach its browsing context anymore.
+  void ClearWindowProxy() { mWindowProxy = nullptr; }
+
   // Type of BrowsingContent
   const Type mType;
 
   // Unique id identifying BrowsingContext
   const uint64_t mBrowsingContextId;
 
   RefPtr<BrowsingContextGroup> mBrowsingContextGroup;
   RefPtr<BrowsingContext> mParent;
   Children mChildren;
   WeakPtr<BrowsingContext> mOpener;
   nsCOMPtr<nsIDocShell> mDocShell;
   nsString mName;
+  // This is not a strong reference, but using a JS::Heap for that should be
+  // fine. The JSObject stored in here should be a proxy with a
+  // nsOuterWindowProxy handler, which will update the pointer from its
+  // objectMoved hook and clear it from its finalize hook.
+  JS::Heap<JSObject*> mWindowProxy;
+  bool mClosed;
 };
 
+/**
+ * Gets a WindowProxy object for a BrowsingContext that lives in a different
+ * process (creating the object if it doesn't already exist). The WindowProxy
+ * object will be in the compartment that aCx is currently in. This should only
+ * be called if aContext doesn't hold a docshell, otherwise the BrowsingContext
+ * lives in this process, and a same-process WindowProxy should be used (see
+ * nsGlobalWindowOuter). This should only be called by bindings code, ToJSValue
+ * is the right API to get a WindowProxy for a BrowsingContext.
+ */
+extern bool GetRemoteOuterWindowProxy(JSContext* aCx, BrowsingContext* aContext,
+                                      JS::MutableHandle<JSObject*> aRetVal);
+
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // !defined(mozilla_dom_BrowsingContext_h)
--- a/docshell/base/ChromeBrowsingContext.h
+++ b/docshell/base/ChromeBrowsingContext.h
@@ -29,16 +29,17 @@ class ChromeBrowsingContext final : publ
   static void CleanupContexts(uint64_t aProcessId);
   static already_AddRefed<ChromeBrowsingContext> Get(uint64_t aId);
   static ChromeBrowsingContext* Cast(BrowsingContext* aContext);
   static const ChromeBrowsingContext* Cast(const BrowsingContext* aContext);
 
   bool IsOwnedByProcess(uint64_t aProcessId) const {
     return mProcessId == aProcessId;
   }
+  uint64_t OwnerProcessId() const { return mProcessId; }
 
   void GetWindowGlobals(nsTArray<RefPtr<WindowGlobalParent>>& aWindows);
 
   // Called by WindowGlobalParent to register and unregister window globals.
   void RegisterWindowGlobal(WindowGlobalParent* aGlobal);
   void UnregisterWindowGlobal(WindowGlobalParent* aGlobal);
 
   // The current active WindowGlobal.
--- a/docshell/base/moz.build
+++ b/docshell/base/moz.build
@@ -98,16 +98,17 @@ UNIFIED_SOURCES += [
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/docshell/shistory',
     '/dom/base',
+    '/dom/bindings',
     '/layout/base',
     '/layout/generic',
     '/layout/style',
     '/layout/xul',
     '/netwerk/base',
     '/netwerk/protocol/viewsource',
     '/toolkit/components/browser',
     '/toolkit/components/find',
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -2304,17 +2304,17 @@ nsDocShell::NotifyScrollObservers() {
 }
 
 //*****************************************************************************
 // nsDocShell::nsIDocShellTreeItem
 //*****************************************************************************
 
 NS_IMETHODIMP
 nsDocShell::GetName(nsAString& aName) {
-  mBrowsingContext->GetName(aName);
+  aName = mBrowsingContext->Name();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::SetName(const nsAString& aName) {
   mBrowsingContext->SetName(aName);
   return NS_OK;
 }
@@ -13416,17 +13416,13 @@ nsDocShell::GetColorMatrix(uint32_t* aMa
     memcpy(*aMatrix, mColorMatrix->components, 20 * sizeof(float));
   }
 
   return NS_OK;
 }
 
 bool nsDocShell::IsForceReloading() { return IsForceReloadType(mLoadType); }
 
-BrowsingContext* nsDocShell::GetBrowsingContext() const {
-  return mBrowsingContext;
-}
-
 NS_IMETHODIMP
 nsDocShell::GetBrowsingContext(BrowsingContext** aBrowsingContext) {
   *aBrowsingContext = do_AddRef(mBrowsingContext).take();
   return NS_OK;
 }
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -378,20 +378,23 @@ class nsDocShell final : public nsDocLoa
   static nsDocShell* Cast(nsIDocShell* aDocShell) {
     return static_cast<nsDocShell*>(aDocShell);
   }
 
   // Returns true if the current load is a force reload (started by holding
   // shift while triggering reload)
   bool IsForceReloading();
 
-  /**
-   * Native getter for a DocShell's BrowsingContext.
-   */
-  mozilla::dom::BrowsingContext* GetBrowsingContext() const;
+  mozilla::dom::BrowsingContext* GetBrowsingContext() const {
+    return mBrowsingContext;
+  }
+  mozilla::dom::BrowsingContext* GetWindowProxy() {
+    EnsureScriptEnvironment();
+    return mBrowsingContext;
+  }
 
   /**
    * Loads the given URI. See comments on nsDocShellLoadState members for more
    * information on information used. aDocShell and aRequest come from
    * onLinkClickSync, which is triggered during form submission.
    */
   nsresult InternalLoad(nsDocShellLoadState* aLoadState,
                         nsIDocShell** aDocShell, nsIRequest** aRequest);
--- a/dom/base/ContentFrameMessageManager.h
+++ b/dom/base/ContentFrameMessageManager.h
@@ -10,16 +10,20 @@
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/dom/MessageManagerGlobal.h"
 #include "nsContentUtils.h"
 #include "xpcpublic.h"
 
 namespace mozilla {
 namespace dom {
 
+template <typename>
+struct Nullable;
+class WindowProxyHolder;
+
 #define NS_CONTENTFRAMEMESSAGEMANAGER_IID            \
   {                                                  \
     0x97e192a6, 0xab7a, 0x4c8f, {                    \
       0xb7, 0xdd, 0xf7, 0xec, 0x36, 0x38, 0x71, 0xb5 \
     }                                                \
   }
 
 /**
@@ -28,18 +32,17 @@ namespace dom {
 class ContentFrameMessageManager : public DOMEventTargetHelper,
                                    public MessageManagerGlobal {
  public:
   using DOMEventTargetHelper::AddRef;
   using DOMEventTargetHelper::Release;
 
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_CONTENTFRAMEMESSAGEMANAGER_IID)
 
-  virtual already_AddRefed<nsPIDOMWindowOuter> GetContent(
-      ErrorResult& aError) = 0;
+  virtual Nullable<WindowProxyHolder> GetContent(ErrorResult& aError) = 0;
   virtual already_AddRefed<nsIDocShell> GetDocShell(ErrorResult& aError) = 0;
   virtual already_AddRefed<nsIEventTarget> GetTabEventTarget() = 0;
   virtual uint64_t ChromeOuterWindowID() = 0;
 
   nsFrameMessageManager* GetMessageManager() { return mMessageManager; }
   void DisconnectMessageManager() {
     mMessageManager->Disconnect();
     mMessageManager = nullptr;
--- a/dom/base/InProcessTabChildMessageManager.cpp
+++ b/dom/base/InProcessTabChildMessageManager.cpp
@@ -1,29 +1,31 @@
 /* -*- 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 "InProcessTabChildMessageManager.h"
 #include "nsContentUtils.h"
+#include "nsDocShell.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIComponentManager.h"
 #include "nsIServiceManager.h"
 #include "nsComponentManagerUtils.h"
 #include "nsFrameLoader.h"
 #include "xpcpublic.h"
 #include "nsIMozBrowserFrame.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/dom/ChromeMessageSender.h"
 #include "mozilla/dom/MessageManagerBinding.h"
 #include "mozilla/dom/SameProcessMessageQueue.h"
 #include "mozilla/dom/ScriptLoader.h"
+#include "mozilla/dom/WindowProxyHolder.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::dom::ipc;
 
 bool InProcessTabChildMessageManager::DoSendBlockingMessage(
     JSContext* aCx, const nsAString& aMessage, StructuredCloneData& aData,
     JS::Handle<JSObject*> aCpows, nsIPrincipal* aPrincipal,
@@ -71,17 +73,17 @@ nsresult InProcessTabChildMessageManager
     return rv;
   }
 
   queue->Push(ev);
   return NS_OK;
 }
 
 InProcessTabChildMessageManager::InProcessTabChildMessageManager(
-    nsIDocShell* aShell, nsIContent* aOwner, nsFrameMessageManager* aChrome)
+    nsDocShell* aShell, nsIContent* aOwner, nsFrameMessageManager* aChrome)
     : ContentFrameMessageManager(new nsFrameMessageManager(this)),
       mDocShell(aShell),
       mLoadingScript(false),
       mPreventEventsEscaping(false),
       mOwner(aOwner),
       mChromeMessageManager(aChrome) {
   mozilla::HoldJSObjects(this);
 
@@ -142,39 +144,37 @@ JSObject* InProcessTabChildMessageManage
   return ContentFrameMessageManager_Binding::Wrap(aCx, this, aGivenProto);
 }
 
 void InProcessTabChildMessageManager::CacheFrameLoader(
     nsFrameLoader* aFrameLoader) {
   mFrameLoader = aFrameLoader;
 }
 
-already_AddRefed<nsPIDOMWindowOuter>
-InProcessTabChildMessageManager::GetContent(ErrorResult& aError) {
-  nsCOMPtr<nsPIDOMWindowOuter> content;
-  if (mDocShell) {
-    content = mDocShell->GetWindow();
+Nullable<WindowProxyHolder> InProcessTabChildMessageManager::GetContent(
+    ErrorResult& aError) {
+  if (!mDocShell) {
+    return nullptr;
   }
-  return content.forget();
+  return WindowProxyHolder(mDocShell->GetBrowsingContext());
 }
 
 already_AddRefed<nsIEventTarget>
 InProcessTabChildMessageManager::GetTabEventTarget() {
   nsCOMPtr<nsIEventTarget> target = GetMainThreadEventTarget();
   return target.forget();
 }
 
 uint64_t InProcessTabChildMessageManager::ChromeOuterWindowID() {
   if (!mDocShell) {
     return 0;
   }
 
-  nsCOMPtr<nsIDocShellTreeItem> item = mDocShell;
   nsCOMPtr<nsIDocShellTreeItem> root;
-  nsresult rv = item->GetRootTreeItem(getter_AddRefs(root));
+  nsresult rv = mDocShell->GetRootTreeItem(getter_AddRefs(root));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return 0;
   }
 
   nsPIDOMWindowOuter* topWin = root->GetWindow();
   if (!topWin) {
     return 0;
   }
--- a/dom/base/InProcessTabChildMessageManager.h
+++ b/dom/base/InProcessTabChildMessageManager.h
@@ -12,17 +12,17 @@
 #include "mozilla/RefPtr.h"
 #include "mozilla/dom/ContentFrameMessageManager.h"
 #include "nsCOMPtr.h"
 #include "nsFrameMessageManager.h"
 #include "nsIScriptContext.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsIScriptContext.h"
 #include "nsIClassInfo.h"
-#include "nsIDocShell.h"
+#include "nsDocShell.h"
 #include "nsCOMArray.h"
 #include "nsIRunnable.h"
 #include "nsWeakReference.h"
 
 namespace mozilla {
 class EventChainPreVisitor;
 
 namespace dom {
@@ -37,22 +37,22 @@ class InProcessTabChildMessageManager fi
     : public ContentFrameMessageManager,
       public nsMessageManagerScriptExecutor,
       public nsIInProcessContentFrameMessageManager,
       public nsSupportsWeakReference,
       public mozilla::dom::ipc::MessageManagerCallback {
   typedef mozilla::dom::ipc::StructuredCloneData StructuredCloneData;
 
  private:
-  InProcessTabChildMessageManager(nsIDocShell* aShell, nsIContent* aOwner,
+  InProcessTabChildMessageManager(nsDocShell* aShell, nsIContent* aOwner,
                                   nsFrameMessageManager* aChrome);
 
  public:
   static already_AddRefed<InProcessTabChildMessageManager> Create(
-      nsIDocShell* aShell, nsIContent* aOwner, nsFrameMessageManager* aChrome) {
+      nsDocShell* aShell, nsIContent* aOwner, nsFrameMessageManager* aChrome) {
     RefPtr<InProcessTabChildMessageManager> mm =
         new InProcessTabChildMessageManager(aShell, aOwner, aChrome);
 
     NS_ENSURE_TRUE(mm->Init(), nullptr);
 
     return mm.forget();
   }
 
@@ -60,22 +60,20 @@ class InProcessTabChildMessageManager fi
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(
       InProcessTabChildMessageManager, DOMEventTargetHelper)
 
   void MarkForCC();
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
-  virtual already_AddRefed<nsPIDOMWindowOuter> GetContent(
-      ErrorResult& aError) override;
+  Nullable<WindowProxyHolder> GetContent(ErrorResult& aError) override;
   virtual already_AddRefed<nsIDocShell> GetDocShell(
       ErrorResult& aError) override {
-    nsCOMPtr<nsIDocShell> docShell(mDocShell);
-    return docShell.forget();
+    return do_AddRef(mDocShell);
   }
   virtual already_AddRefed<nsIEventTarget> GetTabEventTarget() override;
   virtual uint64_t ChromeOuterWindowID() override;
 
   NS_FORWARD_SAFE_NSIMESSAGESENDER(mMessageManager)
 
   NS_DECL_NSIINPROCESSCONTENTFRAMEMESSAGEMANAGER
 
@@ -116,17 +114,17 @@ class InProcessTabChildMessageManager fi
     mChromeMessageManager = aParent;
   }
 
   already_AddRefed<nsFrameLoader> GetFrameLoader();
 
  protected:
   virtual ~InProcessTabChildMessageManager();
 
-  nsCOMPtr<nsIDocShell> mDocShell;
+  RefPtr<nsDocShell> mDocShell;
   bool mLoadingScript;
 
   // Is this the message manager for an in-process <iframe mozbrowser>? This
   // affects where events get sent, so it affects GetEventTargetParent.
   bool mIsBrowserFrame;
   bool mPreventEventsEscaping;
 
   // We keep a strong reference to the frameloader after we've started
--- a/dom/base/Location.h
+++ b/dom/base/Location.h
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_Location_h
 #define mozilla_dom_Location_h
 
 #include "js/TypeDecls.h"
 #include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BrowsingContext.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIWeakReferenceUtils.h"
 #include "nsPIDOMWindow.h"
 #include "nsString.h"
 #include "nsWrapperCache.h"
 
 class nsIDocShell;
 class nsIURI;
@@ -23,16 +24,18 @@ namespace mozilla {
 namespace dom {
 
 //*****************************************************************************
 // Location: Script "location" object
 //*****************************************************************************
 
 class Location final : public nsISupports, public nsWrapperCache {
  public:
+  typedef Location RemoteProxy;
+
   Location(nsPIDOMWindowInner* aWindow, nsIDocShell* aDocShell);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Location)
 
   // WebIDL API:
   void Assign(const nsAString& aUrl, nsIPrincipal& aSubjectPrincipal,
               ErrorResult& aError);
--- a/dom/base/PostMessageEvent.cpp
+++ b/dom/base/PostMessageEvent.cpp
@@ -14,56 +14,61 @@
 #include "mozilla/dom/MessageEventBinding.h"
 #include "mozilla/dom/MessagePort.h"
 #include "mozilla/dom/MessagePortBinding.h"
 #include "mozilla/dom/PMessagePort.h"
 #include "mozilla/dom/StructuredCloneTags.h"
 #include "mozilla/dom/UnionConversions.h"
 #include "mozilla/EventDispatcher.h"
 #include "nsContentUtils.h"
+#include "nsDocShell.h"
 #include "nsGlobalWindow.h"
+#include "nsIConsoleService.h"
 #include "nsIPresShell.h"
 #include "nsIPrincipal.h"
 #include "nsIScriptError.h"
+#include "nsNetUtil.h"
 #include "nsPresContext.h"
 #include "nsQueryObject.h"
 
 namespace mozilla {
 namespace dom {
 
-PostMessageEvent::PostMessageEvent(nsGlobalWindowOuter* aSource,
+PostMessageEvent::PostMessageEvent(BrowsingContext* aSource,
                                    const nsAString& aCallerOrigin,
                                    nsGlobalWindowOuter* aTargetWindow,
                                    nsIPrincipal* aProvidedPrincipal,
-                                   nsIDocument* aSourceDocument)
+                                   const Maybe<uint64_t>& aCallerWindowID,
+                                   nsIURI* aCallerDocumentURI,
+                                   bool aIsFromPrivateWindow)
     : Runnable("dom::PostMessageEvent"),
-      StructuredCloneHolder(CloningSupported, TransferringSupported,
-                            StructuredCloneScope::SameProcessSameThread),
       mSource(aSource),
       mCallerOrigin(aCallerOrigin),
       mTargetWindow(aTargetWindow),
       mProvidedPrincipal(aProvidedPrincipal),
-      mSourceDocument(aSourceDocument) {}
+      mCallerWindowID(aCallerWindowID),
+      mCallerDocumentURI(aCallerDocumentURI),
+      mIsFromPrivateWindow(aIsFromPrivateWindow) {}
 
 PostMessageEvent::~PostMessageEvent() {}
 
 NS_IMETHODIMP
 PostMessageEvent::Run() {
   // Note: We don't init this AutoJSAPI with targetWindow, because we do not
   // want exceptions during message deserialization to trigger error events on
   // targetWindow.
   AutoJSAPI jsapi;
   jsapi.Init();
   JSContext* cx = jsapi.cx();
 
-  // The document is just used for the principal mismatch error message below.
-  // Use a stack variable so mSourceDocument is not held onto after this method
-  // finishes, regardless of the method outcome.
-  nsCOMPtr<nsIDocument> sourceDocument;
-  sourceDocument.swap(mSourceDocument);
+  // The document URI is just used for the principal mismatch error message
+  // below. Use a stack variable so mCallerDocumentURI is not held onto after
+  // this method finishes, regardless of the method outcome.
+  nsCOMPtr<nsIURI> callerDocumentURI;
+  callerDocumentURI.swap(mCallerDocumentURI);
 
   // If we bailed before this point we're going to leak mMessage, but
   // that's probably better than crashing.
 
   RefPtr<nsGlobalWindowInner> targetWindow;
   if (mTargetWindow->IsClosedOrClosing() ||
       !(targetWindow = mTargetWindow->GetCurrentInnerWindowInternal()) ||
       targetWindow->IsDying())
@@ -109,44 +114,78 @@ PostMessageEvent::Run() {
       nsAutoString providedOrigin, targetOrigin;
       nsresult rv = nsContentUtils::GetUTFOrigin(targetPrin, targetOrigin);
       NS_ENSURE_SUCCESS(rv, rv);
       rv = nsContentUtils::GetUTFOrigin(mProvidedPrincipal, providedOrigin);
       NS_ENSURE_SUCCESS(rv, rv);
 
       const char16_t* params[] = {providedOrigin.get(), targetOrigin.get()};
 
-      nsContentUtils::ReportToConsole(
-          nsIScriptError::errorFlag, NS_LITERAL_CSTRING("DOM Window"),
-          sourceDocument, nsContentUtils::eDOM_PROPERTIES,
-          "TargetPrincipalDoesNotMatch", params, ArrayLength(params));
+      nsAutoString errorText;
+      nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+                                            "TargetPrincipalDoesNotMatch",
+                                            params, errorText);
+
+      nsCOMPtr<nsIScriptError> errorObject =
+          do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
+      NS_ENSURE_SUCCESS(rv, rv);
 
-      return NS_OK;
+      if (mCallerWindowID.isSome()) {
+        rv = errorObject->InitWithSourceURI(
+            errorText, callerDocumentURI, EmptyString(), 0, 0,
+            nsIScriptError::errorFlag, "DOM Window", mCallerWindowID.value());
+      } else {
+        nsString uriSpec;
+        rv = NS_GetSanitizedURIStringFromURI(callerDocumentURI, uriSpec);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        rv = errorObject->Init(errorText, uriSpec, EmptyString(), 0, 0,
+                               nsIScriptError::errorFlag, "DOM Window",
+                               mIsFromPrivateWindow);
+      }
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      nsCOMPtr<nsIConsoleService> consoleService =
+          do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      return consoleService->LogMessage(errorObject);
     }
   }
 
   IgnoredErrorResult rv;
   JS::Rooted<JS::Value> messageData(cx);
   nsCOMPtr<mozilla::dom::EventTarget> eventTarget =
       do_QueryObject(targetWindow);
 
-  Read(targetWindow->AsInner(), cx, &messageData, rv);
+  StructuredCloneHolder* holder;
+  if (mHolder.constructed<StructuredCloneHolder>()) {
+    mHolder.ref<StructuredCloneHolder>().Read(targetWindow->AsInner(), cx,
+                                              &messageData, rv);
+    holder = &mHolder.ref<StructuredCloneHolder>();
+  } else {
+    MOZ_ASSERT(mHolder.constructed<ipc::StructuredCloneData>());
+    mHolder.ref<ipc::StructuredCloneData>().Read(cx, &messageData, rv);
+    holder = &mHolder.ref<ipc::StructuredCloneData>();
+  }
   if (NS_WARN_IF(rv.Failed())) {
     DispatchError(cx, targetWindow, eventTarget);
     return NS_OK;
   }
 
   // Create the event
   RefPtr<MessageEvent> event = new MessageEvent(eventTarget, nullptr, nullptr);
 
   Nullable<WindowProxyOrMessagePortOrServiceWorker> source;
-  source.SetValue().SetAsWindowProxy() = mSource ? mSource->AsOuter() : nullptr;
+  if (mSource) {
+    source.SetValue().SetAsWindowProxy() = mSource;
+  }
 
   Sequence<OwningNonNull<MessagePort>> ports;
-  if (!TakeTransferredPortsAsSequence(ports)) {
+  if (!holder->TakeTransferredPortsAsSequence(ports)) {
     DispatchError(cx, targetWindow, eventTarget);
     return NS_OK;
   }
 
   event->InitMessageEvent(nullptr, NS_LITERAL_STRING("message"), CanBubble::eNo,
                           Cancelable::eNo, messageData, mCallerOrigin,
                           EmptyString(), source, ports);
 
@@ -158,17 +197,17 @@ void PostMessageEvent::DispatchError(JSC
                                      nsGlobalWindowInner* aTargetWindow,
                                      mozilla::dom::EventTarget* aEventTarget) {
   RootedDictionary<MessageEventInit> init(aCx);
   init.mBubbles = false;
   init.mCancelable = false;
   init.mOrigin = mCallerOrigin;
 
   if (mSource) {
-    init.mSource.SetValue().SetAsWindowProxy() = mSource->AsOuter();
+    init.mSource.SetValue().SetAsWindowProxy() = mSource;
   }
 
   RefPtr<Event> event = MessageEvent::Constructor(
       aEventTarget, NS_LITERAL_STRING("messageerror"), init);
   Dispatch(aTargetWindow, event);
 }
 
 void PostMessageEvent::Dispatch(nsGlobalWindowInner* aTargetWindow,
--- a/dom/base/PostMessageEvent.h
+++ b/dom/base/PostMessageEvent.h
@@ -3,54 +3,102 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_PostMessageEvent_h
 #define mozilla_dom_PostMessageEvent_h
 
 #include "mozilla/dom/Event.h"
+#include "mozilla/dom/ipc/StructuredCloneData.h"
 #include "mozilla/dom/StructuredCloneHolder.h"
 #include "nsCOMPtr.h"
+#include "mozilla/MaybeOneOf.h"
 #include "mozilla/RefPtr.h"
 #include "nsTArray.h"
 #include "nsThreadUtils.h"
 
 class nsGlobalWindowOuter;
 class nsGlobalWindowInner;
 class nsIDocument;
 class nsIPrincipal;
 
 namespace mozilla {
 namespace dom {
 
+class BrowsingContext;
+
 /**
  * Class used to represent events generated by calls to Window.postMessage,
  * which asynchronously creates and dispatches events.
  */
-class PostMessageEvent final : public Runnable, public StructuredCloneHolder {
+class PostMessageEvent final : public Runnable {
  public:
   NS_DECL_NSIRUNNABLE
 
-  PostMessageEvent(nsGlobalWindowOuter* aSource, const nsAString& aCallerOrigin,
+  // aCallerWindowID should not be 0.
+  PostMessageEvent(BrowsingContext* aSource, const nsAString& aCallerOrigin,
+                   nsGlobalWindowOuter* aTargetWindow,
+                   nsIPrincipal* aProvidedPrincipal, uint64_t aCallerWindowID,
+                   nsIURI* aCallerDocumentURI)
+      : PostMessageEvent(aSource, aCallerOrigin, aTargetWindow,
+                         aProvidedPrincipal, Some(aCallerWindowID),
+                         aCallerDocumentURI, false) {}
+
+  // To be used if there is no WindowID for the PostMessage caller's window (for
+  // example because it lives in a different process).
+  PostMessageEvent(BrowsingContext* aSource, const nsAString& aCallerOrigin,
+                   nsGlobalWindowOuter* aTargetWindow,
+                   nsIPrincipal* aProvidedPrincipal, nsIURI* aCallerDocumentURI,
+                   bool aIsFromPrivateWindow)
+      : PostMessageEvent(aSource, aCallerOrigin, aTargetWindow,
+                         aProvidedPrincipal, Nothing(), aCallerDocumentURI,
+                         aIsFromPrivateWindow) {}
+
+  void Write(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+             JS::Handle<JS::Value> aTransfer, ErrorResult& aError) {
+    mHolder.construct<StructuredCloneHolder>(
+        StructuredCloneHolder::CloningSupported,
+        StructuredCloneHolder::TransferringSupported,
+        JS::StructuredCloneScope::SameProcessSameThread);
+    mHolder.ref<StructuredCloneHolder>().Write(aCx, aMessage, aTransfer,
+                                               JS::CloneDataPolicy(), aError);
+  }
+  void UnpackFrom(const ClonedMessageData& aMessageData) {
+    mHolder.construct<ipc::StructuredCloneData>();
+    // FIXME Want to steal!
+    //       See https://bugzilla.mozilla.org/show_bug.cgi?id=1516349.
+    mHolder.ref<ipc::StructuredCloneData>().CopyFromClonedMessageDataForChild(
+        aMessageData);
+  }
+
+ private:
+  PostMessageEvent(BrowsingContext* aSource, const nsAString& aCallerOrigin,
                    nsGlobalWindowOuter* aTargetWindow,
                    nsIPrincipal* aProvidedPrincipal,
-                   nsIDocument* aSourceDocument);
-
- private:
+                   const Maybe<uint64_t>& aCallerWindowID,
+                   nsIURI* aCallerDocumentURI, bool aIsFromPrivateWindow);
   ~PostMessageEvent();
 
   void Dispatch(nsGlobalWindowInner* aTargetWindow, Event* aEvent);
 
   void DispatchError(JSContext* aCx, nsGlobalWindowInner* aTargetWindow,
                      mozilla::dom::EventTarget* aEventTarget);
 
-  RefPtr<nsGlobalWindowOuter> mSource;
+  RefPtr<BrowsingContext> mSource;
   nsString mCallerOrigin;
   RefPtr<nsGlobalWindowOuter> mTargetWindow;
   nsCOMPtr<nsIPrincipal> mProvidedPrincipal;
-  nsCOMPtr<nsIDocument> mSourceDocument;
+  // If the postMessage call was made on a WindowProxy whose Window lives in a
+  // separate process then mHolder will contain a StructuredCloneData, else
+  // it'll contain a StructuredCloneHolder.
+  MaybeOneOf<StructuredCloneHolder, ipc::StructuredCloneData> mHolder;
+  Maybe<uint64_t> mCallerWindowID;
+  nsCOMPtr<nsIURI> mCallerDocumentURI;
+  // This is only set to a relevant value if mCallerWindowID doesn't contain a
+  // value.
+  bool mIsFromPrivateWindow;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_PostMessageEvent_h
new file mode 100644
--- /dev/null
+++ b/dom/base/RemoteOuterWindowProxy.cpp
@@ -0,0 +1,194 @@
+/* -*- 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 "AccessCheck.h"
+#include "js/Proxy.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/RemoteObjectProxy.h"
+#include "mozilla/dom/WindowBinding.h"
+#include "xpcprivate.h"
+
+namespace mozilla {
+namespace dom {
+
+/**
+ * RemoteOuterWindowProxy is the proxy handler for the WindowProxy objects for
+ * Window objects that live in a different process.
+ *
+ * RemoteOuterWindowProxy holds a BrowsingContext, which is cycle collected.
+ * However, RemoteOuterWindowProxy only holds BrowsingContexts that don't have a
+ * reference to a docshell, so there's no need to declare the edge from
+ * RemoteOuterWindowProxy to its BrowsingContext to the cycle collector.
+ *
+ * FIXME Verify that this is correct:
+ *       https://bugzilla.mozilla.org/show_bug.cgi?id=1516350.
+ */
+
+class RemoteOuterWindowProxy
+    : public RemoteObjectProxy<BrowsingContext,
+                               Window_Binding::sCrossOriginAttributes,
+                               Window_Binding::sCrossOriginMethods> {
+ public:
+  constexpr RemoteOuterWindowProxy()
+      : RemoteObjectProxy(prototypes::id::Window) {}
+
+  // Standard internal methods
+  bool getOwnPropertyDescriptor(
+      JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
+      JS::MutableHandle<JS::PropertyDescriptor> aDesc) const final;
+  bool ownPropertyKeys(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+                       JS::AutoIdVector& aProps) const final;
+
+  // SpiderMonkey extensions
+  bool getOwnEnumerablePropertyKeys(JSContext* cx, JS::Handle<JSObject*> proxy,
+                                    JS::AutoIdVector& props) const final;
+  void finalize(JSFreeOp* aFop, JSObject* aProxy) const final;
+  const char* className(JSContext* aCx,
+                        JS::Handle<JSObject*> aProxy) const final;
+};
+
+static const RemoteOuterWindowProxy sSingleton;
+
+// Give RemoteOuterWindowProxyClass 2 reserved slots, like the other wrappers,
+// so JSObject::swap can swap it with CrossCompartmentWrappers without requiring
+// malloc.
+const js::Class RemoteOuterWindowProxyClass =
+    PROXY_CLASS_DEF("Proxy", JSCLASS_HAS_RESERVED_SLOTS(2));
+
+bool GetRemoteOuterWindowProxy(JSContext* aCx, BrowsingContext* aContext,
+                               JS::MutableHandle<JSObject*> aRetVal) {
+  MOZ_ASSERT(!aContext->GetDocShell(),
+             "Why are we creating a RemoteOuterWindowProxy?");
+
+  xpc::CompartmentPrivate* priv =
+      xpc::CompartmentPrivate::Get(JS::CurrentGlobalOrNull(aCx));
+  xpc::CompartmentPrivate::RemoteProxyMap& map = priv->GetRemoteProxyMap();
+  auto result = map.lookupForAdd(aContext);
+  if (result) {
+    aRetVal.set(result->value());
+    return true;
+  }
+
+  JS::Rooted<JSObject*> obj(
+      aCx, sSingleton.CreateProxyObject(aCx, aContext,
+                                        &RemoteOuterWindowProxyClass));
+  if (!obj) {
+    return false;
+  }
+  NS_ADDREF(aContext);
+
+  if (!map.add(result, aContext, obj)) {
+    JS_ReportOutOfMemory(aCx);
+    return false;
+  }
+
+  aRetVal.set(obj);
+  return true;
+}
+
+static BrowsingContext* GetBrowsingContext(JSObject* aProxy) {
+  MOZ_ASSERT(IsRemoteObjectProxy(aProxy, prototypes::id::Window));
+  return static_cast<BrowsingContext*>(
+      RemoteObjectProxyBase::GetNative(aProxy));
+}
+
+bool WrapResult(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+                BrowsingContext* aResult, unsigned attrs,
+                JS::MutableHandle<JS::PropertyDescriptor> aDesc) {
+  JS::Rooted<JS::Value> v(aCx);
+  if (!ToJSValue(aCx, WindowProxyHolder(aResult), &v)) {
+    return false;
+  }
+  aDesc.object().set(aProxy);
+  aDesc.setDataDescriptor(v, attrs);
+  return true;
+}
+
+bool RemoteOuterWindowProxy::getOwnPropertyDescriptor(
+    JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
+    JS::MutableHandle<JS::PropertyDescriptor> aDesc) const {
+  BrowsingContext* bc = GetBrowsingContext(aProxy);
+  uint32_t index = GetArrayIndexFromId(aId);
+  if (IsArrayIndex(index)) {
+    const BrowsingContext::Children& children = bc->GetChildren();
+    if (index < children.Length()) {
+      return WrapResult(aCx, aProxy, children[index],
+                        JSPROP_READONLY | JSPROP_ENUMERATE, aDesc);
+    }
+    return ReportCrossOriginDenial(aCx, aId, NS_LITERAL_CSTRING("access"));
+  }
+
+  bool ok = RemoteObjectProxy::getOwnPropertyDescriptorInternal(aCx, aProxy,
+                                                                aId, aDesc);
+  if (!ok || aDesc.object()) {
+    return ok;
+  }
+
+  if (JSID_IS_STRING(aId)) {
+    nsAutoJSString str;
+    if (!str.init(aCx, JSID_TO_STRING(aId))) {
+      return false;
+    }
+
+    for (BrowsingContext* child : bc->GetChildren()) {
+      if (child->NameEquals(str)) {
+        return WrapResult(aCx, aProxy, child, JSPROP_READONLY, aDesc);
+      }
+    }
+  }
+
+  return getOwnPropertyDescriptorTail(aCx, aProxy, aId, aDesc);
+}
+
+bool AppendIndexedPropertyNames(JSContext* aCx, BrowsingContext* aContext,
+                                JS::AutoIdVector& aIndexedProps) {
+  int32_t length = aContext->GetChildren().Length();
+  if (!aIndexedProps.reserve(aIndexedProps.length() + length)) {
+    return false;
+  }
+
+  for (int32_t i = 0; i < length; ++i) {
+    aIndexedProps.infallibleAppend(INT_TO_JSID(i));
+  }
+  return true;
+}
+
+bool RemoteOuterWindowProxy::ownPropertyKeys(JSContext* aCx,
+                                             JS::Handle<JSObject*> aProxy,
+                                             JS::AutoIdVector& aProps) const {
+  BrowsingContext* bc = GetBrowsingContext(aProxy);
+
+  // https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-ownpropertykeys:crossoriginownpropertykeys-(-o-)
+  // step 3 to 5
+  if (!AppendIndexedPropertyNames(aCx, bc, aProps)) {
+    return false;
+  }
+
+  // https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-ownpropertykeys:crossoriginownpropertykeys-(-o-)
+  // step 7
+  return RemoteObjectProxy::ownPropertyKeys(aCx, aProxy, aProps);
+}
+
+bool RemoteOuterWindowProxy::getOwnEnumerablePropertyKeys(
+    JSContext* aCx, JS::Handle<JSObject*> aProxy,
+    JS::AutoIdVector& aProps) const {
+  return AppendIndexedPropertyNames(aCx, GetBrowsingContext(aProxy), aProps);
+}
+
+void RemoteOuterWindowProxy::finalize(JSFreeOp* aFop, JSObject* aProxy) const {
+  BrowsingContext* bc = GetBrowsingContext(aProxy);
+  RefPtr<BrowsingContext> self(dont_AddRef(bc));
+}
+
+const char* RemoteOuterWindowProxy::className(
+    JSContext* aCx, JS::Handle<JSObject*> aProxy) const {
+  MOZ_ASSERT(js::IsProxy(aProxy));
+
+  return "Object";
+}
+
+}  // namespace dom
+}  // namespace mozilla
--- a/dom/base/WindowNamedPropertiesHandler.cpp
+++ b/dom/base/WindowNamedPropertiesHandler.cpp
@@ -13,24 +13,25 @@
 #include "nsHTMLDocument.h"
 #include "nsJSUtils.h"
 #include "xpcprivate.h"
 
 namespace mozilla {
 namespace dom {
 
 static bool ShouldExposeChildWindow(nsString& aNameBeingResolved,
-                                    nsPIDOMWindowOuter* aChild) {
-  Element* e = aChild->GetFrameElementInternal();
+                                    BrowsingContext* aChild) {
+  nsPIDOMWindowOuter* child = aChild->GetDOMWindow();
+  Element* e = child->GetFrameElementInternal();
   if (e && e->IsInShadowTree()) {
     return false;
   }
 
   // If we're same-origin with the child, go ahead and expose it.
-  nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aChild);
+  nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(child);
   NS_ENSURE_TRUE(sop, false);
   if (nsContentUtils::SubjectPrincipal()->Equals(sop->GetPrincipal())) {
     return true;
   }
 
   // If we're not same-origin, expose it _only_ if the name of the browsing
   // context matches the 'name' attribute of the frame element in the parent.
   // The motivations behind this heuristic are worth explaining here.
@@ -95,23 +96,23 @@ bool WindowNamedPropertiesHandler::getOw
 
   if (str.IsEmpty()) {
     return true;
   }
 
   // Grab the DOM window.
   nsGlobalWindowInner* win = xpc::WindowGlobalOrNull(aProxy);
   if (win->Length() > 0) {
-    nsCOMPtr<nsPIDOMWindowOuter> childWin = win->GetChildWindow(str);
-    if (childWin && ShouldExposeChildWindow(str, childWin)) {
+    RefPtr<BrowsingContext> child = win->GetChildWindow(str);
+    if (child && ShouldExposeChildWindow(str, child)) {
       // We found a subframe of the right name. Shadowing via |var foo| in
       // global scope is still allowed, since |var| only looks up |own|
       // properties. But unqualified shadowing will fail, per-spec.
       JS::Rooted<JS::Value> v(aCx);
-      if (!ToJSValue(aCx, nsGlobalWindowOuter::Cast(childWin), &v)) {
+      if (!ToJSValue(aCx, WindowProxyHolder(child.forget()), &v)) {
         return false;
       }
       FillPropertyDescriptor(aDesc, aProxy, 0, v);
       return true;
     }
   }
 
   // The rest of this function is for HTML documents only.
@@ -177,18 +178,18 @@ bool WindowNamedPropertiesHandler::ownPr
         // item->GetWindow().  But it's not obvious whether this does the same
         // thing as GetChildWindow() with the item's name (due to the complexity
         // of FindChildWithName).  Since GetChildWindow is what we use in
         // getOwnPropDescriptor, let's try to be consistent.
         nsString name;
         item->GetName(name);
         if (!names.Contains(name)) {
           // Make sure we really would expose it from getOwnPropDescriptor.
-          nsCOMPtr<nsPIDOMWindowOuter> childWin = win->GetChildWindow(name);
-          if (childWin && ShouldExposeChildWindow(name, childWin)) {
+          RefPtr<BrowsingContext> child = win->GetChildWindow(name);
+          if (child && ShouldExposeChildWindow(name, child)) {
             names.AppendElement(name);
           }
         }
       }
     }
   }
   if (!AppendNamedPropertyIds(aCx, aProxy, names, false, aProps)) {
     return false;
new file mode 100644
--- /dev/null
+++ b/dom/base/WindowProxyHolder.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_WindowProxyHolder_h__
+#define mozilla_dom_WindowProxyHolder_h__
+
+#include "mozilla/dom/BrowsingContext.h"
+
+struct JSContext;
+class JSObject;
+
+namespace JS {
+template <typename T>
+class MutableHandle;
+}  // namespace JS
+
+namespace mozilla {
+namespace dom {
+
+/**
+ * This class is used for passing arguments and the return value for WebIDL
+ * binding code that takes/returns a WindowProxy object and for WebIDL
+ * unions/dictionaries that contain a WindowProxy member. It should never
+ * contain null; if the value in WebIDL is nullable the binding code will use a
+ * Nullable<WindowProxyHolder>.
+ */
+class WindowProxyHolder {
+ public:
+  WindowProxyHolder() = default;
+  explicit WindowProxyHolder(BrowsingContext* aBC) : mBrowsingContext(aBC) {
+    MOZ_ASSERT(mBrowsingContext, "Don't set WindowProxyHolder to null.");
+  }
+  explicit WindowProxyHolder(already_AddRefed<BrowsingContext>&& aBC)
+      : mBrowsingContext(std::move(aBC)) {
+    MOZ_ASSERT(mBrowsingContext, "Don't set WindowProxyHolder to null.");
+  }
+  WindowProxyHolder& operator=(BrowsingContext* aBC) {
+    mBrowsingContext = aBC;
+    MOZ_ASSERT(mBrowsingContext, "Don't set WindowProxyHolder to null.");
+    return *this;
+  }
+  WindowProxyHolder& operator=(already_AddRefed<BrowsingContext>&& aBC) {
+    mBrowsingContext = std::move(aBC);
+    MOZ_ASSERT(mBrowsingContext, "Don't set WindowProxyHolder to null.");
+    return *this;
+  }
+
+  BrowsingContext* get() const {
+    MOZ_ASSERT(mBrowsingContext, "WindowProxyHolder hasn't been initialized.");
+    return mBrowsingContext;
+  }
+
+ private:
+  friend void ImplCycleCollectionUnlink(WindowProxyHolder& aProxy);
+
+  RefPtr<BrowsingContext> mBrowsingContext;
+};
+
+inline void ImplCycleCollectionTraverse(
+    nsCycleCollectionTraversalCallback& aCallback, WindowProxyHolder& aProxy,
+    const char* aName, uint32_t aFlags = 0) {
+  CycleCollectionNoteChild(aCallback, aProxy.get(), "mBrowsingContext", aFlags);
+}
+
+inline void ImplCycleCollectionUnlink(WindowProxyHolder& aProxy) {
+  aProxy.mBrowsingContext = nullptr;
+}
+
+extern bool GetRemoteOuterWindowProxy(JSContext* aCx, BrowsingContext* aContext,
+                                      JS::MutableHandle<JSObject*> aValue);
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif /* mozilla_dom_WindowProxyHolder_h__ */
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -206,16 +206,17 @@ EXPORTS.mozilla.dom += [
     'PlacesBookmark.h',
     'PlacesBookmarkAddition.h',
     'PlacesEvent.h',
     'PlacesObservers.h',
     'PlacesVisit.h',
     'PlacesWeakCallbackWrapper.h',
     'PopupBlocker.h',
     'Pose.h',
+    'PostMessageEvent.h',
     'ProcessMessageManager.h',
     'ResponsiveImageSelector.h',
     'SameProcessMessageQueue.h',
     'ScreenLuminance.h',
     'ScreenOrientation.h',
     'Selection.h',
     'ShadowRoot.h',
     'StructuredCloneBlob.h',
@@ -230,16 +231,17 @@ EXPORTS.mozilla.dom += [
     'Timeout.h',
     'TimeoutHandler.h',
     'TimeoutManager.h',
     'TreeIterator.h',
     'TreeWalker.h',
     'VisualViewport.h',
     'WebKitCSSMatrix.h',
     'WindowOrientationObserver.h',
+    'WindowProxyHolder.h',
 ]
 
 if CONFIG['FUZZING']:
     EXPORTS.mozilla.dom += [
         'FuzzingFunctions.h',
     ]
 
 UNIFIED_SOURCES += [
@@ -363,16 +365,17 @@ UNIFIED_SOURCES += [
     'nsWrapperCache.cpp',
     'nsXHTMLContentSerializer.cpp',
     'nsXMLContentSerializer.cpp',
     'ParentProcessMessageManager.cpp',
     'PopupBlocker.cpp',
     'Pose.cpp',
     'PostMessageEvent.cpp',
     'ProcessMessageManager.cpp',
+    'RemoteOuterWindowProxy.cpp',
     'ResponsiveImageSelector.cpp',
     'SameProcessMessageQueue.cpp',
     'ScreenLuminance.cpp',
     'ScreenOrientation.cpp',
     'Selection.cpp',
     'SelectionChangeEventDispatcher.cpp',
     'ShadowRoot.cpp',
     'StorageAccessPermissionRequest.cpp',
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -8584,18 +8584,21 @@ class StringBuilder {
     Unit* u = AddUnit();
     u->mTextFragment = aTextFragment;
     u->mType = Unit::eTextFragmentWithEncode;
     u->mLength = aLen;
     mLength += aLen;
   }
 
   bool ToString(nsAString& aOut) {
+    if (!mLength.isValid()) {
+      return false;
+    }
     nsresult rv;
-    BulkAppender appender(aOut.BulkWrite(mLength, 0, true, rv));
+    BulkAppender appender(aOut.BulkWrite(mLength.value(), 0, true, rv));
     if (NS_FAILED(rv)) {
       return false;
     }
 
     for (StringBuilder* current = this; current; current = current->mNext) {
       uint32_t len = current->mUnits.Length();
       for (uint32_t i = 0; i < len; ++i) {
         Unit& u = current->mUnits[i];
@@ -8720,17 +8723,17 @@ class StringBuilder {
       aAppender.Append(aStr.FromTo(flushedUntil, currentPosition));
     }
   }
 
   AutoTArray<Unit, STRING_BUFFER_UNITS> mUnits;
   nsAutoPtr<StringBuilder> mNext;
   StringBuilder* mLast;
   // mLength is used only in the first StringBuilder object in the linked list.
-  uint32_t mLength;
+  CheckedInt<uint32_t> mLength;
 };
 
 }  // namespace
 
 static void AppendEncodedCharacters(const nsTextFragment* aText,
                                     StringBuilder& aBuilder) {
   uint32_t extraSpaceNeeded = 0;
   uint32_t len = aText->GetLength();
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -289,16 +289,17 @@
 #include "mozilla/RestyleManager.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "nsHTMLTags.h"
 #include "NodeUbiReporting.h"
 #include "nsICookieService.h"
 #include "mozilla/net/ChannelEventQueue.h"
 #include "mozilla/net/RequestContextService.h"
 #include "StorageAccessPermissionRequest.h"
+#include "mozilla/dom/WindowProxyHolder.h"
 
 #define XML_DECLARATION_BITS_DECLARATION_EXISTS (1 << 0)
 #define XML_DECLARATION_BITS_ENCODING_EXISTS (1 << 1)
 #define XML_DECLARATION_BITS_STANDALONE_EXISTS (1 << 2)
 #define XML_DECLARATION_BITS_STANDALONE_YES (1 << 3)
 
 using namespace mozilla;
 using namespace mozilla::dom;
@@ -3361,16 +3362,24 @@ nsresult nsIDocument::GetSrcdocData(nsAS
     if (inStrmChan) {
       return inStrmChan->GetSrcdocData(aSrcdocData);
     }
   }
   aSrcdocData = VoidString();
   return NS_OK;
 }
 
+Nullable<WindowProxyHolder> nsIDocument::GetDefaultView() const {
+  nsPIDOMWindowOuter* win = GetWindow();
+  if (!win) {
+    return nullptr;
+  }
+  return WindowProxyHolder(win->GetBrowsingContext());
+}
+
 Element* nsIDocument::GetActiveElement() {
   // Get the focused element.
   Element* focusedElement = GetRetargetedFocusedElement();
   if (focusedElement) {
     return focusedElement;
   }
 
   // No focused element anywhere in this document.  Try to get the BODY.
@@ -5693,19 +5702,17 @@ already_AddRefed<TreeWalker> nsIDocument
 
 already_AddRefed<Location> nsIDocument::GetLocation() const {
   nsCOMPtr<nsPIDOMWindowInner> w = do_QueryInterface(mScriptGlobalObject);
 
   if (!w) {
     return nullptr;
   }
 
-  nsGlobalWindowInner* window = nsGlobalWindowInner::Cast(w);
-  RefPtr<Location> loc = window->GetLocation();
-  return loc.forget();
+  return do_AddRef(w->Location());
 }
 
 Element* nsIDocument::GetHtmlElement() const {
   Element* rootElement = GetRootElement();
   if (rootElement && rootElement->IsHTMLElement(nsGkAtoms::html))
     return rootElement;
   return nullptr;
 }
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -498,17 +498,17 @@ nsresult nsFrameLoader::CheckURILoad(nsI
 
   // Bail out if this is an infinite recursion scenario
   if (IsRemoteFrame()) {
     return NS_OK;
   }
   return CheckForRecursiveLoad(aURI);
 }
 
-nsIDocShell* nsFrameLoader::GetDocShell(ErrorResult& aRv) {
+nsDocShell* nsFrameLoader::GetDocShell(ErrorResult& aRv) {
   if (IsRemoteFrame()) {
     return nullptr;
   }
 
   // If we have an owner, make sure we have a docshell and return
   // that. If not, we're most likely in the middle of being torn down,
   // then we just return null.
   if (mOwnerContent) {
@@ -670,41 +670,37 @@ bool nsFrameLoader::Show(int32_t marginW
   NS_ASSERTION(mDocShell, "MaybeCreateDocShell succeeded, but null mDocShell");
   if (!mDocShell) {
     return false;
   }
 
   mDocShell->SetMarginWidth(marginWidth);
   mDocShell->SetMarginHeight(marginHeight);
 
-  nsCOMPtr<nsIScrollable> sc = do_QueryInterface(mDocShell);
-  if (sc) {
-    sc->SetDefaultScrollbarPreferences(nsIScrollable::ScrollOrientation_X,
-                                       scrollbarPrefX);
-    sc->SetDefaultScrollbarPreferences(nsIScrollable::ScrollOrientation_Y,
-                                       scrollbarPrefY);
-  }
+  mDocShell->SetDefaultScrollbarPreferences(nsIScrollable::ScrollOrientation_X,
+                                            scrollbarPrefX);
+  mDocShell->SetDefaultScrollbarPreferences(nsIScrollable::ScrollOrientation_Y,
+                                            scrollbarPrefY);
 
   nsCOMPtr<nsIPresShell> presShell = mDocShell->GetPresShell();
   if (presShell) {
     // Ensure root scroll frame is reflowed in case scroll preferences or
     // margins have changed
     nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame();
     if (rootScrollFrame) {
       presShell->FrameNeedsReflow(rootScrollFrame, nsIPresShell::eResize,
                                   NS_FRAME_IS_DIRTY);
     }
     return true;
   }
 
   nsView* view = frame->EnsureInnerView();
   if (!view) return false;
 
-  nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(mDocShell);
-  NS_ASSERTION(baseWindow, "Found a nsIDocShell that isn't a nsIBaseWindow.");
+  RefPtr<nsDocShell> baseWindow = mDocShell;
   baseWindow->InitWindow(nullptr, view->GetWidget(), 0, 0, size.width,
                          size.height);
   // This is kinda whacky, this "Create()" call doesn't really
   // create anything, one starts to wonder why this was named
   // "Create"...
   baseWindow->Create();
   baseWindow->SetVisibility(true);
   NS_ENSURE_TRUE(mDocShell, false);
@@ -853,19 +849,17 @@ void nsFrameLoader::Hide() {
   }
 
   if (!mDocShell) return;
 
   nsCOMPtr<nsIContentViewer> contentViewer;
   mDocShell->GetContentViewer(getter_AddRefs(contentViewer));
   if (contentViewer) contentViewer->SetSticky(false);
 
-  nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(mDocShell);
-  NS_ASSERTION(baseWin,
-               "Found an nsIDocShell which doesn't implement nsIBaseWindow.");
+  RefPtr<nsDocShell> baseWin = mDocShell;
   baseWin->SetVisibility(false);
   baseWin->SetParentWidget(nullptr);
 }
 
 void nsFrameLoader::ForceLayoutIfNecessary() {
   nsIFrame* frame = GetPrimaryFrameOfOwningContent();
   if (!frame) {
     return;
@@ -1672,19 +1666,18 @@ void nsFrameLoader::DestroyDocShell() {
   }
 
   // Fire the "unload" event if we're in-process.
   if (mChildMessageManager) {
     mChildMessageManager->FireUnloadEvent();
   }
 
   // Destroy the docshell.
-  nsCOMPtr<nsIBaseWindow> base_win(do_QueryInterface(mDocShell));
-  if (base_win) {
-    base_win->Destroy();
+  if (mDocShell) {
+    mDocShell->Destroy();
   }
   mDocShell = nullptr;
 
   if (mChildMessageManager) {
     // Stop handling events in the in-process frame script.
     mChildMessageManager->DisconnectEventListeners();
   }
 }
@@ -1972,18 +1965,18 @@ nsresult nsFrameLoader::MaybeCreateDocSh
                                  nsGkAtoms::allowscriptstoclose,
                                  nsGkAtoms::_true, eCaseMatters)) {
     nsGlobalWindowOuter::Cast(newWindow)->AllowScriptsToClose();
   }
 
   // This is kinda whacky, this call doesn't really create anything,
   // but it must be called to make sure things are properly
   // initialized.
-  nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(mDocShell);
-  if (NS_FAILED(baseWin->Create())) {
+  RefPtr<nsDocShell> docShell = mDocShell;
+  if (NS_FAILED(docShell->Create())) {
     // Do not call Destroy() here. See bug 472312.
     NS_WARNING("Something wrong when creating the docshell for a frameloader!");
     return NS_ERROR_FAILURE;
   }
 
   // If we are an in-process browser, we want to set up our session history. We
   // do this by creating both the child SHistory (which is in the nsDocShell),
   // and creating the corresponding in-process ParentSHistory.
@@ -2292,18 +2285,17 @@ nsresult nsFrameLoader::UpdatePositionAn
     return NS_OK;
   }
   UpdateBaseWindowPositionAndSize(aIFrame);
   return NS_OK;
 }
 
 void nsFrameLoader::UpdateBaseWindowPositionAndSize(
     nsSubDocumentFrame* aIFrame) {
-  nsCOMPtr<nsIBaseWindow> baseWindow =
-      do_QueryInterface(GetDocShell(IgnoreErrors()));
+  nsCOMPtr<nsIBaseWindow> baseWindow = GetDocShell(IgnoreErrors());
 
   // resize the sub document
   if (baseWindow) {
     int32_t x = 0;
     int32_t y = 0;
 
     AutoWeakFrame weakFrame(aIFrame);
 
@@ -3012,17 +3004,17 @@ already_AddRefed<nsITabParent> nsFrameLo
   return do_AddRef(mRemoteBrowser);
 }
 
 already_AddRefed<nsILoadContext> nsFrameLoader::LoadContext() {
   nsCOMPtr<nsILoadContext> loadContext;
   if (IsRemoteFrame() && (mRemoteBrowser || TryRemoteBrowser())) {
     loadContext = mRemoteBrowser->GetLoadContext();
   } else {
-    loadContext = do_GetInterface(GetDocShell(IgnoreErrors()));
+    loadContext = do_GetInterface(ToSupports(GetDocShell(IgnoreErrors())));
   }
   return loadContext.forget();
 }
 
 already_AddRefed<BrowsingContext> nsFrameLoader::GetBrowsingContext() {
   RefPtr<BrowsingContext> browsingContext;
   if (IsRemoteFrame() && (mRemoteBrowser || TryRemoteBrowser())) {
     browsingContext = mRemoteBrowser->GetBrowsingContext();
--- a/dom/base/nsFrameLoader.h
+++ b/dom/base/nsFrameLoader.h
@@ -7,17 +7,17 @@
 /*
  * Class for managing loading of a subframe (creation of the docshell,
  * handling of loads in it, recursion-checking).
  */
 
 #ifndef nsFrameLoader_h_
 #define nsFrameLoader_h_
 
-#include "nsIDocShell.h"
+#include "nsDocShell.h"
 #include "nsStringFwd.h"
 #include "nsIFrameLoaderOwner.h"
 #include "nsPoint.h"
 #include "nsSize.h"
 #include "nsWrapperCache.h"
 #include "nsIURI.h"
 #include "nsFrameMessageManager.h"
 #include "mozilla/dom/BindingUtils.h"
@@ -113,17 +113,17 @@ class nsFrameLoader final : public nsStu
       const {
     return mChildMessageManager;
   }
   nsresult CreateStaticClone(nsFrameLoader* aDest);
   nsresult UpdatePositionAndSize(nsSubDocumentFrame* aIFrame);
 
   // WebIDL methods
 
-  nsIDocShell* GetDocShell(mozilla::ErrorResult& aRv);
+  nsDocShell* GetDocShell(mozilla::ErrorResult& aRv);
 
   already_AddRefed<nsITabParent> GetTabParent();
 
   already_AddRefed<nsILoadContext> LoadContext();
 
   already_AddRefed<mozilla::dom::BrowsingContext> GetBrowsingContext();
 
   /**
@@ -424,17 +424,17 @@ class nsFrameLoader final : public nsStu
   nsresult GetNewTabContext(mozilla::dom::MutableTabContext* aTabContext,
                             nsIURI* aURI = nullptr);
 
   enum TabParentChange { eTabParentRemoved, eTabParentChanged };
   void MaybeUpdatePrimaryTabParent(TabParentChange aChange);
 
   nsresult PopulateUserContextIdFromAttribute(mozilla::OriginAttributes& aAttr);
 
-  nsCOMPtr<nsIDocShell> mDocShell;
+  RefPtr<nsDocShell> mDocShell;
   nsCOMPtr<nsIURI> mURIToLoad;
   nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
   mozilla::dom::Element* mOwnerContent;  // WEAK
 
   // After the frameloader has been removed from the DOM but before all of the
   // messages from the frame have been received, we keep a strong reference to
   // our <browser> element.
   RefPtr<mozilla::dom::Element> mOwnerContentStrong;
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -294,29 +294,38 @@ using mozilla::dom::cache::CacheStorage;
   if (!HasActiveDocument()) {                                        \
     NS_WARNING(outer ? "Inner window does not have active document." \
                      : "No outer window available!");                \
     return err_rval;                                                 \
   }                                                                  \
   return outer->method args;                                         \
   PR_END_MACRO
 
-#define FORWARD_TO_OUTER_OR_THROW(method, args, errorresult, err_rval) \
-  PR_BEGIN_MACRO                                                       \
-  nsGlobalWindowOuter* outer = GetOuterWindowInternal();               \
-  if (MOZ_LIKELY(HasActiveDocument())) {                               \
-    return outer->method args;                                         \
-  }                                                                    \
-  if (!outer) {                                                        \
-    NS_WARNING("No outer window available!");                          \
-    errorresult.Throw(NS_ERROR_NOT_INITIALIZED);                       \
-  } else {                                                             \
-    errorresult.Throw(NS_ERROR_XPC_SECURITY_MANAGER_VETO);             \
-  }                                                                    \
-  return err_rval;                                                     \
+static nsGlobalWindowOuter* GetOuterWindowForForwarding(
+    nsGlobalWindowInner* aInner, ErrorResult& aError) {
+  nsGlobalWindowOuter* outer = aInner->GetOuterWindowInternal();
+  if (MOZ_LIKELY(aInner->HasActiveDocument())) {
+    return outer;
+  }
+  if (!outer) {
+    NS_WARNING("No outer window available!");
+    aError.Throw(NS_ERROR_NOT_INITIALIZED);
+  } else {
+    aError.Throw(NS_ERROR_XPC_SECURITY_MANAGER_VETO);
+  }
+  return nullptr;
+}
+
+#define FORWARD_TO_OUTER_OR_THROW(method, args, errorresult, err_rval)         \
+  PR_BEGIN_MACRO                                                               \
+  nsGlobalWindowOuter* outer = GetOuterWindowForForwarding(this, errorresult); \
+  if (MOZ_LIKELY(outer)) {                                                     \
+    return outer->method args;                                                 \
+  }                                                                            \
+  return err_rval;                                                             \
   PR_END_MACRO
 
 #define FORWARD_TO_OUTER_VOID(method, args)                          \
   PR_BEGIN_MACRO                                                     \
   nsGlobalWindowOuter* outer = GetOuterWindowInternal();             \
   if (!HasActiveDocument()) {                                        \
     NS_WARNING(outer ? "Inner window does not have active document." \
                      : "No outer window available!");                \
@@ -2107,19 +2116,19 @@ void nsPIDOMWindowInner::MuteAudioContex
 void nsPIDOMWindowInner::UnmuteAudioContexts() {
   for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) {
     if (!mAudioContexts[i]->IsOffline()) {
       mAudioContexts[i]->Unmute();
     }
   }
 }
 
-nsGlobalWindowInner* nsGlobalWindowInner::Window() { return this; }
-
-nsGlobalWindowInner* nsGlobalWindowInner::Self() { return this; }
+BrowsingContext* nsGlobalWindowInner::Window() {
+  return mOuterWindow ? mOuterWindow->GetBrowsingContext() : nullptr;
+}
 
 Navigator* nsPIDOMWindowInner::Navigator() {
   if (!mNavigator) {
     mNavigator = new mozilla::dom::Navigator(this);
   }
 
   return mNavigator;
 }
@@ -2541,17 +2550,17 @@ bool nsGlobalWindowInner::HasActiveSpeec
     return !mSpeechSynthesis->HasEmptyQueue();
   }
 
   return false;
 }
 
 #endif
 
-already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowInner::GetParent(
+Nullable<WindowProxyHolder> nsGlobalWindowInner::GetParent(
     ErrorResult& aError) {
   FORWARD_TO_OUTER_OR_THROW(GetParentOuter, (), aError, nullptr);
 }
 
 /**
  * GetScriptableParent is called when script reads window.parent.
  *
  * In contrast to GetRealParent, GetScriptableParent respects <iframe
@@ -3301,22 +3310,22 @@ double nsGlobalWindowInner::GetScrollX(E
 }
 
 double nsGlobalWindowInner::GetScrollY(ErrorResult& aError) {
   FORWARD_TO_OUTER_OR_THROW(GetScrollYOuter, (), aError, 0);
 }
 
 uint32_t nsGlobalWindowInner::Length() { FORWARD_TO_OUTER(Length, (), 0); }
 
-already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowInner::GetTop(
+Nullable<WindowProxyHolder> nsGlobalWindowInner::GetTop(
     mozilla::ErrorResult& aError) {
   FORWARD_TO_OUTER_OR_THROW(GetTopOuter, (), aError, nullptr);
 }
 
-nsPIDOMWindowOuter* nsGlobalWindowInner::GetChildWindow(
+already_AddRefed<BrowsingContext> nsGlobalWindowInner::GetChildWindow(
     const nsAString& aName) {
   if (GetOuterWindowInternal()) {
     return GetOuterWindowInternal()->GetChildWindow(aName);
   }
   return nullptr;
 }
 
 void nsGlobalWindowInner::RefreshRealmPrincipal() {
@@ -3414,17 +3423,17 @@ void nsGlobalWindowInner::Prompt(const n
                                  nsIPrincipal& aSubjectPrincipal,
                                  ErrorResult& aError) {
   FORWARD_TO_OUTER_OR_THROW(
       PromptOuter, (aMessage, aInitial, aReturn, aSubjectPrincipal, aError),
       aError, );
 }
 
 void nsGlobalWindowInner::Focus(ErrorResult& aError) {
-  FORWARD_TO_OUTER_OR_THROW(FocusOuter, (aError), aError, );
+  FORWARD_TO_OUTER_OR_THROW(FocusOuter, (), aError, );
 }
 
 nsresult nsGlobalWindowInner::Focus() {
   ErrorResult rv;
   Focus(rv);
 
   return rv.StealNSResult();
 }
@@ -3679,34 +3688,34 @@ void nsGlobalWindowInner::CaptureEvents(
 }
 
 void nsGlobalWindowInner::ReleaseEvents() {
   if (mDoc) {
     mDoc->WarnOnceAbout(nsIDocument::eUseOfReleaseEvents);
   }
 }
 
-already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowInner::Open(
-    const nsAString& aUrl, const nsAString& aName, const nsAString& aOptions,
-    ErrorResult& aError) {
+Nullable<WindowProxyHolder> nsGlobalWindowInner::Open(const nsAString& aUrl,
+                                                      const nsAString& aName,
+                                                      const nsAString& aOptions,
+                                                      ErrorResult& aError) {
   FORWARD_TO_OUTER_OR_THROW(OpenOuter, (aUrl, aName, aOptions, aError), aError,
                             nullptr);
 }
 
-already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowInner::OpenDialog(
+Nullable<WindowProxyHolder> nsGlobalWindowInner::OpenDialog(
     JSContext* aCx, const nsAString& aUrl, const nsAString& aName,
     const nsAString& aOptions, const Sequence<JS::Value>& aExtraArgument,
     ErrorResult& aError) {
   FORWARD_TO_OUTER_OR_THROW(
       OpenDialogOuter, (aCx, aUrl, aName, aOptions, aExtraArgument, aError),
       aError, nullptr);
 }
 
-already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowInner::GetFrames(
-    ErrorResult& aError) {
+BrowsingContext* nsGlobalWindowInner::GetFrames(ErrorResult& aError) {
   FORWARD_TO_OUTER_OR_THROW(GetFramesOuter, (), aError, nullptr);
 }
 
 void nsGlobalWindowInner::PostMessageMoz(JSContext* aCx,
                                          JS::Handle<JS::Value> aMessage,
                                          const nsAString& aTargetOrigin,
                                          JS::Handle<JS::Value> aTransfer,
                                          nsIPrincipal& aSubjectPrincipal,
@@ -3746,18 +3755,18 @@ void nsGlobalWindowInner::PostMessageMoz
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   PostMessageMoz(aCx, aMessage, aOptions.mTargetOrigin, transferArray,
                  aSubjectPrincipal, aRv);
 }
 
-void nsGlobalWindowInner::Close(ErrorResult& aError) {
-  FORWARD_TO_OUTER_OR_THROW(CloseOuter, (nsContentUtils::IsCallerChrome()),
+void nsGlobalWindowInner::Close(CallerType aCallerType, ErrorResult& aError) {
+  FORWARD_TO_OUTER_OR_THROW(CloseOuter, (aCallerType == CallerType::System),
                             aError, );
 }
 
 nsresult nsGlobalWindowInner::Close() {
   FORWARD_TO_OUTER(Close, (), NS_ERROR_UNEXPECTED);
 }
 
 bool nsGlobalWindowInner::IsInModalState() {
@@ -3876,17 +3885,17 @@ void nsGlobalWindowInner::Btoa(const nsA
                                ErrorResult& aError) {
   aError = nsContentUtils::Btoa(aBinaryData, aAsciiBase64String);
 }
 
 //*****************************************************************************
 // EventTarget
 //*****************************************************************************
 
-nsPIDOMWindowOuter* nsGlobalWindowInner::GetOwnerGlobalForBindings() {
+nsPIDOMWindowOuter* nsGlobalWindowInner::GetOwnerGlobalForBindingsInternal() {
   return nsPIDOMWindowOuter::GetFromCurrentInner(this);
 }
 
 bool nsGlobalWindowInner::DispatchEvent(Event& aEvent, CallerType aCallerType,
                                         ErrorResult& aRv) {
   if (!IsCurrentInnerWindow()) {
     NS_WARNING(
         "DispatchEvent called on non-current inner window, dropping. "
@@ -3929,17 +3938,17 @@ EventListenerManager* nsGlobalWindowInne
 EventListenerManager* nsGlobalWindowInner::GetExistingListenerManager() const {
   return mListenerManager;
 }
 
 //*****************************************************************************
 // nsGlobalWindowInner::nsPIDOMWindow
 //*****************************************************************************
 
-Location* nsGlobalWindowInner::GetLocation() {
+Location* nsGlobalWindowInner::Location() {
   if (!mLocation) {
     mLocation = new dom::Location(this, GetDocShell());
   }
 
   return mLocation;
 }
 
 void nsGlobalWindowInner::MaybeUpdateTouchState() {
@@ -6905,17 +6914,17 @@ already_AddRefed<External> nsGlobalWindo
   return nullptr;
 #endif
 }
 
 void nsGlobalWindowInner::GetSidebar(OwningExternalOrWindowProxy& aResult,
                                      ErrorResult& aRv) {
 #ifdef HAVE_SIDEBAR
   // First check for a named frame named "sidebar"
-  nsCOMPtr<nsPIDOMWindowOuter> domWindow =
+  RefPtr<BrowsingContext> domWindow =
       GetChildWindow(NS_LITERAL_STRING("sidebar"));
   if (domWindow) {
     aResult.SetAsWindowProxy() = domWindow.forget();
     return;
   }
 
   RefPtr<External> external = GetExternal(aRv);
   if (external) {
--- a/dom/base/nsGlobalWindowInner.h
+++ b/dom/base/nsGlobalWindowInner.h
@@ -92,16 +92,17 @@ class IdleRequestExecutor;
 class DialogValueHolder;
 
 class PromiseDocumentFlushedResolver;
 
 namespace mozilla {
 class AbstractThread;
 namespace dom {
 class BarProp;
+class BrowsingContext;
 struct ChannelPixelLayout;
 class ClientSource;
 class Console;
 class Crypto;
 class CustomElementRegistry;
 class DocGroup;
 class External;
 class Function;
@@ -208,16 +209,18 @@ class nsGlobalWindowInner final : public
                                   public nsIScriptGlobalObject,
                                   public nsIScriptObjectPrincipal,
                                   public nsSupportsWeakReference,
                                   public nsIInterfaceRequestor,
                                   public PRCListStr,
                                   public nsAPostRefreshObserver,
                                   public mozilla::webgpu::InstanceProvider {
  public:
+  typedef mozilla::dom::BrowsingContext RemoteProxy;
+
   typedef mozilla::TimeStamp TimeStamp;
   typedef mozilla::TimeDuration TimeDuration;
 
   typedef nsDataHashtable<nsUint64HashKey, nsGlobalWindowInner*>
       InnerWindowByIdTable;
 
   static void AssertIsOnMainThread()
 #ifdef DEBUG
@@ -301,17 +304,17 @@ class nsGlobalWindowInner final : public
 
   virtual mozilla::EventListenerManager* GetExistingListenerManager()
       const override;
 
   virtual mozilla::EventListenerManager* GetOrCreateListenerManager() override;
 
   bool ComputeDefaultWantsUntrusted(mozilla::ErrorResult& aRv) final;
 
-  virtual nsPIDOMWindowOuter* GetOwnerGlobalForBindings() override;
+  virtual nsPIDOMWindowOuter* GetOwnerGlobalForBindingsInternal() override;
 
   virtual nsIGlobalObject* GetOwnerGlobal() const override;
 
   EventTarget* GetTargetForDOMEvent() override;
 
   using mozilla::dom::EventTarget::DispatchEvent;
   bool DispatchEvent(mozilla::dom::Event& aEvent,
                      mozilla::dom::CallerType aCallerType,
@@ -415,17 +418,18 @@ class nsGlobalWindowInner final : public
   void GetOwnPropertyNames(JSContext* aCx, JS::AutoIdVector& aNames,
                            bool aEnumerableOnly, mozilla::ErrorResult& aRv);
 
   nsPIDOMWindowOuter* GetScriptableTop() override;
   inline nsGlobalWindowOuter* GetTopInternal();
 
   inline nsGlobalWindowOuter* GetScriptableTopInternal();
 
-  nsPIDOMWindowOuter* GetChildWindow(const nsAString& aName);
+  already_AddRefed<mozilla::dom::BrowsingContext> GetChildWindow(
+      const nsAString& aName);
 
   // These return true if we've reached the state in this top level window
   // where we ask the user if further dialogs should be blocked.
   //
   // DialogsAreBeingAbused must be called on the scriptable top inner window.
   //
   // nsGlobalWindowOuter::ShouldPromptToBlockDialogs is implemented in terms of
   // nsGlobalWindowInner::DialogsAreBeingAbused, and will get the scriptable top
@@ -592,65 +596,67 @@ class nsGlobalWindowInner final : public
 #undef ERROR_EVENT
 #undef EVENT
 
   nsISupports* GetParentObject() { return nullptr; }
 
   static JSObject* CreateNamedPropertiesObject(JSContext* aCx,
                                                JS::Handle<JSObject*> aProto);
 
-  nsGlobalWindowInner* Window();
-  nsGlobalWindowInner* Self();
+  mozilla::dom::BrowsingContext* Window();
+  mozilla::dom::BrowsingContext* Self() { return Window(); }
   nsIDocument* GetDocument() { return GetDoc(); }
   void GetName(nsAString& aName, mozilla::ErrorResult& aError);
   void SetName(const nsAString& aName, mozilla::ErrorResult& aError);
-  mozilla::dom::Location* GetLocation() override;
+  mozilla::dom::Location* Location() override;
   nsHistory* GetHistory(mozilla::ErrorResult& aError);
   mozilla::dom::CustomElementRegistry* CustomElements() override;
   mozilla::dom::BarProp* GetLocationbar(mozilla::ErrorResult& aError);
   mozilla::dom::BarProp* GetMenubar(mozilla::ErrorResult& aError);
   mozilla::dom::BarProp* GetPersonalbar(mozilla::ErrorResult& aError);
   mozilla::dom::BarProp* GetScrollbars(mozilla::ErrorResult& aError);
   mozilla::dom::BarProp* GetStatusbar(mozilla::ErrorResult& aError);
   mozilla::dom::BarProp* GetToolbar(mozilla::ErrorResult& aError);
   void GetStatus(nsAString& aStatus, mozilla::ErrorResult& aError);
   void SetStatus(const nsAString& aStatus, mozilla::ErrorResult& aError);
-  void Close(mozilla::ErrorResult& aError);
+  void Close(mozilla::dom::CallerType aCallerType,
+             mozilla::ErrorResult& aError);
   nsresult Close() override;
   bool GetClosed(mozilla::ErrorResult& aError);
   void Stop(mozilla::ErrorResult& aError);
   void Focus(mozilla::ErrorResult& aError);
   nsresult Focus() override;
   void Blur(mozilla::ErrorResult& aError);
   nsDOMWindowList* GetFrames() final;
-  already_AddRefed<nsPIDOMWindowOuter> GetFrames(mozilla::ErrorResult& aError);
+  mozilla::dom::BrowsingContext* GetFrames(mozilla::ErrorResult& aError);
   uint32_t Length();
-  already_AddRefed<nsPIDOMWindowOuter> GetTop(mozilla::ErrorResult& aError);
+  mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> GetTop(
+      mozilla::ErrorResult& aError);
 
  protected:
   explicit nsGlobalWindowInner(nsGlobalWindowOuter* aOuterWindow);
   // Initializes the mWasOffline member variable
   void InitWasOffline();
 
  public:
   nsPIDOMWindowOuter* GetOpenerWindow(mozilla::ErrorResult& aError);
   void GetOpener(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval,
                  mozilla::ErrorResult& aError);
   void SetOpener(JSContext* aCx, JS::Handle<JS::Value> aOpener,
                  mozilla::ErrorResult& aError);
   void GetEvent(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval);
-  already_AddRefed<nsPIDOMWindowOuter> GetParent(mozilla::ErrorResult& aError);
+  mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> GetParent(
+      mozilla::ErrorResult& aError);
   nsPIDOMWindowOuter* GetScriptableParent() override;
   mozilla::dom::Element* GetFrameElement(nsIPrincipal& aSubjectPrincipal,
                                          mozilla::ErrorResult& aError);
   mozilla::dom::Element* GetFrameElement() override;
-  already_AddRefed<nsPIDOMWindowOuter> Open(const nsAString& aUrl,
-                                            const nsAString& aName,
-                                            const nsAString& aOptions,
-                                            mozilla::ErrorResult& aError);
+  mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> Open(
+      const nsAString& aUrl, const nsAString& aName, const nsAString& aOptions,
+      mozilla::ErrorResult& aError);
   nsDOMOfflineResourceList* GetApplicationCache(mozilla::ErrorResult& aError);
   nsDOMOfflineResourceList* GetApplicationCache() override;
 
 #if defined(MOZ_WIDGET_ANDROID)
   int16_t Orientation(mozilla::dom::CallerType aCallerType) const;
 #endif
 
   already_AddRefed<mozilla::dom::Console> GetConsole(JSContext* aCx,
@@ -852,17 +858,17 @@ class nsGlobalWindowInner final : public
   void SetFullScreen(bool aFullscreen, mozilla::ErrorResult& aError);
   bool Find(const nsAString& aString, bool aCaseSensitive, bool aBackwards,
             bool aWrapAround, bool aWholeWord, bool aSearchInFrames,
             bool aShowDialog, mozilla::ErrorResult& aError);
   uint64_t GetMozPaintCount(mozilla::ErrorResult& aError);
 
   bool ShouldResistFingerprinting();
 
-  already_AddRefed<nsPIDOMWindowOuter> OpenDialog(
+  mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> OpenDialog(
       JSContext* aCx, const nsAString& aUrl, const nsAString& aName,
       const nsAString& aOptions,
       const mozilla::dom::Sequence<JS::Value>& aExtraArgument,
       mozilla::ErrorResult& aError);
   void UpdateCommands(const nsAString& anAction, mozilla::dom::Selection* aSel,
                       int16_t aReason);
 
   void GetContent(JSContext* aCx, JS::MutableHandle<JSObject*> aRetval,
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -33,16 +33,17 @@
 #include "mozilla/dom/Performance.h"
 #include "mozilla/dom/StorageEvent.h"
 #include "mozilla/dom/StorageEventBinding.h"
 #include "mozilla/dom/StorageNotifierService.h"
 #include "mozilla/dom/StorageUtils.h"
 #include "mozilla/dom/Timeout.h"
 #include "mozilla/dom/TimeoutHandler.h"
 #include "mozilla/dom/TimeoutManager.h"
+#include "mozilla/dom/WindowProxyHolder.h"
 #include "mozilla/IntegerPrintfMacros.h"
 #if defined(MOZ_WIDGET_ANDROID)
 #include "mozilla/dom/WindowOrientationObserver.h"
 #endif
 #include "nsError.h"
 #include "nsIIdleService.h"
 #include "nsISizeOfEventTarget.h"
 #include "nsDOMJSUtils.h"
@@ -439,16 +440,20 @@ const char* nsOuterWindowProxy::classNam
 
   return "Window";
 }
 
 void nsOuterWindowProxy::finalize(JSFreeOp* fop, JSObject* proxy) const {
   nsGlobalWindowOuter* outerWindow = GetOuterWindow(proxy);
   if (outerWindow) {
     outerWindow->ClearWrapper(proxy);
+    BrowsingContext* bc = outerWindow->GetBrowsingContext();
+    if (bc) {
+      bc->ClearWindowProxy();
+    }
 
     // Ideally we would use OnFinalize here, but it's possible that
     // EnsureScriptEnvironment will later be called on the window, and we don't
     // want to create a new script object in that case. Therefore, we need to
     // write a non-null value that will reliably crash when dereferenced.
     outerWindow->PoisonOuterWindowProxy(proxy);
   }
 }
@@ -797,16 +802,20 @@ bool nsOuterWindowProxy::AppendIndexedPr
 
   return true;
 }
 
 size_t nsOuterWindowProxy::objectMoved(JSObject* obj, JSObject* old) const {
   nsGlobalWindowOuter* outerWindow = GetOuterWindow(obj);
   if (outerWindow) {
     outerWindow->UpdateWrapper(obj, old);
+    BrowsingContext* bc = outerWindow->GetBrowsingContext();
+    if (bc) {
+      bc->UpdateWindowProxy(obj, old);
+    }
   }
   return 0;
 }
 
 const nsOuterWindowProxy nsOuterWindowProxy::singleton;
 
 class nsChromeOuterWindowProxy : public nsOuterWindowProxy {
  public:
@@ -975,16 +984,19 @@ nsGlobalWindowOuter::~nsGlobalWindowOute
   }
 #endif
 
   MOZ_LOG(gDOMLeakPRLogOuter, LogLevel::Debug,
           ("DOMWINDOW %p destroyed", this));
 
   JSObject* proxy = GetWrapperMaybeDead();
   if (proxy) {
+    if (mBrowsingContext) {
+      mBrowsingContext->ClearWindowProxy();
+    }
     js::SetProxyReservedSlot(proxy, 0, js::PrivateValue(nullptr));
   }
 
   // An outer window is destroyed with inner windows still possibly
   // alive, iterate through the inner windows and null out their
   // back pointer to this outer, and pull them out of the list of
   // inner windows.
   //
@@ -1175,16 +1187,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
   // Traverse stuff from nsPIDOMWindow
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeEventHandler)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParentTarget)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessageManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameElement)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOpenerForInitialContentBrowser)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContext)
 
   tmp->TraverseHostObjectURIs(cb);
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeFields.mBrowserDOMWindow)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindowOuter)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext)
@@ -1202,16 +1215,20 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
   // Unlink stuff from nsPIDOMWindow
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeEventHandler)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mParentTarget)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessageManager)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameElement)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOpenerForInitialContentBrowser)
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell)
+  if (tmp->mBrowsingContext) {
+    tmp->mBrowsingContext->ClearWindowProxy();
+    tmp->mBrowsingContext = nullptr;
+  }
 
   tmp->UnlinkHostObjectURIs();
 
   if (tmp->IsChromeWindow()) {
     NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeFields.mBrowserDOMWindow)
   }
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
@@ -1814,16 +1831,17 @@ nsresult nsGlobalWindowOuter::SetNewDocu
 
       JS::Rooted<JSObject*> obj(cx, GetWrapperPreserveColor());
 
       js::SetProxyReservedSlot(obj, 0, js::PrivateValue(nullptr));
       js::SetProxyReservedSlot(outerObject, 0, js::PrivateValue(nullptr));
 
       outerObject = xpc::TransplantObject(cx, obj, outerObject);
       if (!outerObject) {
+        mBrowsingContext->ClearWindowProxy();
         NS_ERROR("unable to transplant wrappers, probably OOM");
         return NS_ERROR_FAILURE;
       }
 
       js::SetProxyReservedSlot(outerObject, 0,
                                js::PrivateValue(ToSupports(this)));
 
       SetWrapper(outerObject);
@@ -1835,16 +1853,17 @@ nsresult nsGlobalWindowOuter::SetNewDocu
     }
 
     // Enter the new global's realm.
     JSAutoRealm ar(cx, GetWrapperPreserveColor());
 
     {
       JS::Rooted<JSObject*> outer(cx, GetWrapperPreserveColor());
       js::SetWindowProxy(cx, newInnerGlobal, outer);
+      mBrowsingContext->SetWindowProxy(outer);
     }
 
     // Set scriptability based on the state of the docshell.
     bool allow = GetDocShell()->GetCanExecuteScripts();
     xpc::Scriptability::Get(GetWrapperPreserveColor())
         .SetDocShellAllowsScript(allow);
 
     if (!aState) {
@@ -2079,24 +2098,25 @@ void nsGlobalWindowOuter::DispatchDOMWin
             ? "chrome-document-global-created"
             : "content-document-global-created",
         origin.get());
   }
 }
 
 void nsGlobalWindowOuter::ClearStatus() { SetStatusOuter(EmptyString()); }
 
-void nsGlobalWindowOuter::SetDocShell(nsIDocShell* aDocShell) {
+void nsGlobalWindowOuter::SetDocShell(nsDocShell* aDocShell) {
   MOZ_ASSERT(aDocShell);
 
   if (aDocShell == mDocShell) {
     return;
   }
 
   mDocShell = aDocShell;  // Weak Reference
+  mBrowsingContext = aDocShell->GetBrowsingContext();
 
   nsCOMPtr<nsPIDOMWindowOuter> parentWindow = GetScriptableParentOrNull();
   MOZ_RELEASE_ASSERT(!parentWindow || !mTabGroup ||
                      mTabGroup ==
                          nsGlobalWindowOuter::Cast(parentWindow)->mTabGroup);
 
   mTopLevelOuterContentWindow =
       !mIsChrome && GetScriptableTopInternal() == this;
@@ -2619,41 +2639,44 @@ bool nsPIDOMWindowOuter::GetServiceWorke
   // iframes get the correct devtools setting.
   nsCOMPtr<nsPIDOMWindowOuter> topWindow = GetScriptableTop();
   if (!topWindow) {
     return false;
   }
   return topWindow->mServiceWorkersTestingEnabled;
 }
 
-already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowOuter::GetParentOuter() {
-  if (!mDocShell) {
+Nullable<WindowProxyHolder> nsGlobalWindowOuter::GetParentOuter() {
+  nsPIDOMWindowOuter* parent = GetScriptableParent();
+  BrowsingContext* parentBC;
+  if (!parent || !(parentBC = parent->GetBrowsingContext())) {
     return nullptr;
   }
 
-  nsCOMPtr<nsPIDOMWindowOuter> parent;
-  if (mDocShell->GetIsMozBrowser()) {
-    parent = this;
-  } else {
-    parent = GetParent();
-  }
-
-  return parent.forget();
+  return WindowProxyHolder(parentBC);
 }
 
 /**
  * GetScriptableParent is called when script reads window.parent.
  *
  * In contrast to GetRealParent, GetScriptableParent respects <iframe
  * mozbrowser> boundaries, so if |this| is contained by an <iframe
  * mozbrowser>, we will return |this| as its own parent.
  */
 nsPIDOMWindowOuter* nsGlobalWindowOuter::GetScriptableParent() {
-  nsCOMPtr<nsPIDOMWindowOuter> parent = GetParentOuter();
-  return parent.get();
+  if (!mDocShell) {
+    return nullptr;
+  }
+
+  if (mDocShell->GetIsMozBrowser()) {
+    return this;
+  }
+
+  nsCOMPtr<nsPIDOMWindowOuter> parent = GetParent();
+  return parent;
 }
 
 /**
  * Behavies identically to GetScriptableParent extept that it returns null
  * if GetScriptableParent would return this window.
  */
 nsPIDOMWindowOuter* nsGlobalWindowOuter::GetScriptableParentOrNull() {
   nsPIDOMWindowOuter* parent = GetScriptableParent();
@@ -2691,24 +2714,21 @@ static nsresult GetTopImpl(nsGlobalWindo
   nsCOMPtr<nsPIDOMWindowOuter> parent = aWin;
   do {
     if (!parent) {
       break;
     }
 
     prevParent = parent;
 
-    nsCOMPtr<nsPIDOMWindowOuter> newParent;
     if (aScriptable) {
-      newParent = parent->GetScriptableParent();
+      parent = parent->GetScriptableParent();
     } else {
-      newParent = parent->GetParent();
-    }
-
-    parent = newParent;
+      parent = parent->GetParent();
+    }
 
   } while (parent != prevParent);
 
   if (parent) {
     parent.swap(*aTop);
   }
 
   return NS_OK;
@@ -2755,26 +2775,27 @@ void nsGlobalWindowOuter::GetContentOute
   }
 
   aRetval.set(nullptr);
 }
 
 already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowOuter::GetContentInternal(
     ErrorResult& aError, CallerType aCallerType) {
   // First check for a named frame named "content"
-  nsCOMPtr<nsPIDOMWindowOuter> domWindow =
-      GetChildWindow(NS_LITERAL_STRING("content"));
-  if (domWindow) {
-    return domWindow.forget();
+  RefPtr<BrowsingContext> bc = GetChildWindow(NS_LITERAL_STRING("content"));
+  if (bc) {
+    nsCOMPtr<nsPIDOMWindowOuter> content(bc->GetDOMWindow());
+    return content.forget();
   }
 
   // If we're contained in <iframe mozbrowser>, then GetContent is the same as
   // window.top.
   if (mDocShell && mDocShell->GetIsInMozBrowser()) {
-    return GetTopOuter();
+    nsCOMPtr<nsPIDOMWindowOuter> domWindow(GetScriptableTop());
+    return domWindow.forget();
   }
 
   nsCOMPtr<nsIDocShellTreeItem> primaryContent;
   if (aCallerType != CallerType::System) {
     if (mDoc) {
       mDoc->WarnOnceAbout(nsIDocument::eWindowContentUntrusted);
     }
     // If we're called by non-chrome code, make sure we don't return
@@ -2802,17 +2823,17 @@ already_AddRefed<nsPIDOMWindowOuter> nsG
 
     treeOwner->GetPrimaryContentShell(getter_AddRefs(primaryContent));
   }
 
   if (!primaryContent) {
     return nullptr;
   }
 
-  domWindow = primaryContent->GetWindow();
+  nsCOMPtr<nsPIDOMWindowOuter> domWindow = primaryContent->GetWindow();
   return domWindow.forget();
 }
 
 nsresult nsGlobalWindowOuter::GetPrompter(nsIPrompt** aPrompt) {
   if (!mDocShell) return NS_ERROR_FAILURE;
 
   nsCOMPtr<nsIPrompt> prompter(do_GetInterface(mDocShell));
   NS_ENSURE_TRUE(prompter, NS_ERROR_NO_INTERFACE);
@@ -3545,31 +3566,30 @@ double nsGlobalWindowOuter::GetScrollXOu
 double nsGlobalWindowOuter::GetScrollYOuter() { return GetScrollXY(false).y; }
 
 uint32_t nsGlobalWindowOuter::Length() {
   nsDOMWindowList* windows = GetFrames();
 
   return windows ? windows->GetLength() : 0;
 }
 
-already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowOuter::GetTopOuter() {
+Nullable<WindowProxyHolder> nsGlobalWindowOuter::GetTopOuter() {
   nsCOMPtr<nsPIDOMWindowOuter> top = GetScriptableTop();
-  return top.forget();
-}
-
-nsPIDOMWindowOuter* nsGlobalWindowOuter::GetChildWindow(
+  BrowsingContext* topBC;
+  if (!top || !(topBC = top->GetBrowsingContext())) {
+    return nullptr;
+  }
+  return WindowProxyHolder(topBC);
+}
+
+already_AddRefed<BrowsingContext> nsGlobalWindowOuter::GetChildWindow(
     const nsAString& aName) {
-  nsCOMPtr<nsIDocShell> docShell(GetDocShell());
-  NS_ENSURE_TRUE(docShell, nullptr);
-
-  nsCOMPtr<nsIDocShellTreeItem> child;
-  docShell->FindChildWithName(aName, false, true, nullptr, nullptr,
-                              getter_AddRefs(child));
-
-  return child ? child->GetWindow() : nullptr;
+  NS_ENSURE_TRUE(mBrowsingContext, nullptr);
+
+  return mBrowsingContext->FindChildWithName(aName);
 }
 
 bool nsGlobalWindowOuter::DispatchCustomEvent(const nsAString& aEventName) {
   bool defaultActionEnabled = true;
   nsContentUtils::DispatchTrustedEvent(mDoc, ToSupports(this), aEventName,
                                        CanBubble::eYes, Cancelable::eYes,
                                        &defaultActionEnabled);
 
@@ -4455,18 +4475,18 @@ void nsGlobalWindowOuter::PromptOuter(co
   nsString outValue;
   outValue.Adopt(inoutValue);
 
   if (ok && inoutValue) {
     aReturn.Assign(outValue);
   }
 }
 
-void nsGlobalWindowOuter::FocusOuter(ErrorResult& aError) {
-  nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+void nsGlobalWindowOuter::FocusOuter() {
+  nsFocusManager* fm = nsFocusManager::GetFocusManager();
   if (!fm) {
     return;
   }
 
   nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(mDocShell);
 
   bool isVisible = false;
   if (baseWin) {
@@ -4546,26 +4566,32 @@ void nsGlobalWindowOuter::FocusOuter(Err
     if (!parentdoc) {
       return;
     }
 
     RefPtr<Element> frame = parentdoc->FindContentForSubDocument(mDoc);
     if (frame) {
       uint32_t flags = nsIFocusManager::FLAG_NOSCROLL;
       if (canFocus) flags |= nsIFocusManager::FLAG_RAISE;
-      aError = fm->SetFocus(frame, flags);
+      DebugOnly<nsresult> rv = fm->SetFocus(frame, flags);
+      MOZ_ASSERT(NS_SUCCEEDED(rv),
+                 "SetFocus only fails if the first argument is null, "
+                 "but we pass an element");
     }
     return;
   }
 
   if (canFocus) {
     // if there is no parent, this must be a toplevel window, so raise the
     // window if canFocus is true. If this is a child process, the raise
     // window request will get forwarded to the parent by the puppet widget.
-    aError = fm->SetActiveWindow(this);
+    DebugOnly<nsresult> rv = fm->SetActiveWindow(this);
+    MOZ_ASSERT(NS_SUCCEEDED(rv),
+               "SetActiveWindow only fails if passed null or a non-toplevel "
+               "window, which is not the case here.");
   }
 }
 
 nsresult nsGlobalWindowOuter::Focus() {
   FORWARD_TO_INNER(Focus, (), NS_ERROR_UNEXPECTED);
 }
 
 void nsGlobalWindowOuter::BlurOuter() {
@@ -5222,22 +5248,26 @@ void nsGlobalWindowOuter::FireAbuseEvent
     ios->NewURI(NS_ConvertUTF16toUTF8(aPopupURL), nullptr, baseURL,
                 getter_AddRefs(popupURI));
 
   // fire an event block full of informative URIs
   FirePopupBlockedEvent(topDoc, popupURI, aPopupWindowName,
                         aPopupWindowFeatures);
 }
 
-already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowOuter::OpenOuter(
+Nullable<WindowProxyHolder> nsGlobalWindowOuter::OpenOuter(
     const nsAString& aUrl, const nsAString& aName, const nsAString& aOptions,
     ErrorResult& aError) {
   nsCOMPtr<nsPIDOMWindowOuter> window;
   aError = OpenJS(aUrl, aName, aOptions, getter_AddRefs(window));
-  return window.forget();
+  RefPtr<BrowsingContext> bc;
+  if (!window || !(bc = window->GetBrowsingContext())) {
+    return nullptr;
+  }
+  return WindowProxyHolder(bc.forget());
 }
 
 nsresult nsGlobalWindowOuter::Open(const nsAString& aUrl,
                                    const nsAString& aName,
                                    const nsAString& aOptions,
                                    nsDocShellLoadState* aLoadState,
                                    bool aForceNoOpener,
                                    nsPIDOMWindowOuter** _retval) {
@@ -5297,17 +5327,17 @@ nsresult nsGlobalWindowOuter::OpenDialog
                       false,             // aDoJSFixups
                       false,             // aNavigate
                       nullptr, nullptr,  // No args
                       nullptr,           // aLoadState
                       false,             // aForceNoOpener
                       _retval);
 }
 
-already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowOuter::OpenDialogOuter(
+Nullable<WindowProxyHolder> nsGlobalWindowOuter::OpenDialogOuter(
     JSContext* aCx, const nsAString& aUrl, const nsAString& aName,
     const nsAString& aOptions, const Sequence<JS::Value>& aExtraArgument,
     ErrorResult& aError) {
   nsCOMPtr<nsIJSArgArray> argvArray;
   aError =
       NS_CreateJSArgv(aCx, aExtraArgument.Length(), aExtraArgument.Elements(),
                       getter_AddRefs(argvArray));
   if (aError.Failed()) {
@@ -5320,25 +5350,30 @@ already_AddRefed<nsPIDOMWindowOuter> nsG
                         false,               // aContentModal
                         false,               // aCalledNoScript
                         false,               // aDoJSFixups
                         true,                // aNavigate
                         argvArray, nullptr,  // Arguments
                         nullptr,             // aLoadState
                         false,               // aForceNoOpener
                         getter_AddRefs(dialog));
-  return dialog.forget();
-}
-
-already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowOuter::GetFramesOuter() {
+  RefPtr<BrowsingContext> bc;
+  if (!dialog || !(bc = dialog->GetBrowsingContext())) {
+    return nullptr;
+  }
+  return WindowProxyHolder(bc.forget());
+}
+
+BrowsingContext* nsGlobalWindowOuter::GetFramesOuter() {
   RefPtr<nsPIDOMWindowOuter> frames(this);
   FlushPendingNotifications(FlushType::ContentAndNotify);
-  return frames.forget();
-}
-
+  return mBrowsingContext;
+}
+
+/* static */
 nsGlobalWindowInner* nsGlobalWindowOuter::CallerInnerWindow(JSContext* aCx) {
   nsIGlobalObject* global = GetIncumbentGlobal();
   NS_ENSURE_TRUE(global, nullptr);
   JS::Rooted<JSObject*> scope(aCx, global->GetGlobalJSObject());
   NS_ENSURE_TRUE(scope, nullptr);
 
   // When Jetpack runs content scripts inside a sandbox, it uses
   // sandboxPrototype to make them appear as though they're running in the
@@ -5360,115 +5395,147 @@ nsGlobalWindowInner* nsGlobalWindowOuter
   }
 
   // The calling window must be holding a reference, so we can return a weak
   // pointer.
   nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(global);
   return nsGlobalWindowInner::Cast(win);
 }
 
-void nsGlobalWindowOuter::PostMessageMozOuter(JSContext* aCx,
-                                              JS::Handle<JS::Value> aMessage,
-                                              const nsAString& aTargetOrigin,
-                                              JS::Handle<JS::Value> aTransfer,
-                                              nsIPrincipal& aSubjectPrincipal,
-                                              ErrorResult& aError) {
+/* static */
+bool nsGlobalWindowOuter::GatherPostMessageData(
+    JSContext* aCx, const nsAString& aTargetOrigin, BrowsingContext** aSource,
+    nsAString& aOrigin, nsIURI** aTargetOriginURI,
+    nsIPrincipal** aCallerPrincipal, nsGlobalWindowInner** aCallerInnerWindow,
+    nsIURI** aCallerDocumentURI, ErrorResult& aError) {
   //
   // Window.postMessage is an intentional subversion of the same-origin policy.
   // As such, this code must be particularly careful in the information it
   // exposes to calling code.
   //
   // http://www.whatwg.org/specs/web-apps/current-work/multipage/section-crossDocumentMessages.html
   //
 
   // First, get the caller's window
   RefPtr<nsGlobalWindowInner> callerInnerWin = CallerInnerWindow(aCx);
   nsIPrincipal* callerPrin;
   if (callerInnerWin) {
+    nsCOMPtr<nsIDocument> doc = callerInnerWin->GetExtantDoc();
+    if (!doc) {
+      return false;
+    }
+    NS_IF_ADDREF(*aCallerDocumentURI = doc->GetDocumentURI());
+
     // Compute the caller's origin either from its principal or, in the case the
     // principal doesn't carry a URI (e.g. the system principal), the caller's
     // document.  We must get this now instead of when the event is created and
     // dispatched, because ultimately it is the identity of the calling window
     // *now* that determines who sent the message (and not an identity which
     // might have changed due to intervening navigations).
     callerPrin = callerInnerWin->GetPrincipal();
   } else {
     // In case the global is not a window, it can be a sandbox, and the
     // sandbox's principal can be used for the security check.
     nsIGlobalObject* global = GetIncumbentGlobal();
     NS_ASSERTION(global, "Why is there no global object?");
     callerPrin = global->PrincipalOrNull();
   }
   if (!callerPrin) {
-    return;
+    return false;
   }
 
   nsCOMPtr<nsIURI> callerOuterURI;
   if (NS_FAILED(callerPrin->GetURI(getter_AddRefs(callerOuterURI)))) {
-    return;
-  }
-
-  nsAutoString origin;
+    return false;
+  }
+
   if (callerOuterURI) {
     // if the principal has a URI, use that to generate the origin
-    nsContentUtils::GetUTFOrigin(callerPrin, origin);
+    nsContentUtils::GetUTFOrigin(callerPrin, aOrigin);
   } else if (callerInnerWin) {
+    if (!*aCallerDocumentURI) {
+      return false;
+    }
     // otherwise use the URI of the document to generate origin
-    nsCOMPtr<nsIDocument> doc = callerInnerWin->GetExtantDoc();
-    if (!doc) {
-      return;
-    }
-    callerOuterURI = doc->GetDocumentURI();
-    // if the principal has a URI, use that to generate the origin
-    nsContentUtils::GetUTFOrigin(callerOuterURI, origin);
+    nsContentUtils::GetUTFOrigin(*aCallerDocumentURI, aOrigin);
   } else {
     // in case of a sandbox with a system principal origin can be empty
     if (!nsContentUtils::IsSystemPrincipal(callerPrin)) {
-      return;
-    }
-  }
+      return false;
+    }
+  }
+  NS_IF_ADDREF(*aCallerPrincipal = callerPrin);
+
+  // "/" indicates same origin as caller, "*" indicates no specific origin is
+  // required.
+  if (!aTargetOrigin.EqualsASCII("/") && !aTargetOrigin.EqualsASCII("*")) {
+    nsCOMPtr<nsIURI> targetOriginURI;
+    if (NS_FAILED(NS_NewURI(getter_AddRefs(targetOriginURI), aTargetOrigin))) {
+      aError.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+      return false;
+    }
+
+    nsresult rv = NS_MutateURI(targetOriginURI)
+                      .SetUserPass(EmptyCString())
+                      .SetPathQueryRef(EmptyCString())
+                      .Finalize(aTargetOriginURI);
+    if (NS_FAILED(rv)) {
+      return false;
+    }
+  }
+
+  if (!nsContentUtils::IsCallerChrome() && callerInnerWin &&
+      callerInnerWin->GetOuterWindowInternal()) {
+    NS_ADDREF(*aSource = callerInnerWin->GetOuterWindowInternal()
+                             ->GetBrowsingContext());
+  } else {
+    *aSource = nullptr;
+  }
+
+  callerInnerWin.forget(aCallerInnerWindow);
+
+  return true;
+}
+
+bool nsGlobalWindowOuter::GetPrincipalForPostMessage(
+    const nsAString& aTargetOrigin, nsIURI* aTargetOriginURI,
+    nsIPrincipal* aCallerPrincipal, nsIPrincipal& aSubjectPrincipal,
+    nsIPrincipal** aProvidedPrincipal) {
+  //
+  // Window.postMessage is an intentional subversion of the same-origin policy.
+  // As such, this code must be particularly careful in the information it
+  // exposes to calling code.
+  //
+  // http://www.whatwg.org/specs/web-apps/current-work/multipage/section-crossDocumentMessages.html
+  //
 
   // Convert the provided origin string into a URI for comparison purposes.
   nsCOMPtr<nsIPrincipal> providedPrincipal;
 
   if (aTargetOrigin.EqualsASCII("/")) {
-    providedPrincipal = callerPrin;
+    providedPrincipal = aCallerPrincipal;
   }
   // "*" indicates no specific origin is required.
   else if (!aTargetOrigin.EqualsASCII("*")) {
-    nsCOMPtr<nsIURI> originURI;
-    if (NS_FAILED(NS_NewURI(getter_AddRefs(originURI), aTargetOrigin))) {
-      aError.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-      return;
-    }
-
-    nsresult rv = NS_MutateURI(originURI)
-                      .SetUserPass(EmptyCString())
-                      .SetPathQueryRef(EmptyCString())
-                      .Finalize(originURI);
-    if (NS_FAILED(rv)) {
-      return;
-    }
-
     OriginAttributes attrs = aSubjectPrincipal.OriginAttributesRef();
     if (aSubjectPrincipal.GetIsSystemPrincipal()) {
       auto principal = BasePrincipal::Cast(GetPrincipal());
 
       if (attrs != principal->OriginAttributesRef()) {
         nsCOMPtr<nsIURI> targetURI;
         nsAutoCString targetURL;
         nsAutoCString sourceOrigin;
         nsAutoCString targetOrigin;
 
         if (NS_FAILED(principal->GetURI(getter_AddRefs(targetURI))) ||
             NS_FAILED(targetURI->GetAsciiSpec(targetURL)) ||
             NS_FAILED(principal->GetOrigin(targetOrigin)) ||
             NS_FAILED(aSubjectPrincipal.GetOrigin(sourceOrigin))) {
           NS_WARNING("Failed to get source and target origins");
-          return;
+          return false;
         }
 
         nsContentUtils::LogSimpleConsoleError(
             NS_ConvertUTF8toUTF16(nsPrintfCString(
                 R"(Attempting to post a message to window with url "%s" and )"
                 R"(origin "%s" from a system principal scope with mismatched )"
                 R"(origin "%s".)",
                 targetURL.get(), targetOrigin.get(), sourceOrigin.get())),
@@ -5476,26 +5543,26 @@ void nsGlobalWindowOuter::PostMessageMoz
 
         attrs = principal->OriginAttributesRef();
       }
     }
 
     // Create a nsIPrincipal inheriting the app/browser attributes from the
     // caller.
     providedPrincipal =
-        BasePrincipal::CreateCodebasePrincipal(originURI, attrs);
+        BasePrincipal::CreateCodebasePrincipal(aTargetOriginURI, attrs);
     if (NS_WARN_IF(!providedPrincipal)) {
-      return;
+      return false;
     }
   } else {
     // We still need to check the originAttributes if the target origin is '*'.
     // But we will ingore the FPD here since the FPDs are possible to be
     // different.
     auto principal = BasePrincipal::Cast(GetPrincipal());
-    NS_ENSURE_TRUE_VOID(principal);
+    NS_ENSURE_TRUE(principal, false);
 
     OriginAttributes targetAttrs = principal->OriginAttributesRef();
     OriginAttributes sourceAttrs = aSubjectPrincipal.OriginAttributesRef();
     // We have to exempt the check of OA if the subject prioncipal is a system
     // principal since there are many tests try to post messages to content from
     // chrome with a mismatch OA. For example, using the ContentTask.spawn() to
     // post a message into a private browsing window. The injected code in
     // ContentTask.spawn() will be executed under the system principal and the
@@ -5504,33 +5571,58 @@ void nsGlobalWindowOuter::PostMessageMoz
     MOZ_DIAGNOSTIC_ASSERT(aSubjectPrincipal.GetIsSystemPrincipal() ||
                           sourceAttrs.EqualsIgnoringFPD(targetAttrs));
 
     // If 'privacy.firstparty.isolate.block_post_message' is true, we will block
     // postMessage across different first party domains.
     if (OriginAttributes::IsBlockPostMessageForFPI() &&
         !aSubjectPrincipal.GetIsSystemPrincipal() &&
         sourceAttrs.mFirstPartyDomain != targetAttrs.mFirstPartyDomain) {
-      return;
-    }
+      return false;
+    }
+  }
+
+  providedPrincipal.forget(aProvidedPrincipal);
+  return true;
+}
+
+void nsGlobalWindowOuter::PostMessageMozOuter(JSContext* aCx,
+                                              JS::Handle<JS::Value> aMessage,
+                                              const nsAString& aTargetOrigin,
+                                              JS::Handle<JS::Value> aTransfer,
+                                              nsIPrincipal& aSubjectPrincipal,
+                                              ErrorResult& aError) {
+  RefPtr<BrowsingContext> sourceBc;
+  nsAutoString origin;
+  nsCOMPtr<nsIURI> targetOriginURI;
+  nsCOMPtr<nsIPrincipal> callerPrincipal;
+  RefPtr<nsGlobalWindowInner> callerInnerWindow;
+  nsCOMPtr<nsIURI> callerDocumentURI;
+  if (!GatherPostMessageData(aCx, aTargetOrigin, getter_AddRefs(sourceBc),
+                             origin, getter_AddRefs(targetOriginURI),
+                             getter_AddRefs(callerPrincipal),
+                             getter_AddRefs(callerInnerWindow),
+                             getter_AddRefs(callerDocumentURI), aError)) {
+    return;
+  }
+
+  nsCOMPtr<nsIPrincipal> providedPrincipal;
+  if (!GetPrincipalForPostMessage(aTargetOrigin, targetOriginURI,
+                                  callerPrincipal, aSubjectPrincipal,
+                                  getter_AddRefs(providedPrincipal))) {
+    return;
   }
 
   // Create and asynchronously dispatch a runnable which will handle actual DOM
   // event creation and dispatch.
-  RefPtr<PostMessageEvent> event =
-      new PostMessageEvent(nsContentUtils::IsCallerChrome() || !callerInnerWin
-                               ? nullptr
-                               : callerInnerWin->GetOuterWindowInternal(),
-                           origin, this, providedPrincipal,
-                           callerInnerWin ? callerInnerWin->GetDoc() : nullptr);
-
-  JS::Rooted<JS::Value> message(aCx, aMessage);
-  JS::Rooted<JS::Value> transfer(aCx, aTransfer);
-
-  event->Write(aCx, message, transfer, JS::CloneDataPolicy(), aError);
+  RefPtr<PostMessageEvent> event = new PostMessageEvent(
+      sourceBc, origin, this, providedPrincipal,
+      callerInnerWindow ? callerInnerWindow->WindowID() : 0, callerDocumentURI);
+
+  event->Write(aCx, aMessage, aTransfer, aError);
   if (NS_WARN_IF(aError.Failed())) {
     return;
   }
 
   aError = Dispatch(TaskCategory::Other, event.forget());
 }
 
 class nsCloseEvent : public Runnable {
@@ -6085,17 +6177,17 @@ bool nsGlobalWindowOuter::FindOuter(cons
   aError = finder->FindNext(&didFind);
   return didFind;
 }
 
 //*****************************************************************************
 // EventTarget
 //*****************************************************************************
 
-nsPIDOMWindowOuter* nsGlobalWindowOuter::GetOwnerGlobalForBindings() {
+nsPIDOMWindowOuter* nsGlobalWindowOuter::GetOwnerGlobalForBindingsInternal() {
   return this;
 }
 
 bool nsGlobalWindowOuter::DispatchEvent(Event& aEvent, CallerType aCallerType,
                                         ErrorResult& aRv) {
   FORWARD_TO_INNER(DispatchEvent, (aEvent, aCallerType, aRv), false);
 }
 
@@ -6150,17 +6242,17 @@ nsPIDOMWindowOuter* nsGlobalWindowOuter:
   }
 
   return top;
 }
 
 // This has a caller in Windows-only code (nsNativeAppSupportWin).
 Location* nsGlobalWindowOuter::GetLocation() {
   // This method can be called on the outer window as well.
-  FORWARD_TO_INNER(GetLocation, (), nullptr);
+  FORWARD_TO_INNER(Location, (), nullptr);
 }
 
 void nsGlobalWindowOuter::ActivateOrDeactivate(bool aActivate) {
   if (!mDoc) {
     return;
   }
 
   // Set / unset mIsActive on the top level window, which is used for the
@@ -7042,23 +7134,23 @@ ChromeMessageBroadcaster* nsGlobalWindow
   if (!mInnerWindow) {
     NS_WARNING("No inner window available!");
     return nullptr;
   }
   return GetCurrentInnerWindowInternal()->GetGroupMessageManager(aGroup);
 }
 
 void nsPIDOMWindowOuter::SetOpenerForInitialContentBrowser(
-    nsPIDOMWindowOuter* aOpenerWindow) {
+    BrowsingContext* aOpenerWindow) {
   MOZ_ASSERT(!mOpenerForInitialContentBrowser,
              "Don't set OpenerForInitialContentBrowser twice!");
   mOpenerForInitialContentBrowser = aOpenerWindow;
 }
 
-already_AddRefed<nsPIDOMWindowOuter>
+already_AddRefed<BrowsingContext>
 nsPIDOMWindowOuter::TakeOpenerForInitialContentBrowser() {
   // Intentionally forget our own member
   return mOpenerForInitialContentBrowser.forget();
 }
 
 void nsGlobalWindowOuter::InitWasOffline() { mWasOffline = NS_IsOffline(); }
 
 #if defined(MOZ_WIDGET_ANDROID)
@@ -7249,17 +7341,17 @@ nsGlobalWindowOuter::TemporarilyDisableD
   }
 }
 
 mozilla::dom::TabGroup* nsPIDOMWindowOuter::TabGroup() {
   return nsGlobalWindowOuter::Cast(this)->TabGroupOuter();
 }
 
 /* static */ already_AddRefed<nsGlobalWindowOuter> nsGlobalWindowOuter::Create(
-    nsIDocShell* aDocShell, bool aIsChrome) {
+    nsDocShell* aDocShell, bool aIsChrome) {
   uint64_t outerWindowID = aDocShell->GetOuterWindowID();
   RefPtr<nsGlobalWindowOuter> window = new nsGlobalWindowOuter(outerWindowID);
   if (aIsChrome) {
     window->mIsChrome = true;
   }
   window->SetDocShell(aDocShell);
 
   window->InitWasOffline();
@@ -7392,13 +7484,8 @@ nsPIDOMWindowOuter::nsPIDOMWindowOuter(u
       mIsRootOuterWindow(false),
       mInnerWindow(nullptr),
       mWindowID(aWindowID),
       mMarkedCCGeneration(0),
       mServiceWorkersTestingEnabled(false),
       mLargeAllocStatus(LargeAllocStatus::NONE) {}
 
 nsPIDOMWindowOuter::~nsPIDOMWindowOuter() {}
-
-mozilla::dom::BrowsingContext* nsPIDOMWindowOuter::GetBrowsingContext() const {
-  return mDocShell ? nsDocShell::Cast(mDocShell)->GetBrowsingContext()
-                   : nullptr;
-}
--- a/dom/base/nsGlobalWindowOuter.h
+++ b/dom/base/nsGlobalWindowOuter.h
@@ -52,16 +52,17 @@
 #include "mozilla/dom/WindowBinding.h"
 #include "Units.h"
 #include "nsComponentManagerUtils.h"
 #include "nsSize.h"
 #include "nsCheapSets.h"
 #include "mozilla/dom/ImageBitmapSource.h"
 #include "mozilla/UniquePtr.h"
 
+class nsDocShell;
 class nsIArray;
 class nsIBaseWindow;
 class nsIContent;
 class nsICSSDeclaration;
 class nsIDocShellTreeOwner;
 class nsIDOMWindowUtils;
 class nsIScrollableFrame;
 class nsIControllers;
@@ -88,16 +89,17 @@ class IdleRequestExecutor;
 
 struct IdleObserverHolder;
 
 namespace mozilla {
 class AbstractThread;
 class DOMEventTargetHelper;
 namespace dom {
 class BarProp;
+class BrowsingContext;
 struct ChannelPixelLayout;
 class Console;
 class Crypto;
 class CustomElementRegistry;
 class DocGroup;
 class External;
 class Function;
 class Gamepad;
@@ -106,16 +108,17 @@ class IdleRequest;
 class IdleRequestCallback;
 class IncrementalRunnable;
 class IntlUtils;
 class Location;
 class MediaQueryList;
 class Navigator;
 class OwningExternalOrWindowProxy;
 class Promise;
+class PostMessageData;
 class PostMessageEvent;
 struct RequestInit;
 class RequestOrUSVString;
 class Selection;
 class SpeechSynthesis;
 class TabGroup;
 class Timeout;
 class U2F;
@@ -211,17 +214,17 @@ class nsGlobalWindowOuter final : public
     return sOuterWindowsById;
   }
 
   static nsGlobalWindowOuter* FromSupports(nsISupports* supports) {
     // Make sure this matches the casts we do in QueryInterface().
     return (nsGlobalWindowOuter*)(mozilla::dom::EventTarget*)supports;
   }
 
-  static already_AddRefed<nsGlobalWindowOuter> Create(nsIDocShell* aDocShell,
+  static already_AddRefed<nsGlobalWindowOuter> Create(nsDocShell* aDocShell,
                                                       bool aIsChrome);
 
   // public methods
   nsPIDOMWindowOuter* GetPrivateParent();
 
   // callback for close event
   void ReallyCloseWindow();
 
@@ -266,17 +269,17 @@ class nsGlobalWindowOuter final : public
 
   virtual mozilla::EventListenerManager* GetExistingListenerManager()
       const override;
 
   virtual mozilla::EventListenerManager* GetOrCreateListenerManager() override;
 
   bool ComputeDefaultWantsUntrusted(mozilla::ErrorResult& aRv) final;
 
-  virtual nsPIDOMWindowOuter* GetOwnerGlobalForBindings() override;
+  virtual nsPIDOMWindowOuter* GetOwnerGlobalForBindingsInternal() override;
 
   virtual nsIGlobalObject* GetOwnerGlobal() const override;
 
   EventTarget* GetTargetForEventTargetChain() override;
 
   using mozilla::dom::EventTarget::DispatchEvent;
   bool DispatchEvent(mozilla::dom::Event& aEvent,
                      mozilla::dom::CallerType aCallerType,
@@ -357,17 +360,18 @@ class nsGlobalWindowOuter final : public
   already_AddRefed<nsPIDOMWindowOuter> IndexedGetterOuter(uint32_t aIndex);
 
   already_AddRefed<nsPIDOMWindowOuter> GetTop() override;
   nsPIDOMWindowOuter* GetScriptableTop() override;
   inline nsGlobalWindowOuter* GetTopInternal();
 
   inline nsGlobalWindowOuter* GetScriptableTopInternal();
 
-  nsPIDOMWindowOuter* GetChildWindow(const nsAString& aName);
+  already_AddRefed<mozilla::dom::BrowsingContext> GetChildWindow(
+      const nsAString& aName);
 
   // These return true if we've reached the state in this top level window
   // where we ask the user if further dialogs should be blocked.
   //
   // DialogsAreBeingAbused must be called on the scriptable top inner window.
   //
   // ShouldPromptToBlockDialogs is implemented in terms of
   // DialogsAreBeingAbused, and will get the scriptable top inner window
@@ -518,45 +522,44 @@ class nsGlobalWindowOuter final : public
   mozilla::dom::Location* GetLocation() override;
   void GetStatusOuter(nsAString& aStatus);
   void SetStatusOuter(const nsAString& aStatus);
   void CloseOuter(bool aTrustedCaller);
   nsresult Close() override;
   bool GetClosedOuter();
   bool Closed() override;
   void StopOuter(mozilla::ErrorResult& aError);
-  void FocusOuter(mozilla::ErrorResult& aError);
+  void FocusOuter();
   nsresult Focus() override;
   void BlurOuter();
-  already_AddRefed<nsPIDOMWindowOuter> GetFramesOuter();
+  mozilla::dom::BrowsingContext* GetFramesOuter();
   nsDOMWindowList* GetFrames() final;
   uint32_t Length();
-  already_AddRefed<nsPIDOMWindowOuter> GetTopOuter();
+  mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> GetTopOuter();
 
   nsresult GetPrompter(nsIPrompt** aPrompt) override;
 
  protected:
   nsPIDOMWindowOuter* GetOpenerWindowOuter();
   // Initializes the mWasOffline member variable
   void InitWasOffline();
 
  public:
   nsPIDOMWindowOuter* GetSanitizedOpener(nsPIDOMWindowOuter* aOpener);
 
   already_AddRefed<nsPIDOMWindowOuter> GetOpener() override;
-  already_AddRefed<nsPIDOMWindowOuter> GetParentOuter();
+  mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> GetParentOuter();
   already_AddRefed<nsPIDOMWindowOuter> GetParent() override;
   nsPIDOMWindowOuter* GetScriptableParent() override;
   nsPIDOMWindowOuter* GetScriptableParentOrNull() override;
   mozilla::dom::Element* GetFrameElementOuter(nsIPrincipal& aSubjectPrincipal);
   mozilla::dom::Element* GetFrameElement() override;
-  already_AddRefed<nsPIDOMWindowOuter> OpenOuter(const nsAString& aUrl,
-                                                 const nsAString& aName,
-                                                 const nsAString& aOptions,
-                                                 mozilla::ErrorResult& aError);
+  mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> OpenOuter(
+      const nsAString& aUrl, const nsAString& aName, const nsAString& aOptions,
+      mozilla::ErrorResult& aError);
   nsresult Open(const nsAString& aUrl, const nsAString& aName,
                 const nsAString& aOptions, nsDocShellLoadState* aLoadState,
                 bool aForceNoOpener, nsPIDOMWindowOuter** _retval) override;
   mozilla::dom::Navigator* GetNavigator() override;
 
 #if defined(MOZ_WIDGET_ANDROID)
   int16_t Orientation(mozilla::dom::CallerType aCallerType) const;
 #endif
@@ -610,17 +613,17 @@ class nsGlobalWindowOuter final : public
   nsresult SetFullScreen(bool aFullscreen) override;
   bool FindOuter(const nsAString& aString, bool aCaseSensitive, bool aBackwards,
                  bool aWrapAround, bool aWholeWord, bool aSearchInFrames,
                  bool aShowDialog, mozilla::ErrorResult& aError);
   uint64_t GetMozPaintCountOuter();
 
   bool ShouldResistFingerprinting();
 
-  already_AddRefed<nsPIDOMWindowOuter> OpenDialogOuter(
+  mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> OpenDialogOuter(
       JSContext* aCx, const nsAString& aUrl, const nsAString& aName,
       const nsAString& aOptions,
       const mozilla::dom::Sequence<JS::Value>& aExtraArgument,
       mozilla::ErrorResult& aError);
   nsresult OpenDialog(const nsAString& aUrl, const nsAString& aName,
                       const nsAString& aOptions, nsISupports* aExtraArgument,
                       nsPIDOMWindowOuter** _retval) override;
   void UpdateCommands(const nsAString& anAction, mozilla::dom::Selection* aSel,
@@ -750,17 +753,17 @@ class nsGlobalWindowOuter final : public
   void CleanUp();
   void ClearControllers();
   // Outer windows only.
   void FinalClose();
 
   inline void MaybeClearInnerWindow(nsGlobalWindowInner* aExpectedInner);
 
   // We need a JSContext to get prototypes inside CallerInnerWindow.
-  nsGlobalWindowInner* CallerInnerWindow(JSContext* aCx);
+  static nsGlobalWindowInner* CallerInnerWindow(JSContext* aCx);
 
   // Get the parent, returns null if this is a toplevel window
   nsPIDOMWindowOuter* GetParentInternal();
 
  public:
   // popup tracking
   bool IsPopupSpamWindow();
 
@@ -960,16 +963,80 @@ class nsGlobalWindowOuter final : public
                                    mozilla::ErrorResult& aError);
 
   void PostMessageMozOuter(JSContext* aCx, JS::Handle<JS::Value> aMessage,
                            const nsAString& aTargetOrigin,
                            JS::Handle<JS::Value> aTransfer,
                            nsIPrincipal& aSubjectPrincipal,
                            mozilla::ErrorResult& aError);
 
+ public:
+  /**
+   * Compute the principal to use for checking against the target principal in a
+   * postMessage call.
+   *
+   * @param aTargetOrigin The value passed as the targetOrigin argument to the
+   * postMessage call.
+   *
+   * @param aTargetOriginURI The origin of the URI contained in aTargetOrigin
+   * (see GatherPostMessageData).
+   *
+   * @param aCallerPrincipal The principal of the incumbent global of the
+   * postMessage call (see GatherPostMessageData).
+   *
+   * @param aSubjectPrincipal The subject principal for the postMessage call.
+   *
+   * @param aProvidedPrincipal [out] The principal to use for checking against
+   * the target's principal.
+   *
+   * @return Whether the postMessage call should continue or return now.
+   */
+  bool GetPrincipalForPostMessage(const nsAString& aTargetOrigin,
+                                  nsIURI* aTargetOriginURI,
+                                  nsIPrincipal* aCallerPrincipal,
+                                  nsIPrincipal& aSubjectPrincipal,
+                                  nsIPrincipal** aProvidedPrincipal);
+
+ private:
+  /**
+   * Gather the necessary data from the caller for a postMessage call.
+   *
+   * @param aCx The JSContext.
+   *
+   * @param aTargetOrigin The value passed as the targetOrigin argument to the
+   * postMessage call.
+   *
+   * @param aSource [out] The browsing context for the incumbent global.
+   *
+   * @param aOrigin [out] The value to use for the origin property of the
+   * MessageEvent object.
+   *
+   * @param aTargetOriginURI [out] The origin of the URI contained in
+   * aTargetOrigin, null if aTargetOrigin is "/" or "*".
+   *
+   * @param aCallerPrincipal [out] The principal of the incumbent global of the
+   *                               postMessage call.
+   *
+   * @param aCallerInnerWindow [out] Inner window of the caller of
+   * postMessage, or null if the incumbent global is not a Window.
+   *
+   * @param aCallerDocumentURI [out] The URI of the document of the incumbent
+   * global if it's a Window, null otherwise.
+   *
+   * @param aError [out] The error, if any.
+   *
+   * @return Whether the postMessage call should continue or return now.
+   */
+  static bool GatherPostMessageData(
+      JSContext* aCx, const nsAString& aTargetOrigin,
+      mozilla::dom::BrowsingContext** aSource, nsAString& aOrigin,
+      nsIURI** aTargetOriginURI, nsIPrincipal** aCallerPrincipal,
+      nsGlobalWindowInner** aCallerInnerWindow, nsIURI** aCallerDocumentURI,
+      mozilla::ErrorResult& aError);
+
   // Ask the user if further dialogs should be blocked, if dialogs are currently
   // being abused. This is used in the cases where we have no modifiable UI to
   // show, in that case we show a separate dialog to ask this question.
   bool ConfirmDialogIfNeeded();
 
   // Helper called after moving/resizing, to update docShell's presContext
   // if we have caused a resolution change by moving across monitors.
   void CheckForDPIChange();
@@ -977,17 +1044,17 @@ class nsGlobalWindowOuter final : public
  private:
   enum class SecureContextFlags { eDefault, eIgnoreOpener };
   // Called only on outer windows to compute the value that will be returned by
   // IsSecureContext() for the inner window that corresponds to aDocument.
   bool ComputeIsSecureContext(
       nsIDocument* aDocument,
       SecureContextFlags aFlags = SecureContextFlags::eDefault);
 
-  void SetDocShell(nsIDocShell* aDocShell);
+  void SetDocShell(nsDocShell* aDocShell);
 
   // nsPIDOMWindow{Inner,Outer} should be able to see these helper methods.
   friend class nsPIDOMWindowInner;
   friend class nsPIDOMWindowOuter;
 
   mozilla::dom::TabGroup* TabGroupOuter();
 
   void SetIsBackgroundInternal(bool aIsBackground);
@@ -1095,16 +1162,17 @@ class nsGlobalWindowOuter final : public
     // A weak pointer to the nsPresShell that we are doing fullscreen for.
     // The pointer being set indicates we've set the IsInFullscreenChange
     // flag on this pres shell.
     nsWeakPtr mFullscreenPresShell;
   } mChromeFields;
 
   friend class nsDOMScriptableHelper;
   friend class nsDOMWindowUtils;
+  friend class mozilla::dom::BrowsingContext;
   friend class mozilla::dom::PostMessageEvent;
   friend class DesktopNotification;
   friend class mozilla::dom::TimeoutManager;
   friend class IdleRequestExecutor;
   friend class nsGlobalWindowInner;
 };
 
 // XXX: EWW - This is an awful hack - let's not do this
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -43,16 +43,17 @@
 #include "mozilla/net/ReferrerPolicy.h"  // for member
 #include "mozilla/UseCounter.h"
 #include "mozilla/WeakPtr.h"
 #include "Units.h"
 #include "nsContentListDeclarations.h"
 #include "nsExpirationTracker.h"
 #include "nsClassHashtable.h"
 #include "mozilla/CORSMode.h"
+#include "mozilla/dom/BrowsingContext.h"
 #include "mozilla/dom/ContentBlockingLog.h"
 #include "mozilla/dom/DispatcherTrait.h"
 #include "mozilla/dom/DocumentOrShadowRoot.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/NotNull.h"
 #include "mozilla/SegmentedVector.h"
 #include "mozilla/ServoBindingTypes.h"
 #include "mozilla/StyleSheet.h"
@@ -3074,17 +3075,18 @@ class nsIDocument : public nsINode,
   nsIHTMLCollection* Plugins() { return Embeds(); }
   nsIHTMLCollection* Links();
   nsIHTMLCollection* Forms();
   nsIHTMLCollection* Scripts();
   already_AddRefed<nsContentList> GetElementsByName(const nsAString& aName) {
     return GetFuncStringContentList<nsCachableElementsByNameNodeList>(
         this, MatchNameAttribute, nullptr, UseExistingNameString, aName);
   }
-  nsPIDOMWindowOuter* GetDefaultView() const { return GetWindow(); }
+  mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> GetDefaultView()
+      const;
   Element* GetActiveElement();
   bool HasFocus(mozilla::ErrorResult& rv) const;
   nsIHTMLCollection* Applets();
   nsIHTMLCollection* Anchors();
   mozilla::TimeStamp LastFocusTime() const;
   void SetLastFocusTime(const mozilla::TimeStamp& aFocusTime);
   // Event handlers are all on nsINode already
   bool MozSyntheticDocument() const { return IsSyntheticDocument(); }
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -1042,17 +1042,17 @@ nsresult nsINode::PostHandleEvent(EventC
 EventListenerManager* nsINode::GetOrCreateListenerManager() {
   return nsContentUtils::GetListenerManagerForNode(this);
 }
 
 EventListenerManager* nsINode::GetExistingListenerManager() const {
   return nsContentUtils::GetExistingListenerManagerForNode(this);
 }
 
-nsPIDOMWindowOuter* nsINode::GetOwnerGlobalForBindings() {
+nsPIDOMWindowOuter* nsINode::GetOwnerGlobalForBindingsInternal() {
   bool dummy;
   auto* window = static_cast<nsGlobalWindowInner*>(
       OwnerDoc()->GetScriptHandlingObject(dummy));
   return window ? nsPIDOMWindowOuter::GetFromCurrentInner(window->AsInner())
                 : nullptr;
 }
 
 nsIGlobalObject* nsINode::GetOwnerGlobal() const {
--- a/dom/base/nsINode.h
+++ b/dom/base/nsINode.h
@@ -939,17 +939,17 @@ class nsINode : public mozilla::dom::Eve
   virtual mozilla::EventListenerManager* GetExistingListenerManager()
       const override;
   virtual mozilla::EventListenerManager* GetOrCreateListenerManager() override;
 
   bool ComputeDefaultWantsUntrusted(mozilla::ErrorResult& aRv) final;
 
   virtual bool IsApzAware() const override;
 
-  virtual nsPIDOMWindowOuter* GetOwnerGlobalForBindings() override;
+  virtual nsPIDOMWindowOuter* GetOwnerGlobalForBindingsInternal() override;
   virtual nsIGlobalObject* GetOwnerGlobal() const override;
 
   using mozilla::dom::EventTarget::DispatchEvent;
   bool DispatchEvent(mozilla::dom::Event& aEvent,
                      mozilla::dom::CallerType aCallerType,
                      mozilla::ErrorResult& aRv) override;
 
   nsresult PostHandleEvent(mozilla::EventChainPostVisitor& aVisitor) override;
--- a/dom/base/nsObjectLoadingContent.cpp
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -1072,18 +1072,18 @@ nsObjectLoadingContent::OnDataAvailable(
 
 // nsIFrameLoaderOwner
 NS_IMETHODIMP_(already_AddRefed<nsFrameLoader>)
 nsObjectLoadingContent::GetFrameLoader() {
   RefPtr<nsFrameLoader> loader = mFrameLoader;
   return loader.forget();
 }
 
-void nsObjectLoadingContent::PresetOpenerWindow(mozIDOMWindowProxy* aWindow,
-                                                mozilla::ErrorResult& aRv) {
+void nsObjectLoadingContent::PresetOpenerWindow(
+    const Nullable<WindowProxyHolder>& aOpenerWindow, ErrorResult& aRv) {
   aRv.Throw(NS_ERROR_FAILURE);
 }
 
 void nsObjectLoadingContent::InternalSetFrameLoader(
     nsFrameLoader* aNewFrameLoader) {
   MOZ_CRASH(
       "You shouldn't be calling this function, it doesn't make any sense on "
       "this type.");
--- a/dom/base/nsObjectLoadingContent.h
+++ b/dom/base/nsObjectLoadingContent.h
@@ -34,16 +34,19 @@ class nsPluginFrame;
 class nsPluginInstanceOwner;
 
 namespace mozilla {
 namespace dom {
 template <typename T>
 class Sequence;
 struct MozPluginParameter;
 class HTMLIFrameElement;
+template <typename T>
+struct Nullable;
+class WindowProxyHolder;
 class XULFrameElement;
 }  // namespace dom
 }  // namespace mozilla
 
 class nsObjectLoadingContent : public nsImageLoadingContent,
                                public nsIStreamListener,
                                public nsIFrameLoaderOwner,
                                public nsIObjectLoadingContent,
@@ -230,17 +233,18 @@ class nsObjectLoadingContent : public ns
                   JS::MutableHandle<JS::Value> aRetval,
                   mozilla::ErrorResult& aRv);
 
   uint32_t GetRunID(mozilla::dom::SystemCallerGuarantee,
                     mozilla::ErrorResult& aRv);
 
   bool IsRewrittenYoutubeEmbed() const { return mRewrittenYoutubeEmbed; }
 
-  void PresetOpenerWindow(mozIDOMWindowProxy* aOpenerWindow,
+  void PresetOpenerWindow(const mozilla::dom::Nullable<
+                              mozilla::dom::WindowProxyHolder>& aOpenerWindow,
                           mozilla::ErrorResult& aRv);
 
  protected:
   /**
    * Begins loading the object when called
    *
    * Attributes of |this| QI'd to nsIContent will be inspected, depending on
    * the node type. This function currently assumes it is a <object> or
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -534,17 +534,17 @@ class nsPIDOMWindowInner : public mozIDO
   // WebIDL-ish APIs
   void MarkUncollectableForCCGeneration(uint32_t aGeneration) {
     mMarkedCCGeneration = aGeneration;
   }
 
   uint32_t GetMarkedCCGeneration() { return mMarkedCCGeneration; }
 
   mozilla::dom::Navigator* Navigator();
-  virtual mozilla::dom::Location* GetLocation() = 0;
+  virtual mozilla::dom::Location* Location() = 0;
 
   virtual nsresult GetControllers(nsIControllers** aControllers) = 0;
 
   virtual nsDOMWindowList* GetFrames() = 0;
 
   virtual nsresult GetInnerWidth(int32_t* aWidth) = 0;
   virtual nsresult GetInnerHeight(int32_t* aHeight) = 0;
 
@@ -878,17 +878,20 @@ class nsPIDOMWindowOuter : public mozIDO
   // the window was frozen.
   virtual nsresult FireDelayedDOMEvents() = 0;
 
   /**
    * Get the docshell in this window.
    */
   inline nsIDocShell* GetDocShell() const;
 
-  mozilla::dom::BrowsingContext* GetBrowsingContext() const;
+  /**
+   * Get the browsing context in this window.
+   */
+  inline mozilla::dom::BrowsingContext* GetBrowsingContext() const;
 
   /**
    * Set a new document in the window. Calling this method will in most cases
    * create a new inner window. This may be called with a pointer to the current
    * document, in that case the document remains unchanged, but a new inner
    * window will be created.
    *
    * aDocument must not be null.
@@ -1098,18 +1101,20 @@ class nsPIDOMWindowOuter : public mozIDO
    * the window before the content itself is created. This is important in order
    * to set the DocGroup of a document, as the opener must be set before the
    * document is created.
    *
    * SetOpenerForInitialContentBrowser is used to set which opener will be used,
    * and TakeOpenerForInitialContentBrowser is used by nsXULElement in order to
    * take the value set earlier, and null out the value in the window.
    */
-  void SetOpenerForInitialContentBrowser(nsPIDOMWindowOuter* aOpener);
-  already_AddRefed<nsPIDOMWindowOuter> TakeOpenerForInitialContentBrowser();
+  void SetOpenerForInitialContentBrowser(
+      mozilla::dom::BrowsingContext* aOpener);
+  already_AddRefed<mozilla::dom::BrowsingContext>
+  TakeOpenerForInitialContentBrowser();
 
  protected:
   // Lazily instantiate an about:blank document if necessary, and if
   // we have what it takes to do so.
   void MaybeCreateDoc();
 
   void SetChromeEventHandlerInternal(
       mozilla::dom::EventTarget* aChromeEventHandler);
@@ -1124,18 +1129,19 @@ class nsPIDOMWindowOuter : public mozIDO
   // Cache the URI when mDoc is cleared.
   nsCOMPtr<nsIURI> mDocumentURI;  // strong
 
   nsCOMPtr<mozilla::dom::EventTarget> mParentTarget;                 // strong
   RefPtr<mozilla::dom::ContentFrameMessageManager> mMessageManager;  // strong
 
   nsCOMPtr<mozilla::dom::Element> mFrameElement;
 
-  // This reference is used by nsGlobalWindow.
+  // These references are used by nsGlobalWindow.
   nsCOMPtr<nsIDocShell> mDocShell;
+  RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext;
 
   uint32_t mModalStateDepth;
 
   // Tracks activation state that's used for :-moz-window-inactive.
   bool mIsActive;
 
   // Tracks whether our docshell is active.  If it is, mIsBackground
   // is false.  Too bad we have so many different concepts of
@@ -1175,17 +1181,17 @@ class nsPIDOMWindowOuter : public mozIDO
   uint32_t mMarkedCCGeneration;
 
   // Let the service workers plumbing know that some feature are enabled while
   // testing.
   bool mServiceWorkersTestingEnabled;
 
   mozilla::dom::LargeAllocStatus mLargeAllocStatus;
 
-  nsCOMPtr<nsPIDOMWindowOuter> mOpenerForInitialContentBrowser;
+  RefPtr<mozilla::dom::BrowsingContext> mOpenerForInitialContentBrowser;
 
   using PermissionInfo = std::pair<bool, mozilla::TimeStamp>;
   nsDataHashtable<nsStringHashKey, PermissionInfo> mAutoplayTemporaryPermission;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsPIDOMWindowOuter, NS_PIDOMWINDOWOUTER_IID)
 
 #include "nsPIDOMWindowInlines.h"
--- a/dom/base/nsPIDOMWindowInlines.h
+++ b/dom/base/nsPIDOMWindowInlines.h
@@ -63,15 +63,19 @@ bool nsPIDOMWindowInner::IsTopInnerWindo
 }
 
 nsIDocShell* nsPIDOMWindowOuter::GetDocShell() const { return mDocShell; }
 
 nsIDocShell* nsPIDOMWindowInner::GetDocShell() const {
   return mOuterWindow ? mOuterWindow->GetDocShell() : nullptr;
 }
 
+mozilla::dom::BrowsingContext* nsPIDOMWindowOuter::GetBrowsingContext() const {
+  return mBrowsingContext;
+}
+
 mozilla::dom::Element* nsPIDOMWindowOuter::GetFocusedElement() const {
   return mInnerWindow ? mInnerWindow->GetFocusedElement() : nullptr;
 }
 
 mozilla::dom::Element* nsPIDOMWindowInner::GetFocusedElement() const {
   return mFocusedElement;
 }
--- a/dom/base/nsWindowRoot.cpp
+++ b/dom/base/nsWindowRoot.cpp
@@ -101,18 +101,18 @@ void nsWindowRoot::GetEventTargetParent(
   aVisitor.mItemData = static_cast<nsISupports*>(mWindow);
   aVisitor.SetParentTarget(mParent, false);
 }
 
 nsresult nsWindowRoot::PostHandleEvent(EventChainPostVisitor& aVisitor) {
   return NS_OK;
 }
 
-nsPIDOMWindowOuter* nsWindowRoot::GetOwnerGlobalForBindings() {
-  return GetWindow();
+nsPIDOMWindowOuter* nsWindowRoot::GetOwnerGlobalForBindingsInternal() {
+  return mWindow;
 }
 
 nsIGlobalObject* nsWindowRoot::GetOwnerGlobal() const {
   nsCOMPtr<nsIGlobalObject> global =
       do_QueryInterface(mWindow->GetCurrentInnerWindow());
   // We're still holding a ref to it, so returning the raw pointer is ok...
   return global;
 }
--- a/dom/base/nsWindowRoot.h
+++ b/dom/base/nsWindowRoot.h
@@ -55,17 +55,17 @@ class nsWindowRoot final : public nsPIWi
   virtual void SetPopupNode(nsINode* aNode) override;
 
   virtual void SetParentTarget(mozilla::dom::EventTarget* aTarget) override {
     mParent = aTarget;
   }
   virtual mozilla::dom::EventTarget* GetParentTarget() override {
     return mParent;
   }
-  virtual nsPIDOMWindowOuter* GetOwnerGlobalForBindings() override;
+  virtual nsPIDOMWindowOuter* GetOwnerGlobalForBindingsInternal() override;
   virtual nsIGlobalObject* GetOwnerGlobal() const override;
 
   nsIGlobalObject* GetParentObject();
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsWindowRoot)
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -1121,16 +1121,21 @@ bool VariantToJsval(JSContext* aCx, nsIV
       Throw(aCx, NS_FAILED(rv) ? rv : NS_ERROR_UNEXPECTED);
     }
     return false;
   }
 
   return true;
 }
 
+bool WrapObject(JSContext* cx, const WindowProxyHolder& p,
+                JS::MutableHandle<JS::Value> rval) {
+  return ToJSValue(cx, p, rval);
+}
+
 static int CompareIdsAtIndices(const void* aElement1, const void* aElement2,
                                void* aClosure) {
   const uint16_t index1 = *static_cast<const uint16_t*>(aElement1);
   const uint16_t index2 = *static_cast<const uint16_t*>(aElement2);
   const PropertyInfo* infos = static_cast<PropertyInfo*>(aClosure);
 
   MOZ_ASSERT(JSID_BITS(infos[index1].Id()) != JSID_BITS(infos[index2].Id()));
 
@@ -2793,16 +2798,28 @@ namespace binding_detail {
  * ExtractThisObject: Takes a CallArgs for which HasValidThisValue was true and
  *                    returns the JSObject* to use for getting |this|.
  *
  * MaybeUnwrapThisObject: If our |this| is a JSObject* that this policy wants to
  *                        allow unchecked access to for this
  *                        getter/setter/method, unwrap it.  Otherwise just
  *                        return the given object.
  *
+ * UnwrapThisObject: Takes a MutableHandle for a JSObject which contains the
+ *                   this object (which the caller probably got from
+ *                   MaybeUnwrapThisObject). It will try to get the right native
+ *                   out of aObj. In some cases there are 2 possible types for
+ *                   the native (which is why aSelf is a reference to a void*).
+ *                   The ThisPolicy user should use the this JSObject* to
+ *                   determine what C++ class aSelf contains. aObj is used to
+ *                   keep the reflector object alive while self is being used,
+ *                   so its value before and after the UnwrapThisObject call
+ *                   could be different (if aObj was wrapped). The return value
+ *                   is an nsresult, which will signal if an error occurred.
+ *
  * HandleInvalidThis: If the |this| is not valid (wrong type of value, wrong
  *                    object, etc), decide what to do about it.  Returns a
  *                    boolean to return from the JSNative (false for failure,
  *                    true for succcess).
  */
 struct NormalThisPolicy {
   // This needs to be inlined because it's called on no-exceptions fast-paths.
   static MOZ_ALWAYS_INLINE bool HasValidThisValue(const JS::CallArgs& aArgs) {
@@ -2820,16 +2837,24 @@ struct NormalThisPolicy {
       const JS::CallArgs& aArgs) {
     return &aArgs.thisv().toObject();
   }
 
   static MOZ_ALWAYS_INLINE JSObject* MaybeUnwrapThisObject(JSObject* aObj) {
     return aObj;
   }
 
+  static MOZ_ALWAYS_INLINE nsresult
+  UnwrapThisObject(JS::MutableHandle<JSObject*> aObj, void*& aSelf,
+                   prototypes::ID aProtoID, uint32_t aProtoDepth) {
+    binding_detail::MutableObjectHandleWrapper wrapper(aObj);
+    return binding_detail::UnwrapObjectInternal<void, true>(
+        wrapper, aSelf, aProtoID, aProtoDepth);
+  }
+
   static bool HandleInvalidThis(JSContext* aCx, JS::CallArgs& aArgs,
                                 bool aSecurityError, prototypes::ID aProtoId) {
     return ThrowInvalidThis(aCx, aArgs, aSecurityError, aProtoId);
   }
 };
 
 struct MaybeGlobalThisPolicy : public NormalThisPolicy {
   static MOZ_ALWAYS_INLINE bool HasValidThisValue(const JS::CallArgs& aArgs) {
@@ -2876,21 +2901,60 @@ struct CrossOriginThisPolicy : public Ma
 
   // We want the ExtractThisObject of MaybeGlobalThisPolicy.
 
   static MOZ_ALWAYS_INLINE JSObject* MaybeUnwrapThisObject(JSObject* aObj) {
     if (xpc::WrapperFactory::IsXrayWrapper(aObj)) {
       return js::UncheckedUnwrap(aObj);
     }
 
-    // Else just return aObj; our UnwrapObjectInternal call will try to
-    // CheckedUnwrap it, and eitehr succeed or get a security error as needed.
+    // Else just return aObj; our UnwrapThisObject call will try to
+    // CheckedUnwrap it, and either succeed or get a security error as needed.
     return aObj;
   }
 
+  // After calling UnwrapThisObject aSelf can contain one of 2 types, depending
+  // on whether aObj is a proxy with a RemoteObjectProxy handler or a (maybe
+  // wrapped) normal WebIDL reflector. The generated binding code relies on this
+  // and uses IsRemoteObjectProxy to determine what type aSelf points to.
+  static MOZ_ALWAYS_INLINE nsresult
+  UnwrapThisObject(JS::MutableHandle<JSObject*> aObj, void*& aSelf,
+                   prototypes::ID aProtoID, uint32_t aProtoDepth) {
+    binding_detail::MutableObjectHandleWrapper wrapper(aObj);
+    // We need to pass false here, because if aObj doesn't have a DOMJSClass
+    // it might be a remote proxy object, and we don't want to throw in that
+    // case (even though unwrapping would fail).
+    nsresult rv = binding_detail::UnwrapObjectInternal<void, false>(
+        wrapper, aSelf, aProtoID, aProtoDepth);
+    if (NS_SUCCEEDED(rv)) {
+      return rv;
+    }
+
+    if (js::IsWrapper(wrapper)) {
+      JSObject* unwrappedObj =
+          js::CheckedUnwrap(wrapper, /* stopAtWindowProxy = */ false);
+      if (!unwrappedObj) {
+        return NS_ERROR_XPC_SECURITY_MANAGER_VETO;
+      }
+
+      // At this point we want to keep "unwrappedObj" alive, because we don't
+      // hold a strong reference in "aSelf".
+      wrapper = unwrappedObj;
+
+      return binding_detail::UnwrapObjectInternal<void, false>(
+          wrapper, aSelf, aProtoID, aProtoDepth);
+    }
+
+    if (!IsRemoteObjectProxy(wrapper, aProtoID)) {
+      return NS_ERROR_XPC_BAD_CONVERT_JS;
+    }
+    aSelf = RemoteObjectProxyBase::GetNative(wrapper);
+    return NS_OK;
+  }
+
   // We want the HandleInvalidThis of MaybeGlobalThisPolicy.
 };
 
 /**
  * An ExceptionPolicy struct provides a single HandleException method which is
  * used to handle an exception, if any.  The method is given the current
  * success/failure boolean so it can decide whether there is in fact an
  * exception involved.
@@ -2936,19 +3000,18 @@ bool GenericGetter(JSContext* cx, unsign
   JS::Rooted<JSObject*> obj(cx, ThisPolicy::ExtractThisObject(args));
 
   // NOTE: we want to leave obj in its initial compartment, so don't want to
   // pass it to UnwrapObjectInternal.  Also, the thing we pass to
   // UnwrapObjectInternal may be affected by our ThisPolicy.
   JS::Rooted<JSObject*> rootSelf(cx, ThisPolicy::MaybeUnwrapThisObject(obj));
   void* self;
   {
-    binding_detail::MutableObjectHandleWrapper wrapper(&rootSelf);
-    nsresult rv = binding_detail::UnwrapObjectInternal<void, true>(
-        wrapper, self, protoID, info->depth);
+    nsresult rv =
+        ThisPolicy::UnwrapThisObject(&rootSelf, self, protoID, info->depth);
     if (NS_FAILED(rv)) {
       bool ok = ThisPolicy::HandleInvalidThis(
           cx, args, rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO, protoID);
       return ExceptionPolicy::HandleException(cx, args, info, ok);
     }
   }
 
   MOZ_ASSERT(info->type() == JSJitInfo::Getter);
@@ -2993,19 +3056,18 @@ bool GenericSetter(JSContext* cx, unsign
   JS::Rooted<JSObject*> obj(cx, ThisPolicy::ExtractThisObject(args));
 
   // NOTE: we want to leave obj in its initial compartment, so don't want to
   // pass it to UnwrapObject.  Also the thing we pass to UnwrapObjectInternal
   // may be affected by our ThisPolicy.
   JS::Rooted<JSObject*> rootSelf(cx, ThisPolicy::MaybeUnwrapThisObject(obj));
   void* self;
   {
-    binding_detail::MutableObjectHandleWrapper wrapper(&rootSelf);
-    nsresult rv = binding_detail::UnwrapObjectInternal<void, true>(
-        wrapper, self, protoID, info->depth);
+    nsresult rv =
+        ThisPolicy::UnwrapThisObject(&rootSelf, self, protoID, info->depth);
     if (NS_FAILED(rv)) {
       return ThisPolicy::HandleInvalidThis(
           cx, args, rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO, protoID);
     }
   }
   if (args.length() == 0) {
     return ThrowNoSetterArg(cx, args, protoID);
   }
@@ -3043,19 +3105,18 @@ bool GenericMethod(JSContext* cx, unsign
   JS::Rooted<JSObject*> obj(cx, ThisPolicy::ExtractThisObject(args));
 
   // NOTE: we want to leave obj in its initial compartment, so don't want to
   // pass it to UnwrapObjectInternal.  Also, the thing we pass to
   // UnwrapObjectInternal may be affected by our ThisPolicy.
   JS::Rooted<JSObject*> rootSelf(cx, ThisPolicy::MaybeUnwrapThisObject(obj));
   void* self;
   {
-    binding_detail::MutableObjectHandleWrapper wrapper(&rootSelf);
-    nsresult rv = binding_detail::UnwrapObjectInternal<void, true>(
-        wrapper, self, protoID, info->depth);
+    nsresult rv =
+        ThisPolicy::UnwrapThisObject(&rootSelf, self, protoID, info->depth);
     if (NS_FAILED(rv)) {
       bool ok = ThisPolicy::HandleInvalidThis(
           cx, args, rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO, protoID);
       return ExceptionPolicy::HandleException(cx, args, info, ok);
     }
   }
   MOZ_ASSERT(info->type() == JSJitInfo::Method);
   JSJitMethodOp method = info->method;
@@ -3218,24 +3279,31 @@ nsresult UnwrapArgImpl(JSContext* cx, JS
 
   // We need to go through the QueryInterface logic to make this return
   // the right thing for the various 'special' interfaces; e.g.
   // nsIPropertyBag. We must use AggregatedQueryInterface in cases where
   // there is an outer to avoid nasty recursion.
   return wrappedJS->QueryInterface(iid, ppArg);
 }
 
-nsresult UnwrapWindowProxyImpl(JSContext* cx, JS::Handle<JSObject*> src,
-                               nsPIDOMWindowOuter** ppArg) {
+nsresult UnwrapWindowProxyArg(JSContext* cx, JS::Handle<JSObject*> src,
+                              WindowProxyHolder& ppArg) {
+  if (IsRemoteObjectProxy(src, prototypes::id::Window)) {
+    ppArg =
+        static_cast<BrowsingContext*>(RemoteObjectProxyBase::GetNative(src));
+    return NS_OK;
+  }
+
   nsCOMPtr<nsPIDOMWindowInner> inner;
   nsresult rv = UnwrapArg<nsPIDOMWindowInner>(cx, src, getter_AddRefs(inner));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsPIDOMWindowOuter> outer = inner->GetOuterWindow();
-  outer.forget(ppArg);
+  RefPtr<BrowsingContext> bc = outer ? outer->GetBrowsingContext() : nullptr;
+  ppArg = bc.forget();
   return NS_OK;
 }
 
 template <decltype(JS::NewMapObject) Method>
 bool GetMaplikeSetlikeBackingObject(JSContext* aCx, JS::Handle<JSObject*> aObj,
                                     size_t aSlotIndex,
                                     JS::MutableHandle<JSObject*> aBackingObj,
                                     bool* aBackingObjCreated) {
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -49,38 +49,32 @@ namespace mozilla {
 
 enum UseCounter : int16_t;
 
 namespace dom {
 class CustomElementReactionsStack;
 class MessageManagerGlobal;
 template <typename KeyType, typename ValueType>
 class Record;
+class WindowProxyHolder;
 
 nsresult UnwrapArgImpl(JSContext* cx, JS::Handle<JSObject*> src,
                        const nsIID& iid, void** ppArg);
 
-nsresult UnwrapWindowProxyImpl(JSContext* cx, JS::Handle<JSObject*> src,
-                               nsPIDOMWindowOuter** ppArg);
-
 /** Convert a jsval to an XPCOM pointer. Caller must not assume that src will
     keep the XPCOM pointer rooted. */
 template <class Interface>
 inline nsresult UnwrapArg(JSContext* cx, JS::Handle<JSObject*> src,
                           Interface** ppArg) {
   return UnwrapArgImpl(cx, src, NS_GET_TEMPLATE_IID(Interface),
                        reinterpret_cast<void**>(ppArg));
 }
 
-template <>
-inline nsresult UnwrapArg<nsPIDOMWindowOuter>(JSContext* cx,
-                                              JS::Handle<JSObject*> src,
-                                              nsPIDOMWindowOuter** ppArg) {
-  return UnwrapWindowProxyImpl(cx, src, ppArg);
-}
+nsresult UnwrapWindowProxyArg(JSContext* cx, JS::Handle<JSObject*> src,
+                              WindowProxyHolder& ppArg);
 
 bool ThrowInvalidThis(JSContext* aCx, const JS::CallArgs& aArgs,
                       bool aSecurityError, const char* aInterfaceName);
 
 bool ThrowInvalidThis(JSContext* aCx, const JS::CallArgs& aArgs,
                       bool aSecurityError, prototypes::ID aProtoId);
 
 // Returns true if the JSClass is used for DOM objects.
@@ -1419,16 +1413,19 @@ inline bool WrapObject<JSObject>(JSConte
 }
 
 inline bool WrapObject(JSContext* cx, JSObject& p,
                        JS::MutableHandle<JS::Value> rval) {
   rval.set(JS::ObjectValue(p));
   return true;
 }
 
+bool WrapObject(JSContext* cx, const WindowProxyHolder& p,
+                JS::MutableHandle<JS::Value> rval);
+
 // Given an object "p" that inherits from nsISupports, wrap it and return the
 // result.  Null is returned on wrapping failure.  This is somewhat similar to
 // WrapObject() above, but does NOT allow Xrays around the result, since we
 // don't want those for our parent object.
 template <typename T>
 static inline JSObject* WrapNativeISupports(JSContext* cx, T* p,
                                             nsWrapperCache* cache) {
   xpcObjectHelper helper(ToSupports(p), cache);
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -1482,18 +1482,17 @@ DOMInterfaces = {
     },
     'implicitJSContext': [
         'createImageBitmap',
         'requestIdleCallback'
     ],
 },
 
 'WindowProxy': {
-    'nativeType': 'nsPIDOMWindowOuter',
-    'headerFile': 'nsPIDOMWindow.h',
+    'headerFile': 'mozilla/dom/WindowProxyHolder.h',
     'concrete': False
 },
 
 'WindowRoot': {
     'nativeType': 'nsWindowRoot'
 },
 
 'WorkerDebuggerGlobalScope': {
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -1383,16 +1383,21 @@ def UnionTypes(unionTypes, config):
                             # Callback interfaces always use strong refs, so
                             # we need to include the right header to be able
                             # to Release() in our inlined code.
                             #
                             # Similarly, sequences always contain strong
                             # refs, so we'll need the header to handler
                             # those.
                             headers.add(typeDesc.headerFile)
+                        elif typeDesc.interface.identifier.name == "WindowProxy":
+                            # In UnionTypes.h we need to see the declaration of the
+                            # WindowProxyHolder that we use to store the WindowProxy, so
+                            # we have its sizeof and know how big to make our union.
+                            headers.add(typeDesc.headerFile)
                         else:
                             declarations.add((typeDesc.nativeType, False))
                             implheaders.add(typeDesc.headerFile)
                 elif f.isDictionary():
                     # For a dictionary, we need to see its declaration in
                     # UnionTypes.h so we have its sizeof and know how big to
                     # make our union.
                     headers.add(CGHeaders.getDeclarationFilename(f.inner))
@@ -2078,16 +2083,85 @@ class PropertyDefiner:
         return MemberCondition(
             PropertyDefiner.getStringAttr(interfaceMember,
                                           "Pref"),
             PropertyDefiner.getStringAttr(interfaceMember,
                                           "Func"),
             interfaceMember.getExtendedAttribute("SecureContext") is not None,
             nonExposureSet)
 
+    @staticmethod
+    def generatePrefableArrayValues(array, descriptor, specFormatter, specTerminator,
+                                    getCondition, getDataTuple,
+                                    switchToCondition=None):
+        """
+        This method generates an array of spec entries for interface members. It returns
+          a tuple containing the array of spec entries and the maximum of the number of
+          spec entries per condition.
+
+        array is an array of interface members.
+
+        descriptor is the descriptor for the interface that array contains members of.
+
+        specFormatter is a function that takes a single argument, a tuple,
+          and returns a string, a spec array entry.
+
+        specTerminator is a terminator for the spec array (inserted every time
+          our controlling pref changes and at the end of the array).
+
+        getCondition is a callback function that takes an array entry and
+          returns the corresponding MemberCondition.
+
+        getDataTuple is a callback function that takes an array entry and
+          returns a tuple suitable to be passed to specFormatter.
+
+        switchToCondition is a function that takes a MemberCondition and an array of
+          previously generated spec entries. If None is passed for this function then all
+          the interface members should return the same value from getCondition.
+        """
+        def unsupportedSwitchToCondition(condition, specs):
+            # If no specs have been added yet then this is just the first call to
+            # switchToCondition that we call to avoid putting a specTerminator at the
+            # front of the list.
+            if len(specs) == 0:
+                return
+            raise "Not supported"
+
+        if switchToCondition is None:
+            switchToCondition = unsupportedSwitchToCondition
+
+        specs = []
+        numSpecsInCurPrefable = 0
+        maxNumSpecsInPrefable = 0
+
+        # So we won't put a specTerminator at the very front of the list:
+        lastCondition = getCondition(array[0], descriptor)
+
+        switchToCondition(lastCondition, specs)
+
+        for member in array:
+            curCondition = getCondition(member, descriptor)
+            if lastCondition != curCondition:
+                # Terminate previous list
+                specs.append(specTerminator)
+                if numSpecsInCurPrefable > maxNumSpecsInPrefable:
+                    maxNumSpecsInPrefable = numSpecsInCurPrefable
+                numSpecsInCurPrefable = 0
+                # And switch to our new condition
+                switchToCondition(curCondition, specs)
+                lastCondition = curCondition
+            # And the actual spec
+            specs.append(specFormatter(getDataTuple(member, descriptor)))
+            numSpecsInCurPrefable += 1
+        if numSpecsInCurPrefable > maxNumSpecsInPrefable:
+            maxNumSpecsInPrefable = numSpecsInCurPrefable
+        specs.append(specTerminator)
+
+        return (specs, maxNumSpecsInPrefable)
+
     def generatePrefableArray(self, array, name, specFormatter, specTerminator,
                               specType, getCondition, getDataTuple):
         """
         This method generates our various arrays.
 
         array is an array of interface members as passed to generateArray
 
         name is the name as passed to generateArray
@@ -2109,76 +2183,54 @@ class PropertyDefiner:
 
         # We want to generate a single list of specs, but with specTerminator
         # inserted at every point where the pref name controlling the member
         # changes.  That will make sure the order of the properties as exposed
         # on the interface and interface prototype objects does not change when
         # pref control is added to members while still allowing us to define all
         # the members in the smallest number of JSAPI calls.
         assert len(array) != 0
-        # So we won't put a specTerminator at the very front of the list:
-        lastCondition = getCondition(array[0], self.descriptor)
-
-        specs = []
+
         disablers = []
         prefableSpecs = []
 
         disablersTemplate = dedent(
             """
             // Can't be const because the pref-enabled boolean needs to be writable
             static PrefableDisablers %s_disablers%d = {
               true, %s, %s, %s
             };
             """)
         prefableWithDisablersTemplate = '  { &%s_disablers%d, &%s_specs[%d] }'
         prefableWithoutDisablersTemplate = '  { nullptr, &%s_specs[%d] }'
         prefCacheTemplate = '&%s[%d].disablers->enabled'
 
-        def switchToCondition(props, condition):
+        def switchToCondition(condition, specs):
             # Remember the info about where our pref-controlled
             # booleans live.
             if condition.pref is not None:
-                props.prefCacheData.append(
+                self.prefCacheData.append(
                     (condition.pref,
                      prefCacheTemplate % (name, len(prefableSpecs))))
             # Set up pointers to the new sets of specs inside prefableSpecs
             if condition.hasDisablers():
                 prefableSpecs.append(prefableWithDisablersTemplate %
                                      (name, len(specs), name, len(specs)))
                 disablers.append(disablersTemplate %
                                  (name, len(specs),
                                   toStringBool(condition.secureContext),
                                   condition.nonExposedGlobals,
                                   condition.func))
             else:
                 prefableSpecs.append(prefableWithoutDisablersTemplate %
                                      (name, len(specs)))
 
-        switchToCondition(self, lastCondition)
-
-        numSpecsInCurPrefable = 0
-        maxNumSpecsInPrefable = 0
-
-        for member in array:
-            curCondition = getCondition(member, self.descriptor)
-            if lastCondition != curCondition:
-                # Terminate previous list
-                specs.append(specTerminator)
-                if numSpecsInCurPrefable > maxNumSpecsInPrefable:
-                    maxNumSpecsInPrefable = numSpecsInCurPrefable
-                numSpecsInCurPrefable = 0
-                # And switch to our new condition
-                switchToCondition(self, curCondition)
-                lastCondition = curCondition
-            # And the actual spec
-            specs.append(specFormatter(getDataTuple(member)))
-            numSpecsInCurPrefable += 1
-        specs.append(specTerminator)
-        if numSpecsInCurPrefable > maxNumSpecsInPrefable:
-            maxNumSpecsInPrefable = numSpecsInCurPrefable
+        specs, maxNumSpecsInPrefable = self.generatePrefableArrayValues(
+            array, self.descriptor, specFormatter, specTerminator, getCondition,
+            getDataTuple, switchToCondition)
         prefableSpecs.append("  { nullptr, nullptr }")
 
         specType = "const " + specType
         arrays = fill(
             """
             // We deliberately use brace-elision to make Visual Studio produce better initalization code.
             #if defined(__clang__)
             #pragma clang diagnostic push
@@ -2256,29 +2308,30 @@ def EnumerabilityFlags(member):
         return "0"
     return "JSPROP_ENUMERATE"
 
 
 class MethodDefiner(PropertyDefiner):
     """
     A class for defining methods on a prototype object.
     """
-    def __init__(self, descriptor, name, static, unforgeable=False):
+    def __init__(self, descriptor, name, crossOriginOnly, static, unforgeable=False):
         assert not (static and unforgeable)
         PropertyDefiner.__init__(self, descriptor, name)
 
         # FIXME https://bugzilla.mozilla.org/show_bug.cgi?id=772822
         #       We should be able to check for special operations without an
         #       identifier. For now we check if the name starts with __
 
         # Ignore non-static methods for interfaces without a proto object
         if descriptor.interface.hasInterfacePrototypeObject() or static:
             methods = [m for m in descriptor.interface.members if
                        m.isMethod() and m.isStatic() == static and
                        MemberIsUnforgeable(m, descriptor) == unforgeable and
+                       (not crossOriginOnly or m.getExtendedAttribute("CrossOriginCallable")) and
                        not m.isIdentifierLess()]
         else:
             methods = []
         self.chrome = []
         self.regular = []
         for m in methods:
             if m.identifier.name == 'QueryInterface':
                 # QueryInterface is special, because instead of generating an
@@ -2305,26 +2358,17 @@ class MethodDefiner(PropertyDefiner):
                     "name": 'QueryInterface',
                     "methodInfo": False,
                     "length": 1,
                     "flags": "0",
                     "condition": PropertyDefiner.getControllingCondition(m, descriptor)
                 })
                 continue
 
-            method = {
-                "name": m.identifier.name,
-                "methodInfo": not m.isStatic(),
-                "length": methodLength(m),
-                "flags": EnumerabilityFlags(m),
-                "condition": PropertyDefiner.getControllingCondition(m, descriptor),
-                "allowCrossOriginThis": m.getExtendedAttribute("CrossOriginCallable"),
-                "returnsPromise": m.returnsPromise(),
-                "hasIteratorAlias": "@@iterator" in m.aliases
-            }
+            method = self.methodData(m, descriptor)
 
             if m.isStatic():
                 method["nativeName"] = CppKeywords.checkMethodName(IDLToCIdentifier(m.identifier.name))
 
             if isChromeOnly(m):
                 self.chrome.append(method)
             else:
                 self.regular.append(method)
@@ -2449,80 +2493,96 @@ class MethodDefiner(PropertyDefiner):
             if not descriptor.interface.hasInterfaceObject():
                 # static methods go on the interface object
                 assert not self.hasChromeOnly() and not self.hasNonChromeOnly()
         else:
             if not descriptor.interface.hasInterfacePrototypeObject():
                 # non-static methods go on the interface prototype object
                 assert not self.hasChromeOnly() and not self.hasNonChromeOnly()
 
+    @staticmethod
+    def methodData(m, descriptor, overrideFlags=None):
+        return {
+                "name": m.identifier.name,
+                "methodInfo": not m.isStatic(),
+                "length": methodLength(m),
+                "flags": EnumerabilityFlags(m) if (overrideFlags is None) else overrideFlags,
+                "condition": PropertyDefiner.getControllingCondition(m, descriptor),
+                "allowCrossOriginThis": m.getExtendedAttribute("CrossOriginCallable"),
+                "returnsPromise": m.returnsPromise(),
+                "hasIteratorAlias": "@@iterator" in m.aliases
+            }
+
+    @staticmethod
+    def formatSpec(fields):
+        if fields[0].startswith("@@"):
+            fields = (fields[0][2:],) + fields[1:]
+            return '  JS_SYM_FNSPEC(%s, %s, %s, %s, %s, %s)' % fields
+        return '  JS_FNSPEC("%s", %s, %s, %s, %s, %s)' % fields
+
+    @staticmethod
+    def specData(m, descriptor, unforgeable=False):
+        def flags(m, unforgeable):
+            unforgeable = " | JSPROP_PERMANENT | JSPROP_READONLY" if unforgeable else ""
+            return m["flags"] + unforgeable
+
+        if "selfHostedName" in m:
+            selfHostedName = '"%s"' % m["selfHostedName"]
+            assert not m.get("methodInfo", True)
+            accessor = "nullptr"
+            jitinfo = "nullptr"
+        else:
+            selfHostedName = "nullptr"
+            # When defining symbols, function name may not match symbol name
+            methodName = m.get("methodName", m["name"])
+            accessor = m.get("nativeName", IDLToCIdentifier(methodName))
+            if m.get("methodInfo", True):
+                if m.get("returnsPromise", False):
+                    exceptionPolicy = "ConvertExceptionsToPromises"
+                else:
+                    exceptionPolicy = "ThrowExceptions"
+
+                # Cast this in case the methodInfo is a
+                # JSTypedMethodJitInfo.
+                jitinfo = ("reinterpret_cast<const JSJitInfo*>(&%s_methodinfo)" % accessor)
+                if m.get("allowCrossOriginThis", False):
+                    accessor = ("(GenericMethod<CrossOriginThisPolicy, %s>)" %
+                                exceptionPolicy)
+                elif descriptor.interface.isOnGlobalProtoChain():
+                    accessor = ("(GenericMethod<MaybeGlobalThisPolicy, %s>)" %
+                                exceptionPolicy)
+                else:
+                    accessor = ("(GenericMethod<NormalThisPolicy, %s>)" %
+                                exceptionPolicy)
+            else:
+                if m.get("returnsPromise", False):
+                    jitinfo = "&%s_methodinfo" % accessor
+                    accessor = "StaticMethodPromiseWrapper"
+                else:
+                    jitinfo = "nullptr"
+
+        return (m["name"], accessor, jitinfo, m["length"], flags(m, unforgeable), selfHostedName)
+
+    @staticmethod
+    def condition(m, d):
+        return m["condition"]
+
     def generateArray(self, array, name):
         if len(array) == 0:
             return ""
 
-        def condition(m, d):
-            return m["condition"]
-
-        def flags(m):
-            unforgeable = " | JSPROP_PERMANENT | JSPROP_READONLY" if self.unforgeable else ""
-            return m["flags"] + unforgeable
-
-        def specData(m):
-            if "selfHostedName" in m:
-                selfHostedName = '"%s"' % m["selfHostedName"]
-                assert not m.get("methodInfo", True)
-                accessor = "nullptr"
-                jitinfo = "nullptr"
-            else:
-                selfHostedName = "nullptr"
-                # When defining symbols, function name may not match symbol name
-                methodName = m.get("methodName", m["name"])
-                accessor = m.get("nativeName", IDLToCIdentifier(methodName))
-                if m.get("methodInfo", True):
-                    if m.get("returnsPromise", False):
-                        exceptionPolicy = "ConvertExceptionsToPromises"
-                    else:
-                        exceptionPolicy = "ThrowExceptions"
-
-                    # Cast this in case the methodInfo is a
-                    # JSTypedMethodJitInfo.
-                    jitinfo = ("reinterpret_cast<const JSJitInfo*>(&%s_methodinfo)" % accessor)
-                    if m.get("allowCrossOriginThis", False):
-                        accessor = ("(GenericMethod<CrossOriginThisPolicy, %s>)" %
-                                    exceptionPolicy)
-                    elif self.descriptor.interface.isOnGlobalProtoChain():
-                        accessor = ("(GenericMethod<MaybeGlobalThisPolicy, %s>)" %
-                                    exceptionPolicy)
-                    else:
-                        accessor = ("(GenericMethod<NormalThisPolicy, %s>)" %
-                                    exceptionPolicy)
-                else:
-                    if m.get("returnsPromise", False):
-                        jitinfo = "&%s_methodinfo" % accessor
-                        accessor = "StaticMethodPromiseWrapper"
-                    else:
-                        jitinfo = "nullptr"
-
-            return (m["name"], accessor, jitinfo, m["length"], flags(m), selfHostedName)
-
-        def formatSpec(fields):
-            if fields[0].startswith("@@"):
-                fields = (fields[0][2:],) + fields[1:]
-                return '  JS_SYM_FNSPEC(%s, %s, %s, %s, %s, %s)' % fields
-            return '  JS_FNSPEC("%s", %s, %s, %s, %s, %s)' % fields
-
         return self.generatePrefableArray(
             array, name,
-            formatSpec,
+            self.formatSpec,
             '  JS_FS_END',
             'JSFunctionSpec',
-            condition, specData)
-
-
-def IsCrossOriginWritable(attr, descriptor):
+            self.condition, functools.partial(self.specData, unforgeable=self.unforgeable))
+
+
+def isCrossOriginWritable(attr, descriptor):
     """
     Return whether the IDLAttribute in question is cross-origin writable on the
     interface represented by descriptor.  This is needed to handle the fact that
     some, but not all, interfaces implementing URLUtils want a cross-origin
     writable .href.
     """
     crossOriginWritable = attr.getExtendedAttribute("CrossOriginWritable")
     if not crossOriginWritable:
@@ -2533,62 +2593,66 @@ def IsCrossOriginWritable(attr, descript
             len(crossOriginWritable) == 1)
     return crossOriginWritable[0] == descriptor.interface.identifier.name
 
 def isNonExposedNavigatorObjectGetter(attr, descriptor):
     return (attr.navigatorObjectGetter and
             not descriptor.getDescriptor(attr.type.inner.identifier.name).register)
 
 class AttrDefiner(PropertyDefiner):
-    def __init__(self, descriptor, name, static, unforgeable=False):
+    def __init__(self, descriptor, name, crossOriginOnly, static, unforgeable=False):
         assert not (static and unforgeable)
         PropertyDefiner.__init__(self, descriptor, name)
         self.name = name
         # Ignore non-static attributes for interfaces without a proto object
         if descriptor.interface.hasInterfacePrototypeObject() or static:
-            attributes = [m for m in descriptor.interface.members if
-                          m.isAttr() and m.isStatic() == static and
-                          MemberIsUnforgeable(m, descriptor) == unforgeable and
-                          not isNonExposedNavigatorObjectGetter(m, descriptor)]
-        else:
-            attributes = []
-
-        attributes = [
-            {"name": name, "attr": attr}
-            for attr in attributes
-            for name in [attr.identifier.name] + attr.bindingAliases
-        ]
-
+            idlAttrs = [m for m in descriptor.interface.members if
+                        m.isAttr() and m.isStatic() == static and
+                        MemberIsUnforgeable(m, descriptor) == unforgeable and
+                        (not crossOriginOnly or m.getExtendedAttribute("CrossOriginReadable") or
+                         isCrossOriginWritable(m, descriptor)) and
+                        not isNonExposedNavigatorObjectGetter(m, descriptor)]
+        else:
+            idlAttrs = []
+
+        attributes = []
+        for attr in idlAttrs:
+            attributes.extend(self.attrData(attr, unforgeable))
         self.chrome = [m for m in attributes if isChromeOnly(m["attr"])]
         self.regular = [m for m in attributes if not isChromeOnly(m["attr"])]
         self.static = static
-        self.unforgeable = unforgeable
 
         if static:
             if not descriptor.interface.hasInterfaceObject():
                 # static attributes go on the interface object
                 assert not self.hasChromeOnly() and not self.hasNonChromeOnly()
         else:
             if not descriptor.interface.hasInterfacePrototypeObject():
                 # non-static attributes go on the interface prototype object
                 assert not self.hasChromeOnly() and not self.hasNonChromeOnly()
 
-    def generateArray(self, array, name):
-        if len(array) == 0:
-            return ""
-
-        def condition(m, d):
-            return PropertyDefiner.getControllingCondition(m["attr"], d)
-
-        def flags(attr):
-            unforgeable = " | JSPROP_PERMANENT" if self.unforgeable else ""
-            return EnumerabilityFlags(attr) + unforgeable
-
+    @staticmethod
+    def attrData(attr, unforgeable=False, overrideFlags=None):
+        if overrideFlags is None:
+            permanent = " | JSPROP_PERMANENT" if unforgeable else ""
+            flags = EnumerabilityFlags(attr) + permanent
+        else:
+            flags = overrideFlags
+        return ({"name": name, "attr": attr, "flags": flags} for name in [attr.identifier.name] + attr.bindingAliases)
+
+    @staticmethod
+    def condition(m, d):
+        return PropertyDefiner.getControllingCondition(m["attr"], d)
+
+    @staticmethod
+    def specData(entry, descriptor, static=False, crossOriginOnly=False):
         def getter(attr):
-            if self.static:
+            if crossOriginOnly and not attr.getExtendedAttribute("CrossOriginReadable"):
+                return "nullptr, nullptr"
+            if static:
                 if attr.type.isPromise():
                     raise TypeError("Don't know how to handle "
                                     "static Promise-returning "
                                     "attribute %s.%s" %
                                     (self.descriptor.name,
                                      attr.identifier.name))
                 accessor = 'get_' + IDLToCIdentifier(attr.identifier.name)
                 jitinfo = "nullptr"
@@ -2604,64 +2668,72 @@ class AttrDefiner(PropertyDefiner):
                                         "readable attribute %s.%s" %
                                         (self.descriptor.name,
                                          attr.identifier.name))
                     accessor = ("GenericGetter<LenientThisPolicy, %s>" %
                                 exceptionPolicy)
                 elif attr.getExtendedAttribute("CrossOriginReadable"):
                     accessor = ("GenericGetter<CrossOriginThisPolicy, %s>" %
                                 exceptionPolicy)
-                elif self.descriptor.interface.isOnGlobalProtoChain():
+                elif descriptor.interface.isOnGlobalProtoChain():
                     accessor = ("GenericGetter<MaybeGlobalThisPolicy, %s>" %
                                 exceptionPolicy)
                 else:
                     accessor = ("GenericGetter<NormalThisPolicy, %s>" %
                                 exceptionPolicy)
                 jitinfo = ("&%s_getterinfo" %
                            IDLToCIdentifier(attr.identifier.name))
             return "%s, %s" % \
                    (accessor, jitinfo)
 
         def setter(attr):
             if (attr.readonly and
                 attr.getExtendedAttribute("PutForwards") is None and
                 attr.getExtendedAttribute("Replaceable") is None and
                 attr.getExtendedAttribute("LenientSetter") is None):
                 return "nullptr, nullptr"
-            if self.static:
+            if crossOriginOnly and not isCrossOriginWritable(attr, descriptor):
+                return "nullptr, nullptr"
+            if static:
                 accessor = 'set_' + IDLToCIdentifier(attr.identifier.name)
                 jitinfo = "nullptr"
             else:
                 if attr.hasLenientThis():
-                    if IsCrossOriginWritable(attr, self.descriptor):
+                    if isCrossOriginWritable(attr, descriptor):
                         raise TypeError("Can't handle lenient cross-origin "
                                         "writable attribute %s.%s" %
-                                        (self.descriptor.name,
+                                        (descriptor.name,
                                          attr.identifier.name))
                     accessor = "GenericSetter<LenientThisPolicy>"
-                elif IsCrossOriginWritable(attr, self.descriptor):
+                elif isCrossOriginWritable(attr, descriptor):
                     accessor = "GenericSetter<CrossOriginThisPolicy>"
-                elif self.descriptor.interface.isOnGlobalProtoChain():
+                elif descriptor.interface.isOnGlobalProtoChain():
                     accessor = "GenericSetter<MaybeGlobalThisPolicy>"
                 else:
                     accessor = "GenericSetter<NormalThisPolicy>"
                 jitinfo = "&%s_setterinfo" % IDLToCIdentifier(attr.identifier.name)
             return "%s, %s" % \
                    (accessor, jitinfo)
 
-        def specData(entry):
-            name, attr = entry["name"], entry["attr"]
-            return (name, flags(attr), getter(attr), setter(attr))
+        name, attr, flags = entry["name"], entry["attr"], entry["flags"]
+        return (name, flags, getter(attr), setter(attr))
+
+    @staticmethod
+    def formatSpec(fields):
+        return '  { "%s", %s, %s, %s }' % fields
+
+    def generateArray(self, array, name):
+        if len(array) == 0:
+            return ""
 
         return self.generatePrefableArray(
-            array, name,
-            lambda fields: '  { "%s", %s, %s, %s }' % fields,
-            '  { nullptr, 0, nullptr, nullptr, nullptr, nullptr }',
+            array, name, self.formatSpec,
+            '  JS_PS_END',
             'JSPropertySpec',
-            condition, specData)
+            self.condition, functools.partial(self.specData, static=self.static))
 
 
 class ConstDefiner(PropertyDefiner):
     """
     A class for definining constants on the interface object
     """
     def __init__(self, descriptor, name):
         PropertyDefiner.__init__(self, descriptor, name)
@@ -2669,40 +2741,42 @@ class ConstDefiner(PropertyDefiner):
         constants = [m for m in descriptor.interface.members if m.isConst()]
         self.chrome = [m for m in constants if isChromeOnly(m)]
         self.regular = [m for m in constants if not isChromeOnly(m)]
 
     def generateArray(self, array, name):
         if len(array) == 0:
             return ""
 
-        def specData(const):
+        def specData(const, descriptor):
             return (const.identifier.name,
                     convertConstIDLValueToJSVal(const.value))
 
         return self.generatePrefableArray(
             array, name,
             lambda fields: '  { "%s", %s }' % fields,
             '  { 0, JS::UndefinedValue() }',
             'ConstantSpec',
             PropertyDefiner.getControllingCondition, specData)
 
 
 class PropertyArrays():
-    def __init__(self, descriptor):
-        self.staticMethods = MethodDefiner(descriptor, "StaticMethods",
+    def __init__(self, descriptor, crossOriginOnly=False):
+        self.staticMethods = MethodDefiner(descriptor, "StaticMethods", crossOriginOnly,
                                            static=True)
-        self.staticAttrs = AttrDefiner(descriptor, "StaticAttributes",
+        self.staticAttrs = AttrDefiner(descriptor, "StaticAttributes", crossOriginOnly,
                                        static=True)
-        self.methods = MethodDefiner(descriptor, "Methods", static=False)
-        self.attrs = AttrDefiner(descriptor, "Attributes", static=False)
+        self.methods = MethodDefiner(descriptor, "Methods", crossOriginOnly, static=False)
+        self.attrs = AttrDefiner(descriptor, "Attributes", crossOriginOnly, static=False)
         self.unforgeableMethods = MethodDefiner(descriptor, "UnforgeableMethods",
-                                                static=False, unforgeable=True)
+                                                crossOriginOnly, static=False,
+                                                unforgeable=True)
         self.unforgeableAttrs = AttrDefiner(descriptor, "UnforgeableAttributes",
-                                            static=False, unforgeable=True)
+                                            crossOriginOnly, static=False,
+                                            unforgeable=True)
         self.consts = ConstDefiner(descriptor, "Constants")
 
     @staticmethod
     def arrayNames():
         return ["staticMethods", "staticAttrs", "methods", "attrs",
                 "unforgeableMethods", "unforgeableAttrs", "consts"]
 
     def hasChromeOnly(self):
@@ -4071,16 +4145,87 @@ class CGClearCachedValueMethod(CGAbstrac
             declObj=declObj,
             noopRetval=noopRetval,
             saveMember=saveMember,
             slotIndex=slotIndex,
             clearXrayExpandoSlots=clearXrayExpandoSlots,
             regetMember=regetMember)
 
 
+class CGCrossOriginProperties(CGThing):
+    def __init__(self, descriptor):
+        attrs = []
+        methods = []
+        for m in descriptor.interface.members:
+            if m.isAttr() and (m.getExtendedAttribute("CrossOriginReadable") or isCrossOriginWritable(m, descriptor)):
+                if m.isStatic():
+                    raise TypeError("Don't know how to deal with static method %s" %
+                                    m.identifier.name)
+                if PropertyDefiner.getControllingCondition(m, descriptor).hasDisablers():
+                    raise TypeError("Don't know how to deal with disabler for %s" %
+                                    m.identifier.name)
+                if len(m.bindingAliases) > 0:
+                    raise TypeError("Don't know how to deal with aliases for %s" %
+                                    m.identifier.name)
+                attrs.extend(AttrDefiner.attrData(m, overrideFlags="0"))
+            elif m.isMethod() and m.getExtendedAttribute("CrossOriginCallable"):
+                if m.isStatic():
+                    raise TypeError("Don't know how to deal with static method %s" %
+                                    m.identifier.name)
+                if PropertyDefiner.getControllingCondition(m, descriptor).hasDisablers():
+                    raise TypeError("Don't know how to deal with disabler for %s" %
+                                    m.identifier.name)
+                if len(m.aliases) > 0:
+                    raise TypeError("Don't know how to deal with aliases for %s" %
+                                    m.identifier.name)
+                methods.append(MethodDefiner.methodData(m, descriptor, overrideFlags="JSPROP_READONLY"))
+
+        if len(attrs) > 0:
+            self.attributeSpecs, _ = PropertyDefiner.generatePrefableArrayValues(
+                attrs, descriptor, AttrDefiner.formatSpec, '  JS_PS_END\n',
+                AttrDefiner.condition, functools.partial(AttrDefiner.specData, crossOriginOnly=True))
+        else:
+            self.attributeSpecs = [' JS_PS_END\n']
+        if len(methods) > 0:
+            self.methodSpecs, _ = PropertyDefiner.generatePrefableArrayValues(
+                methods, descriptor, MethodDefiner.formatSpec, '  JS_FS_END\n',
+                MethodDefiner.condition, MethodDefiner.specData)
+        else:
+            self.methodSpecs = ['  JS_FS_END\n']
+
+    def declare(self):
+        return fill("""
+            extern JSPropertySpec sCrossOriginAttributes[${attributesLength}];
+            extern JSFunctionSpec sCrossOriginMethods[${methodsLength}];
+            """,
+            attributesLength=len(self.attributeSpecs),
+            methodsLength=len(self.methodSpecs))
+
+    def define(self):
+        return fill(
+            """
+            // We deliberately use brace-elision to make Visual Studio produce better initalization code.
+            #if defined(__clang__)
+            #pragma clang diagnostic push
+            #pragma clang diagnostic ignored "-Wmissing-braces"
+            #endif
+            JSPropertySpec sCrossOriginAttributes[] = {
+              $*{attributeSpecs}
+            };
+            JSFunctionSpec sCrossOriginMethods[] = {
+              $*{methodSpecs}
+            };
+            #if defined(__clang__)
+            #pragma clang diagnostic pop
+            #endif
+            """,
+            attributeSpecs=",\n".join(self.attributeSpecs),
+            methodSpecs=",\n".join(self.methodSpecs))
+
+
 class CGIsPermittedMethod(CGAbstractMethod):
     """
     crossOriginGetters/Setters/Methods are sets of names of the relevant members.
     """
     def __init__(self, descriptor, crossOriginGetters, crossOriginSetters,
                  crossOriginMethods):
         self.crossOriginGetters = crossOriginGetters
         self.crossOriginSetters = crossOriginSetters
@@ -5486,16 +5631,38 @@ def getJSToNativeConversionInfo(type, de
                                                      isOptional)
             template = wrapObjectTemplate(conversion, type,
                                           "${declName} = nullptr;\n",
                                           failureCode)
             return JSToNativeConversionInfo(template, declType=declType,
                                             declArgs=declArgs,
                                             dealWithOptional=isOptional)
 
+        if descriptor.interface.identifier.name == "WindowProxy":
+            declType = CGGeneric("mozilla::dom::WindowProxyHolder")
+            if type.nullable():
+                declType = CGTemplatedType("Nullable", declType)
+                windowProxyHolderRef = "${declName}.SetValue()"
+            else:
+                windowProxyHolderRef = "${declName}"
+
+            failureCode = onFailureBadType(failureCode, descriptor.interface.identifier.name).define()
+            templateBody = fill("""
+                JS::Rooted<JSObject*> source(cx, &$${val}.toObject());
+                if (NS_FAILED(UnwrapWindowProxyArg(cx, source, ${windowProxyHolderRef}))) {
+                    $*{onFailure}
+                }
+                """,
+                windowProxyHolderRef=windowProxyHolderRef,
+                onFailure=failureCode)
+            templateBody = wrapObjectTemplate(templateBody, type,
+                                              "${declName}.SetNull();\n", failureCode)
+            return JSToNativeConversionInfo(templateBody, declType=declType,
+                                            dealWithOptional=isOptional)
+
         # This is an interface that we implement as a concrete class
         # or an XPCOM interface.
 
         # Allow null pointers for nullable types and old-binding classes, and
         # use an RefPtr or raw pointer for callback return values to make
         # them easier to return.
         argIsPointer = (type.nullable() or type.unroll().inner.isExternal() or
                         isCallbackReturnValue)
@@ -6670,16 +6837,26 @@ def getWrapTemplateForType(type, descrip
         # the various RefPtr, rawptr, NonNull, etc cases, which ToJSValue will
         # handle for us.  So just eat the cost of the function call.
         return (wrapAndSetPtr("ToJSValue(cx, %s, ${jsvalHandle})" % result),
                 False)
 
     if type.isGeckoInterface() and not type.isCallbackInterface():
         descriptor = descriptorProvider.getDescriptor(type.unroll().inner.identifier.name)
         if type.nullable():
+            if descriptor.interface.identifier.name == "WindowProxy":
+                template, infal = getWrapTemplateForType(type.inner, descriptorProvider,
+                                                         "%s.Value()" % result, successCode,
+                                                         returnsNewObject, exceptionCode,
+                                                         spiderMonkeyInterfacesAreStructs)
+                return ("if (%s.IsNull()) {\n" % result +
+                        indent(setNull()) +
+                        "}\n" +
+                        template, infal)
+
             wrappingCode = ("if (!%s) {\n" % (result) +
                             indent(setNull()) +
                             "}\n")
         else:
             wrappingCode = ""
 
         if not descriptor.interface.isExternal():
             if descriptor.wrapperCache:
@@ -6991,18 +7168,24 @@ def getRetvalDeclarationForType(returnTy
         return CGGeneric("nsCString"), "ref", None, None, None
     if returnType.isEnum():
         result = CGGeneric(returnType.unroll().inner.identifier.name)
         if returnType.nullable():
             result = CGTemplatedType("Nullable", result)
         return result, None, None, None, None
     if returnType.isGeckoInterface() or returnType.isPromise():
         if returnType.isGeckoInterface():
-            typeName = descriptorProvider.getDescriptor(
-                returnType.unroll().inner.identifier.name).nativeType
+            typeName = returnType.unroll().inner.identifier.name
+            if typeName == "WindowProxy":
+                result = CGGeneric("WindowProxyHolder")
+                if returnType.nullable():
+                    result = CGTemplatedType("Nullable", result)
+                return result, None, None, None, None
+
+            typeName = descriptorProvider.getDescriptor(typeName).nativeType
         else:
             typeName = "Promise"
         if isMember:
             conversion = None
             result = CGGeneric("StrongPtrForMember<%s>::Type" % typeName)
         else:
             conversion = CGGeneric("StrongOrRawPtr<%s>" % typeName)
             result = CGGeneric("auto")
@@ -7506,41 +7689,49 @@ class CGPerSignatureCall(CGThing):
        the method is called (|self->nativeMethodName(...)| vs
        |nativeMethodName(...)|).
 
     We also need to know whether this is a method or a getter/setter
     to do error reporting correctly.
 
     The idlNode parameter can be either a method or an attr. We can query
     |idlNode.identifier| in both cases, so we can be agnostic between the two.
+
+    dontSetSlot should be set to True if the value should not be cached in a
+    slot (even if the attribute is marked as StoreInSlot or Cached in the
+    WebIDL).
     """
     # XXXbz For now each entry in the argument list is either an
     # IDLArgument or a FakeArgument, but longer-term we may want to
     # have ways of flagging things like JSContext* or optional_argc in
     # there.
 
     def __init__(self, returnType, arguments, nativeMethodName, static,
                  descriptor, idlNode, argConversionStartsAt=0, getter=False,
                  setter=False, isConstructor=False, useCounterName=None,
-                 resultVar=None, objectName="obj"):
+                 resultVar=None, objectName="obj", dontSetSlot=False,
+                 extendedAttributes=None):
         assert idlNode.isMethod() == (not getter and not setter)
         assert idlNode.isAttr() == (getter or setter)
         # Constructors are always static
         assert not isConstructor or static
 
         CGThing.__init__(self)
         self.returnType = returnType
         self.descriptor = descriptor
         self.idlNode = idlNode
-        self.extendedAttributes = descriptor.getExtendedAttributes(idlNode,
-                                                                   getter=getter,
-                                                                   setter=setter)
+        if extendedAttributes is None:
+            extendedAttributes = descriptor.getExtendedAttributes(idlNode,
+                                                                  getter=getter,
+                                                                  setter=setter)
+        self.extendedAttributes = extendedAttributes
         self.arguments = arguments
         self.argCount = len(arguments)
         self.isConstructor = isConstructor
+        self.setSlot = not dontSetSlot and idlNode.isAttr() and idlNode.slotIndices is not None
         cgThings = []
 
         deprecated = (idlNode.getExtendedAttribute("Deprecated") or
                       (idlNode.isStatic() and descriptor.interface.getExtendedAttribute("Deprecated")))
         if deprecated:
             cgThings.append(CGGeneric(dedent(
                 """
                 DeprecationWarning(cx, obj, nsIDocument::e%s);
@@ -7773,45 +7964,44 @@ class CGPerSignatureCall(CGThing):
             (self.returnType.isGeckoInterface() or
              self.returnType.isPromise())):
             wrapCode += dedent(
                 """
                 static_assert(!IsPointer<decltype(result)>::value,
                               "NewObject implies that we need to keep the object alive with a strong reference.");
                 """)
 
-        setSlot = self.idlNode.isAttr() and self.idlNode.slotIndices is not None
-        if setSlot:
+        if self.setSlot:
             # For attributes in slots, we want to do some
             # post-processing once we've wrapped them.
             successCode = "break;\n"
         else:
             successCode = None
 
         resultTemplateValues = {
             'jsvalRef': 'args.rval()',
             'jsvalHandle': 'args.rval()',
             'returnsNewObject': returnsNewObject,
             'isConstructorRetval': self.isConstructor,
             'successCode': successCode,
             # 'obj' in this dictionary is the thing whose compartment we are
             # trying to do the to-JS conversion in.  We're going to put that
             # thing in a variable named "conversionScope" if setSlot is true.
             # Otherwise, just use "obj" for lack of anything better.
-            'obj': "conversionScope" if setSlot else "obj"
+            'obj': "conversionScope" if self.setSlot else "obj"
         }
         try:
             wrapCode += wrapForType(self.returnType, self.descriptor, resultTemplateValues)
         except MethodNotNewObjectError, err:
             assert not returnsNewObject
             raise TypeError("%s being returned from non-NewObject method or property %s.%s" %
                             (err.typename,
                              self.descriptor.interface.identifier.name,
                              self.idlNode.identifier.name))
-        if setSlot:
+        if self.setSlot:
             if self.idlNode.isStatic():
                 raise TypeError(
                     "Attribute %s.%s is static, so we don't have a useful slot "
                     "to cache it in, because we don't have support for that on "
                     "interface objects.  See "
                     "https://bugzilla.mozilla.org/show_bug.cgi?id=1363870" %
                     (self.descriptor.interface.identifier.name,
                      self.idlNode.identifier.name))
@@ -8402,38 +8592,43 @@ class CGMethodCall(CGThing):
         return self.cgRoot.define()
 
 
 class CGGetterCall(CGPerSignatureCall):
     """
     A class to generate a native object getter call for a particular IDL
     getter.
     """
-    def __init__(self, returnType, nativeMethodName, descriptor, attr):
+    def __init__(self, returnType, nativeMethodName, descriptor, attr, dontSetSlot=False,
+                 extendedAttributes=None):
         if attr.getExtendedAttribute("UseCounter"):
             useCounterName = "%s_%s_getter" % (descriptor.interface.identifier.name,
                                                attr.identifier.name)
         else:
             useCounterName = None
         if attr.isStatic():
             nativeMethodName = "%s::%s" % (descriptor.nativeType, nativeMethodName)
         CGPerSignatureCall.__init__(self, returnType, [], nativeMethodName,
                                     attr.isStatic(), descriptor, attr,
-                                    getter=True, useCounterName=useCounterName)
+                                    getter=True, useCounterName=useCounterName,
+                                    dontSetSlot=dontSetSlot,
+                                    extendedAttributes=extendedAttributes)
 
 
 class CGNavigatorGetterCall(CGPerSignatureCall):
     """
     A class to generate a native object getter call for an IDL getter for a
     property generated by NavigatorProperty.
     """
-    def __init__(self, returnType, _, descriptor, attr):
+    def __init__(self, returnType, _, descriptor, attr,
+                 dontSetSlot=False):
         nativeMethodName = "%s::ConstructNavigatorObject" % (toBindingNamespace(returnType.inner.identifier.name))
         CGPerSignatureCall.__init__(self, returnType, [], nativeMethodName,
-                                    True, descriptor, attr, getter=True)
+                                    True, descriptor, attr, getter=True,
+                                    dontSetSlot=dontSetSlot)
 
     def getArguments(self):
         # The navigator object should be associated with the global of
         # the navigator it's coming from, which will be the global of
         # the object whose slot it gets cached in.  That's stored in
         # "slotStorage".
         return [(FakeArgument(BuiltinTypes[IDLBuiltinType.Types.object],
                               self.idlNode),
@@ -8603,28 +8798,58 @@ def MakeNativeName(name):
 class CGSpecializedMethod(CGAbstractStaticMethod):
     """
     A class for generating the C++ code for a specialized method that the JIT
     can call with lower overhead.
     """
     def __init__(self, descriptor, method):
         self.method = method
         name = CppKeywords.checkMethodName(IDLToCIdentifier(method.identifier.name))
+        if method.getExtendedAttribute("CrossOriginCallable"):
+            selfArg = Argument('void*', 'void_self')
+        else:
+            selfArg = Argument('%s*' % descriptor.nativeType, 'self')
         args = [Argument('JSContext*', 'cx'),
                 Argument('JS::Handle<JSObject*>', 'obj'),
-                Argument('%s*' % descriptor.nativeType, 'self'),
+                selfArg,
                 Argument('const JSJitMethodCallArgs&', 'args')]
         CGAbstractStaticMethod.__init__(self, descriptor, name, 'bool', args,
                                         canRunScript=True)
 
     def definition_body(self):
         nativeName = CGSpecializedMethod.makeNativeName(self.descriptor,
                                                         self.method)
-        return CGMethodCall(nativeName, self.method.isStatic(), self.descriptor,
+        call = CGMethodCall(nativeName, self.method.isStatic(), self.descriptor,
                             self.method).define()
+        if self.method.getExtendedAttribute("CrossOriginCallable"):
+            for signature in self.method.signatures():
+                # non-void signatures would require us to deal with remote proxies for the
+                # return value here.
+                if not signature[0].isVoid():
+                    raise TypeError("We don't support a method marked as CrossOriginCallable "
+                                    "with non-void return type")
+            prototypeID, _ = PrototypeIDAndDepth(self.descriptor)
+            return fill("""
+                // CrossOriginThisPolicy::UnwrapThisObject stores a ${nativeType}::RemoteProxy in void_self
+                // if obj is a proxy with a RemoteObjectProxy handler for the right type, or else it stores
+                // a ${nativeType}. If we get here from the JIT (without going through UnwrapThisObject) we
+                // know void_self contains a ${nativeType}; we don't have special cases in the JIT to deal
+                // with remote object proxies.
+                if (IsRemoteObjectProxy(obj, ${prototypeID})) {
+                    ${nativeType}::RemoteProxy* self = static_cast<${nativeType}::RemoteProxy*>(void_self);
+                    $*{call}
+                }
+                ${nativeType}* self = static_cast<${nativeType}*>(void_self);
+                $*{call}
+                """,
+                prototypeID=prototypeID,
+                ifaceName=self.descriptor.name,
+                nativeType=self.descriptor.nativeType,
+                call=call)
+        return call
 
     def auto_profiler_label(self):
         interface_name = self.descriptor.interface.identifier.name
         method_name = self.method.identifier.name
         return fill(
             """
             AUTO_PROFILER_LABEL_DYNAMIC_FAST(
               "${interface_name}", "${method_name}", DOM, cx,
@@ -8906,20 +9131,24 @@ class CGStaticMethod(CGAbstractStaticBin
 class CGSpecializedGetter(CGAbstractStaticMethod):
     """
     A class for generating the code for a specialized attribute getter
     that the JIT can call with lower overhead.
     """
     def __init__(self, descriptor, attr):
         self.attr = attr
         name = 'get_' + IDLToCIdentifier(attr.identifier.name)
+        if attr.getExtendedAttribute("CrossOriginReadable"):
+            selfArg = Argument('void*', 'void_self')
+        else:
+            selfArg = Argument('%s*' % descriptor.nativeType, 'self')
         args = [
             Argument('JSContext*', 'cx'),
             Argument('JS::Handle<JSObject*>', 'obj'),
-            Argument('%s*' % descriptor.nativeType, 'self'),
+            selfArg,
             Argument('JSJitGetterCallArgs', 'args')
         ]
         # StoreInSlot attributes have their getters called from Wrap().  We
         # really hope they can't run script, and don't want to annotate Wrap()
         # methods as doing that anyway, so let's not annotate them as
         # MOZ_CAN_RUN_SCRIPT.
         CGAbstractStaticMethod.__init__(
             self, descriptor, name, "bool", args,
@@ -8929,16 +9158,40 @@ class CGSpecializedGetter(CGAbstractStat
         if self.attr.isMaplikeOrSetlikeAttr():
             # If the interface is maplike/setlike, there will be one getter
             # method for the size property of the backing object. Due to having
             # to unpack the backing object from the slot, this requires its own
             # generator.
             return getMaplikeOrSetlikeSizeGetterBody(self.descriptor, self.attr)
         nativeName = CGSpecializedGetter.makeNativeName(self.descriptor,
                                                         self.attr)
+        prefix = ""
+        type = self.attr.type
+        if self.attr.getExtendedAttribute("CrossOriginReadable"):
+            remoteType = type
+            extendedAttributes = self.descriptor.getExtendedAttributes(self.attr, getter=True)
+            if remoteType.isGeckoInterface() and not remoteType.unroll().inner.isExternal():
+                # We'll use a JSObject. It might make more sense to use remoteType's
+                # RemoteProxy, but it's not easy to construct a type for that from here.
+                remoteType = BuiltinTypes[IDLBuiltinType.Types.object]
+                extendedAttributes.append('canOOM')
+                extendedAttributes.remove('infallible')
+            prototypeID, _ = PrototypeIDAndDepth(self.descriptor)
+            prefix = fill("""
+                if (IsRemoteObjectProxy(obj, ${prototypeID})) {
+                    ${nativeType}::RemoteProxy* self = static_cast<${nativeType}::RemoteProxy*>(void_self);
+                    $*{call}
+                }
+                ${nativeType}* self = static_cast<${nativeType}*>(void_self);
+            """,
+            prototypeID=prototypeID,
+            ifaceName=self.descriptor.name,
+            nativeType=self.descriptor.nativeType,
+            call=CGGetterCall(remoteType, nativeName, self.descriptor, self.attr, dontSetSlot=True,
+                              extendedAttributes=extendedAttributes).define())
         if self.attr.slotIndices is not None:
             # We're going to store this return value in a slot on some object,
             # to cache it.  The question is, which object?  For dictionary and
             # sequence return values, we want to use a slot on the Xray expando
             # if we're called via Xrays, and a slot on our reflector otherwise.
             # On the other hand, when dealing with some interfacce types
             # (navigator properties, window.document) we want to avoid calling
             # the getter more than once.  In the case of navigator properties
@@ -8949,31 +9202,31 @@ class CGSpecializedGetter(CGAbstractStat
             # around.
             #
             # The upshot is that we use the reflector slot for any getter whose
             # type is a gecko interface, whether we're called via Xrays or not.
             # Since [Cached] and [StoreInSlot] cannot be used with "NewObject",
             # we know that in the interface type case the returned object is
             # wrappercached.  So creating Xrays to it is reasonable.
             if mayUseXrayExpandoSlots(self.descriptor, self.attr):
-                prefix = fill(
+                prefix += fill(
                     """
                     // Have to either root across the getter call or reget after.
                     bool isXray;
                     JS::Rooted<JSObject*> slotStorage(cx, GetCachedSlotStorageObject(cx, obj, &isXray));
                     if (!slotStorage) {
                       return false;
                     }
                     const size_t slotIndex = isXray ? ${xraySlotIndex} : ${slotIndex};
                     """,
                     xraySlotIndex=memberXrayExpandoReservedSlot(self.attr,
                                                                 self.descriptor),
                     slotIndex=memberReservedSlot(self.attr, self.descriptor))
             else:
-                prefix = fill(
+                prefix += fill(
                     """
                     // Have to either root across the getter call or reget after.
                     JS::Rooted<JSObject*> slotStorage(cx, js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false));
                     MOZ_ASSERT(IsDOMObject(slotStorage));
                     const size_t slotIndex = ${slotIndex};
                     """,
                     slotIndex=memberReservedSlot(self.attr, self.descriptor))
 
@@ -8988,25 +9241,23 @@ class CGSpecializedGetter(CGAbstractStat
                     // The cached value is in the compartment of slotStorage,
                     // so wrap into the caller compartment as needed.
                     return ${maybeWrap}(cx, args.rval());
                   }
                 }
 
                 """,
                 maybeWrap=getMaybeWrapValueFuncForType(self.attr.type))
-        else:
-            prefix = ""
 
         if self.attr.navigatorObjectGetter:
             cgGetterCall = CGNavigatorGetterCall
         else:
             cgGetterCall = CGGetterCall
         return (prefix +
-                cgGetterCall(self.attr.type, nativeName,
+                cgGetterCall(type, nativeName,
                              self.descriptor, self.attr).define())
 
     def auto_profiler_label(self):
         interface_name = self.descriptor.interface.identifier.name
         attr_name = self.attr.identifier.name
         return fill(
             """
             AUTO_PROFILER_LABEL_DYNAMIC_FAST(
@@ -9092,28 +9343,53 @@ class CGStaticGetter(CGAbstractStaticBin
 class CGSpecializedSetter(CGAbstractStaticMethod):
     """
     A class for generating the code for a specialized attribute setter
     that the JIT can call with lower overhead.
     """
     def __init__(self, descriptor, attr):
         self.attr = attr
         name = 'set_' + IDLToCIdentifier(attr.identifier.name)
+        if attr.getExtendedAttribute("CrossOriginWritable"):
+            selfArg = Argument('void*', 'void_self')
+        else:
+            selfArg = Argument('%s*' % descriptor.nativeType, 'self')
         args = [Argument('JSContext*', 'cx'),
                 Argument('JS::Handle<JSObject*>', 'obj'),
-                Argument('%s*' % descriptor.nativeType, 'self'),
+                selfArg,
                 Argument('JSJitSetterCallArgs', 'args')]
         CGAbstractStaticMethod.__init__(self, descriptor, name, "bool", args,
                                         canRunScript=True)
 
     def definition_body(self):
         nativeName = CGSpecializedSetter.makeNativeName(self.descriptor,
                                                         self.attr)
-        return CGSetterCall(self.attr.type, nativeName, self.descriptor,
-                            self.attr).define()
+        type = self.attr.type
+        call = CGSetterCall(type, nativeName, self.descriptor, self.attr).define()
+        if self.attr.getExtendedAttribute("CrossOriginWritable"):
+            if type.isGeckoInterface() and not type.unroll().inner.isExternal():
+                # a setter taking a Gecko interface would require us to deal with remote
+                # proxies for the value here.
+                raise TypeError("We don't support the setter of %s marked as "
+                                "CrossOriginWritable because it takes a Gecko interface "
+                                "as the value", attr.identifier.name)
+            prototypeID, _ = PrototypeIDAndDepth(self.descriptor)
+            return fill("""
+                if (IsRemoteObjectProxy(obj, ${prototypeID})) {
+                    ${nativeType}::RemoteProxy* self = static_cast<${nativeType}::RemoteProxy*>(void_self);
+                    $*{call}
+                }
+                ${nativeType}* self = static_cast<${nativeType}*>(void_self);
+                $*{call}
+                """,
+                prototypeID=prototypeID,
+                ifaceName=self.descriptor.name,
+                nativeType=self.descriptor.nativeType,
+                call=call)
+        return call
 
     def auto_profiler_label(self):
         interface_name = self.descriptor.interface.identifier.name
         attr_name = self.attr.identifier.name
         return fill(
             """
             AUTO_PROFILER_LABEL_DYNAMIC_FAST(
               "${interface_name}", "${attr_name}", DOM, cx,
@@ -9829,21 +10105,23 @@ def getUnionAccessorSignatureType(type, 
 
     # Nested unions are unwrapped automatically into our flatMemberTypes.
     assert not type.isUnion()
 
     if type.isGeckoInterface():
         descriptor = descriptorProvider.getDescriptor(
             type.unroll().inner.identifier.name)
         typeName = CGGeneric(descriptor.nativeType)
-        # Allow null pointers for old-binding classes.
-        if type.unroll().inner.isExternal():
+        if not type.unroll().inner.isExternal():
+            typeName = CGWrapper(typeName, post="&")
+        elif descriptor.interface.identifier.name == "WindowProxy":
+            typeName = CGGeneric("WindowProxyHolder const&")
+        else:
+            # Allow null pointers for old-binding classes.
             typeName = CGWrapper(typeName, post="*")
-        else:
-            typeName = CGWrapper(typeName, post="&")
         return typeName
 
     if type.isSpiderMonkeyInterface():
         typeName = CGGeneric(type.name)
         return CGWrapper(typeName, post=" const &")
 
     if type.isDOMString() or type.isUSVString():
         return CGGeneric("const nsAString&")
@@ -12348,24 +12626,24 @@ def memberProperties(m, descriptor):
                 if m.getExtendedAttribute("CrossOriginCallable"):
                     props.isCrossOriginMethod = True
     elif m.isAttr():
         if not m.isStatic() and descriptor.interface.hasInterfacePrototypeObject():
             if m.getExtendedAttribute("CrossOriginReadable"):
                 props.isCrossOriginGetter = True
         if not m.readonly:
             if not m.isStatic() and descriptor.interface.hasInterfacePrototypeObject():
-                if IsCrossOriginWritable(m, descriptor):
+                if isCrossOriginWritable(m, descriptor):
                     props.isCrossOriginSetter = True
         elif m.getExtendedAttribute("PutForwards"):
-            if IsCrossOriginWritable(m, descriptor):
+            if isCrossOriginWritable(m, descriptor):
                 props.isCrossOriginSetter = True
         elif (m.getExtendedAttribute("Replaceable") or
               m.getExtendedAttribute("LenientSetter")):
-            if IsCrossOriginWritable(m, descriptor):
+            if isCrossOriginWritable(m, descriptor):
                 props.isCrossOriginSetter = True
 
     return props
 
 
 class CGDescriptor(CGThing):
     def __init__(self, descriptor):
         CGThing.__init__(self)
@@ -12598,16 +12876,17 @@ class CGDescriptor(CGThing):
             if descriptor.interface.hasChildInterfaces():
                 cgThings.append(CGGetProtoObjectMethod(descriptor))
         if descriptor.interface.hasInterfaceObject():
             cgThings.append(CGGetConstructorObjectHandleMethod(descriptor))
             cgThings.append(CGGetConstructorObjectMethod(descriptor))
 
         # See whether we need we need to generate an IsPermitted method
         if crossOriginGetters or crossOriginSetters or crossOriginMethods:
+            cgThings.append(CGCrossOriginProperties(descriptor))
             cgThings.append(CGIsPermittedMethod(descriptor,
                                                 crossOriginGetters,
                                                 crossOriginSetters,
                                                 crossOriginMethods))
 
         cgThings = CGList((CGIndenter(t, declareOnly=True) for t in cgThings), "\n")
         cgThings = CGWrapper(cgThings, pre='\n', post='\n')
         self.cgRoot = CGWrapper(CGNamespace(toBindingNamespace(descriptor.name),
@@ -13899,16 +14178,17 @@ class CGBindingRoot(CGThing):
             def hasCrossOriginProperty(m):
                 props = memberProperties(m, desc)
                 return (props.isCrossOriginMethod or
                         props.isCrossOriginGetter or
                         props.isCrossOriginSetter)
 
             return any(hasCrossOriginProperty(m) for m in desc.interface.members)
 
+        bindingDeclareHeaders["mozilla/dom/RemoteObjectProxy.h"] = any(descriptorHasCrossOriginProperties(d) for d in descriptors)
         bindingDeclareHeaders["jsapi.h"] = any(descriptorHasCrossOriginProperties(d) for d in descriptors)
         bindingDeclareHeaders["jspubtd.h"] = not bindingDeclareHeaders["jsapi.h"]
         bindingDeclareHeaders["js/RootingAPI.h"] = not bindingDeclareHeaders["jsapi.h"]
 
         def descriptorHasIteratorAlias(desc):
             def hasIteratorAlias(m):
                 return m.isMethod() and "@@iterator" in m.aliases
             return any(hasIteratorAlias(m) for m in desc.interface.members)
@@ -14471,16 +14751,19 @@ class CGNativeMember(ClassMethod):
             if optional or isMember:
                 typeDecl = "OwningNonNull<Promise>"
             else:
                 typeDecl = "Promise&"
             return (typeDecl, False, False)
 
         if type.isGeckoInterface() and not type.isCallbackInterface():
             iface = type.unroll().inner
+            if iface.identifier.name == "WindowProxy":
+                return "WindowProxyHolder", True, False
+
             argIsPointer = type.nullable() or iface.isExternal()
             forceOwningType = (iface.isCallback() or isMember)
             if argIsPointer:
                 if (optional or isMember) and forceOwningType:
                     typeDecl = "RefPtr<%s>"
                 else:
                     typeDecl = "%s*"
             else:
new file mode 100644
--- /dev/null
+++ b/dom/bindings/RemoteObjectProxy.cpp
@@ -0,0 +1,240 @@
+/* -*- 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 "RemoteObjectProxy.h"
+#include "AccessCheck.h"
+#include "jsfriendapi.h"
+
+namespace mozilla {
+namespace dom {
+
+// Give RemoteObjectProxy 2 reserved slots, like the other wrappers, so
+// JSObject::swap can swap it with CrossCompartmentWrappers without requiring
+// malloc.
+const js::Class RemoteObjectProxyClass =
+    PROXY_CLASS_DEF("Proxy", JSCLASS_HAS_RESERVED_SLOTS(2));
+
+bool RemoteObjectProxyBase::getOwnPropertyDescriptor(
+    JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
+    JS::MutableHandle<JS::PropertyDescriptor> aDesc) const {
+  bool ok = getOwnPropertyDescriptorInternal(aCx, aProxy, aId, aDesc);
+  if (!ok || aDesc.object()) {
+    return ok;
+  }
+
+  return getOwnPropertyDescriptorTail(aCx, aProxy, aId, aDesc);
+}
+
+bool RemoteObjectProxyBase::defineProperty(
+    JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
+    JS::Handle<JS::PropertyDescriptor> aDesc,
+    JS::ObjectOpResult& aResult) const {
+  // https://html.spec.whatwg.org/multipage/browsers.html#windowproxy-defineownproperty
+  // step 3 and
+  // https://html.spec.whatwg.org/multipage/browsers.html#location-defineownproperty
+  // step 2
+  return ReportCrossOriginDenial(aCx, aId, NS_LITERAL_CSTRING("define"));
+}
+
+bool RemoteObjectProxyBase::ownPropertyKeys(JSContext* aCx,
+                                            JS::Handle<JSObject*> aProxy,
+                                            JS::AutoIdVector& aProps) const {
+  // https://html.spec.whatwg.org/multipage/browsers.html#crossoriginownpropertykeys-(-o-)
+  // step 2 and
+  // https://html.spec.whatwg.org/multipage/browsers.html#crossoriginproperties-(-o-)
+  JS::Rooted<JSObject*> holder(aCx);
+  if (!EnsureHolder(aCx, aProxy, &holder) ||
+      !js::GetPropertyKeys(aCx, holder,
+                           JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS,
+                           &aProps)) {
+    return false;
+  }
+
+  // https://html.spec.whatwg.org/multipage/browsers.html#crossoriginownpropertykeys-(-o-)
+  // step 3 and 4
+  return xpc::AppendCrossOriginWhitelistedPropNames(aCx, aProps);
+}
+
+bool RemoteObjectProxyBase::delete_(JSContext* aCx,
+                                    JS::Handle<JSObject*> aProxy,
+                                    JS::Handle<jsid> aId,
+                                    JS::ObjectOpResult& aResult) const {
+  // https://html.spec.whatwg.org/multipage/browsers.html#windowproxy-delete
+  // step 3 and
+  // https://html.spec.whatwg.org/multipage/browsers.html#location-delete step 2
+  return ReportCrossOriginDenial(aCx, aId, NS_LITERAL_CSTRING("delete"));
+}
+
+bool RemoteObjectProxyBase::getPrototype(
+    JSContext* aCx, JS::Handle<JSObject*> aProxy,
+    JS::MutableHandle<JSObject*> aProtop) const {
+  // https://html.spec.whatwg.org/multipage/browsers.html#windowproxy-getprototypeof
+  // step 3 and
+  // https://html.spec.whatwg.org/multipage/browsers.html#location-getprototypeof
+  // step 2
+  aProtop.set(nullptr);
+  return true;
+}
+
+bool RemoteObjectProxyBase::setPrototype(JSContext* aCx,
+                                         JS::Handle<JSObject*> aProxy,
+                                         JS::Handle<JSObject*> aProto,
+                                         JS::ObjectOpResult& aResult) const {
+  // https://html.spec.whatwg.org/multipage/browsers.html#windowproxy-setprototypeof
+  // and
+  // https://html.spec.whatwg.org/multipage/browsers.html#location-setprototypeof
+  // say to call SetImmutablePrototype, which does nothing and just returns
+  // whether the passed-in value equals the current prototype. Our current
+  // prototype is always null, so this just comes down to returning whether null
+  // was passed in.
+  //
+  // In terms of ObjectOpResult that means calling one of the fail*() things on
+  // it if non-null was passed, and it's got one that does just what we want.
+  if (!aProto) {
+    return aResult.succeed();
+  }
+  return aResult.failCantSetProto();
+}
+
+bool RemoteObjectProxyBase::getPrototypeIfOrdinary(
+    JSContext* aCx, JS::Handle<JSObject*> aProxy, bool* aIsOrdinary,
+    JS::MutableHandle<JSObject*> aProtop) const {
+  // WindowProxy's and Location's [[GetPrototypeOf]] traps aren't the ordinary
+  // definition:
+  //
+  //   https://html.spec.whatwg.org/multipage/browsers.html#windowproxy-getprototypeof
+  //   https://html.spec.whatwg.org/multipage/browsers.html#location-getprototypeof
+  //
+  // We nonetheless can implement it with a static [[Prototype]], because the
+  // [[GetPrototypeOf]] trap should always return null.
+  *aIsOrdinary = true;
+  return true;
+}
+
+bool RemoteObjectProxyBase::preventExtensions(
+    JSContext* aCx, JS::Handle<JSObject*> aProxy,
+    JS::ObjectOpResult& aResult) const {
+  // https://html.spec.whatwg.org/multipage/browsers.html#windowproxy-preventextensions
+  // and
+  // https://html.spec.whatwg.org/multipage/browsers.html#location-preventextensions
+  return aResult.failCantPreventExtensions();
+}
+
+bool RemoteObjectProxyBase::isExtensible(JSContext* aCx,
+                                         JS::Handle<JSObject*> aProxy,
+                                         bool* aExtensible) const {
+  // https://html.spec.whatwg.org/multipage/browsers.html#windowproxy-isextensible
+  // and
+  // https://html.spec.whatwg.org/multipage/browsers.html#location-isextensible
+  *aExtensible = true;
+  return true;
+}
+
+bool RemoteObjectProxyBase::get(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+                                JS::Handle<JS::Value> aReceiver,
+                                JS::Handle<jsid> aId,
+                                JS::MutableHandle<JS::Value> aVp) const {
+  Rooted<PropertyDescriptor> desc(aCx);
+  if (!getOwnPropertyDescriptor(aCx, aProxy, aId, &desc)) {
+    return false;
+  }
+
+  MOZ_ASSERT(desc.object());
+
+  if (desc.isDataDescriptor()) {
+    aVp.set(desc.value());
+    return true;
+  }
+
+  JS::Rooted<JSObject*> getter(aCx);
+  if (!desc.hasGetterObject() || !(getter = desc.getterObject())) {
+    return ReportCrossOriginDenial(aCx, aId, NS_LITERAL_CSTRING("get"));
+  }
+
+  return JS::Call(aCx, aReceiver, getter, JS::HandleValueArray::empty(), aVp);
+}
+
+bool RemoteObjectProxyBase::set(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+                                JS::Handle<jsid> aId,
+                                JS::Handle<JS::Value> aValue,
+                                JS::Handle<JS::Value> aReceiver,
+                                JS::ObjectOpResult& aResult) const {
+  Rooted<PropertyDescriptor> desc(aCx);
+  if (!getOwnPropertyDescriptor(aCx, aProxy, aId, &desc)) {
+    return false;
+  }
+
+  MOZ_ASSERT(desc.object());
+
+  JS::Rooted<JSObject*> setter(aCx);
+  if (!desc.hasSetterObject() || !(setter = desc.setterObject())) {
+    return ReportCrossOriginDenial(aCx, aId, NS_LITERAL_CSTRING("set"));
+  }
+
+  JS::Rooted<JS::Value> rv(aCx);
+  return JS::Call(aCx, aReceiver, setter, JS::HandleValueArray(aValue), &rv) &&
+         aResult.succeed();
+}
+
+bool RemoteObjectProxyBase::hasOwn(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+                                   JS::Handle<jsid> aId, bool* aBp) const {
+  JS::Rooted<JSObject*> holder(aCx);
+  if (!EnsureHolder(aCx, aProxy, &holder) ||
+      !JS_AlreadyHasOwnPropertyById(aCx, holder, aId, aBp)) {
+    return false;
+  }
+
+  if (!*aBp) {
+    *aBp = xpc::IsCrossOriginWhitelistedProp(aCx, aId);
+  }
+
+  return true;
+}
+
+bool RemoteObjectProxyBase::getOwnEnumerablePropertyKeys(
+    JSContext* aCx, JS::Handle<JSObject*> aProxy,
+    JS::AutoIdVector& aProps) const {
+  return true;
+}
+
+JSObject* RemoteObjectProxyBase::CreateProxyObject(
+    JSContext* aCx, void* aNative, const js::Class* aClasp) const {
+  js::ProxyOptions options;
+  options.setClass(aClasp);
+  JS::Rooted<JS::Value> native(aCx, JS::PrivateValue(aNative));
+  return js::NewProxyObject(aCx, this, native, nullptr, options);
+}
+
+/* static */
+bool RemoteObjectProxyBase::getOwnPropertyDescriptorTail(
+    JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
+    JS::MutableHandle<JS::PropertyDescriptor> aDesc) {
+  if (xpc::IsCrossOriginWhitelistedProp(aCx, aId)) {
+    // https://html.spec.whatwg.org/multipage/browsers.html#crossorigingetownpropertyhelper-(-o,-p-)
+    // step 3 says to return PropertyDescriptor {
+    //   [[Value]]: undefined, [[Writable]]: false, [[Enumerable]]: false,
+    //   [[Configurable]]: true
+    // }.
+    //
+    aDesc.setDataDescriptor(JS::UndefinedHandleValue, JSPROP_READONLY);
+    aDesc.object().set(aProxy);
+    return true;
+  }
+
+  return ReportCrossOriginDenial(aCx, aId, NS_LITERAL_CSTRING("access"));
+}
+
+/* static */
+bool RemoteObjectProxyBase::ReportCrossOriginDenial(
+    JSContext* aCx, JS::Handle<jsid> aId, const nsACString& aAccessType) {
+  xpc::AccessCheck::reportCrossOriginDenial(aCx, aId, aAccessType);
+  return false;
+}
+
+const char RemoteObjectProxyBase::sCrossOriginProxyFamily = 0;
+
+}  // namespace dom
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/bindings/RemoteObjectProxy.h
@@ -0,0 +1,200 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_RemoteObjectProxy_h
+#define mozilla_dom_RemoteObjectProxy_h
+
+#include "js/Proxy.h"
+#include "mozilla/dom/PrototypeList.h"
+#include "xpcpublic.h"
+
+namespace mozilla {
+namespace dom {
+
+/**
+ * Base class for RemoteObjectProxy. Implements the pieces of the handler that
+ * don't depend on properties/methods of the specific WebIDL interface that this
+ * proxy implements.
+ */
+class RemoteObjectProxyBase : public js::BaseProxyHandler {
+ protected:
+  explicit constexpr RemoteObjectProxyBase(prototypes::ID aPrototypeID)
+      : BaseProxyHandler(&sCrossOriginProxyFamily, false),
+        mPrototypeID(aPrototypeID) {}
+
+ public:
+  bool finalizeInBackground(const JS::Value& priv) const final { return false; }
+
+  // Standard internal methods
+  bool getOwnPropertyDescriptor(
+      JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
+      JS::MutableHandle<JS::PropertyDescriptor> aDesc) const override;
+  bool ownPropertyKeys(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+                       JS::AutoIdVector& aProps) const override;
+  bool defineProperty(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+                      JS::Handle<jsid> aId,
+                      JS::Handle<JS::PropertyDescriptor> aDesc,
+                      JS::ObjectOpResult& result) const final;
+  bool delete_(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+               JS::Handle<jsid> aId, JS::ObjectOpResult& aResult) const final;
+
+  bool getPrototype(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+                    JS::MutableHandle<JSObject*> aProtop) const final;
+  bool setPrototype(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+                    JS::Handle<JSObject*> aProto,
+                    JS::ObjectOpResult& aResult) const final;
+  bool getPrototypeIfOrdinary(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+                              bool* aIsOrdinary,
+                              JS::MutableHandle<JSObject*> aProtop) const final;
+
+  bool preventExtensions(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+                         JS::ObjectOpResult& aResult) const final;
+  bool isExtensible(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+                    bool* aExtensible) const final;
+
+  bool get(JSContext* cx, JS::Handle<JSObject*> aProxy,
+           JS::Handle<JS::Value> aReceiver, JS::Handle<jsid> aId,
+           JS::MutableHandle<JS::Value> aVp) const final;
+  bool set(JSContext* cx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
+           JS::Handle<JS::Value> aValue, JS::Handle<JS::Value> aReceiver,
+           JS::ObjectOpResult& aResult) const final;
+
+  // SpiderMonkey extensions
+  bool hasOwn(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+              JS::Handle<jsid> aId, bool* aBp) const override;
+  bool getOwnEnumerablePropertyKeys(JSContext* aCx,
+                                    JS::Handle<JSObject*> aProxy,
+                                    JS::AutoIdVector& aProps) const override;
+
+  bool isCallable(JSObject* aObj) const final { return false; }
+  bool isConstructor(JSObject* aObj) const final { return false; }
+
+  static void* GetNative(JSObject* aProxy) {
+    return js::GetProxyPrivate(aProxy).toPrivate();
+  }
+
+  /**
+   * Returns true if aProxy represents an object implementing the WebIDL
+   * interface for aProtoID. aProxy should be a proxy object.
+   */
+  static inline bool IsRemoteObjectProxy(JSObject* aProxy,
+                                         prototypes::ID aProtoID) {
+    const js::BaseProxyHandler* handler = js::GetProxyHandler(aProxy);
+    return handler->family() == &sCrossOriginProxyFamily &&
+           static_cast<const RemoteObjectProxyBase*>(handler)->mPrototypeID ==
+               aProtoID;
+  }
+
+ protected:
+  bool getOwnPropertyDescriptorInternal(
+      JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
+      JS::MutableHandle<JS::PropertyDescriptor> aDesc) const {
+    JS::Rooted<JSObject*> holder(aCx);
+    if (!EnsureHolder(aCx, aProxy, &holder) ||
+        !JS_GetOwnPropertyDescriptorById(aCx, holder, aId, aDesc)) {
+      return false;
+    }
+
+    if (aDesc.object()) {
+      aDesc.object().set(aProxy);
+    }
+
+    return true;
+  }
+
+  JSObject* CreateProxyObject(JSContext* aCx, void* aNative,
+                              const js::Class* aClasp) const;
+
+  /**
+   * Implements the tail of getOwnPropertyDescriptor, dealing in particular with
+   * properties that are whitelisted by xpc::IsCrossOriginWhitelistedProp.
+   */
+  static bool getOwnPropertyDescriptorTail(
+      JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
+      JS::MutableHandle<JS::PropertyDescriptor> aDesc);
+  static bool ReportCrossOriginDenial(JSContext* aCx, JS::Handle<jsid> aId,
+                                      const nsACString& aAccessType);
+
+  /**
+   * This gets a cached, or creates and caches, a holder object that contains
+   * the WebIDL properties for this proxy.
+   */
+  bool EnsureHolder(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+                    JS::MutableHandle<JSObject*> aHolder) const {
+    // FIXME Need to have a holder per realm, should store a weakmap in the
+    //       reserved slot.
+    JS::Value v = js::GetProxyReservedSlot(aProxy, 0);
+    if (v.isObject()) {
+      aHolder.set(&v.toObject());
+      return true;
+    }
+
+    aHolder.set(JS_NewObjectWithGivenProto(aCx, nullptr, nullptr));
+    if (!aHolder || !DefinePropertiesAndFunctions(aCx, aHolder)) {
+      return false;
+    }
+
+    js::SetProxyReservedSlot(aProxy, 0, JS::ObjectValue(*aHolder));
+    return true;
+  }
+
+  virtual bool DefinePropertiesAndFunctions(
+      JSContext* aCx, JS::Handle<JSObject*> aHolder) const = 0;
+
+  const prototypes::ID mPrototypeID;
+
+  static const char sCrossOriginProxyFamily;
+};
+
+/**
+ * Proxy handler for proxy objects that represent an object implementing a
+ * WebIDL interface that has cross-origin accessible properties/methods, and
+ * which lives in a different process. The WebIDL code generator will create
+ * arrays of cross-origin accessible properties/methods that can be used as
+ * arguments to this template.
+ *
+ * The properties and methods can be cached on a holder JSObject, stored in a
+ * reserved slot on the proxy object.
+ *
+ * The proxy objects that use a handler derived from this one are stored in a
+ * hash map in the JS compartment's private (@see
+ * xpc::CompartmentPrivate::GetRemoteProxyMap).
+ */
+template <class Native, JSPropertySpec* P, JSFunctionSpec* F>
+class RemoteObjectProxy : public RemoteObjectProxyBase {
+ public:
+  JSObject* CreateProxyObject(JSContext* aCx, Native* aNative,
+                              const js::Class* aClasp) const {
+    return RemoteObjectProxyBase::CreateProxyObject(aCx, aNative, aClasp);
+  }
+
+ protected:
+  using RemoteObjectProxyBase::RemoteObjectProxyBase;
+
+ private:
+  bool DefinePropertiesAndFunctions(
+      JSContext* aCx, JS::Handle<JSObject*> aHolder) const final {
+    return JS_DefineProperties(aCx, aHolder, P) &&
+           JS_DefineFunctions(aCx, aHolder, F);
+  }
+};
+
+/**
+ * Returns true if aObj is a proxy object that represents an object implementing
+ * the WebIDL interface for aProtoID.
+ */
+static inline bool IsRemoteObjectProxy(JSObject* aObj,
+                                       prototypes::ID aProtoID) {
+  if (!js::IsProxy(aObj)) {
+    return false;
+  }
+  return RemoteObjectProxyBase::IsRemoteObjectProxy(aObj, aProtoID);
+}
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif /* mozilla_dom_RemoteObjectProxy_h */
--- a/dom/bindings/ToJSValue.cpp
+++ b/dom/bindings/ToJSValue.cpp
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/ToJSValue.h"
 #include "mozilla/dom/DOMException.h"
 #include "mozilla/dom/Exceptions.h"
 #include "mozilla/dom/Promise.h"
+#include "mozilla/dom/WindowProxyHolder.h"
 #include "nsAString.h"
 #include "nsContentUtils.h"
 #include "nsStringBuffer.h"
 #include "xpcpublic.h"
 
 namespace mozilla {
 namespace dom {
 
@@ -55,10 +56,37 @@ bool ToJSValue(JSContext* aCx, ErrorResu
 }
 
 bool ToJSValue(JSContext* aCx, Promise& aArgument,
                JS::MutableHandle<JS::Value> aValue) {
   aValue.setObject(*aArgument.PromiseObj());
   return MaybeWrapObjectValue(aCx, aValue);
 }
 
+bool ToJSValue(JSContext* aCx, const WindowProxyHolder& aArgument,
+               JS::MutableHandle<JS::Value> aValue) {
+  BrowsingContext* bc = aArgument.get();
+  if (!bc) {
+    aValue.setNull();
+    return true;
+  }
+  JS::Rooted<JSObject*> windowProxy(aCx);
+  if (bc->GetDocShell()) {
+    windowProxy = bc->GetWindowProxy();
+    if (!windowProxy) {
+      nsPIDOMWindowOuter* window = bc->GetDOMWindow();
+      if (!window->EnsureInnerWindow()) {
+        return Throw(aCx, NS_ERROR_UNEXPECTED);
+      }
+      windowProxy = bc->GetWindowProxy();
+    }
+    return ToJSValue(aCx, windowProxy, aValue);
+  }
+
+  if (!GetRemoteOuterWindowProxy(aCx, bc, &windowProxy)) {
+    return false;
+  }
+  aValue.setObjectOrNull(windowProxy);
+  return true;
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/bindings/ToJSValue.h
+++ b/dom/bindings/ToJSValue.h
@@ -18,16 +18,17 @@
 #include "nsTArray.h"
 #include "nsWrapperCache.h"
 #include "nsAutoPtr.h"
 
 namespace mozilla {
 namespace dom {
 
 class Promise;
+class WindowProxyHolder;
 
 // If ToJSValue returns false, it must set an exception on the
 // JSContext.
 
 // Accept strings.
 MOZ_MUST_USE bool ToJSValue(JSContext* aCx, const nsAString& aArgument,
                             JS::MutableHandle<JS::Value> aValue);
 
@@ -216,16 +217,19 @@ ToJSValue(JSContext* aCx, T& aArgument, 
   // Make sure we're called in a compartment
   MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx));
 
   xpcObjectHelper helper(ToSupports(&aArgument));
   JS::Rooted<JSObject*> scope(aCx, JS::CurrentGlobalOrNull(aCx));
   return XPCOMObjectToJsval(aCx, scope, helper, nullptr, true, aValue);
 }
 
+MOZ_MUST_USE bool ToJSValue(JSContext* aCx, const WindowProxyHolder& aArgument,
+                            JS::MutableHandle<JS::Value> aValue);
+
 // Accept nsRefPtr/nsCOMPtr
 template <typename T>
 MOZ_MUST_USE bool ToJSValue(JSContext* aCx, const nsCOMPtr<T>& aArgument,
                             JS::MutableHandle<JS::Value> aValue) {
   return ToJSValue(aCx, *aArgument.get(), aValue);
 }
 
 template <typename T>
--- a/dom/bindings/moz.build
+++ b/dom/bindings/moz.build
@@ -44,16 +44,17 @@ EXPORTS.mozilla.dom += [
     'FakeString.h',
     'IterableIterator.h',
     'JSSlots.h',
     'NonRefcountedDOMObject.h',
     'Nullable.h',
     'PrimitiveConversions.h',
     'ReadableStream.h',
     'Record.h',
+    'RemoteObjectProxy.h',
     'RootedDictionary.h',
     'SimpleGlobalObject.h',
     'SpiderMonkeyInterface.h',
     'StructuredClone.h',
     'ToJSValue.h',
     'TypedArray.h',
     'UnionMember.h',
     'WebIDLGlobalNameHash.h',
@@ -108,16 +109,17 @@ UNIFIED_SOURCES += [
     'CallbackInterface.cpp',
     'CallbackObject.cpp',
     'Date.cpp',
     'DOMJSProxyHandler.cpp',
     'Exceptions.cpp',
     'IterableIterator.cpp',
     'nsScriptError.cpp',
     'nsScriptErrorWithStack.cpp',
+    'RemoteObjectProxy.cpp',
     'SimpleGlobalObject.cpp',
     'ToJSValue.cpp',
     'WebIDLGlobalNameHash.cpp',
 ]
 
 SOURCES += [
     'StructuredClone.cpp',
 ]
--- a/dom/bindings/nsScriptError.cpp
+++ b/dom/bindings/nsScriptError.cpp
@@ -57,26 +57,17 @@ void nsScriptErrorBase::InitializeOnMain
 
   if (mInnerWindowID) {
     nsGlobalWindowInner* window =
         nsGlobalWindowInner::GetInnerWindowWithId(mInnerWindowID);
     if (window) {
       nsPIDOMWindowOuter* outer = window->GetOuterWindow();
       if (outer) mOuterWindowID = outer->WindowID();
 
-      nsIDocShell* docShell = window->GetDocShell();
-      nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(docShell);
-
-      if (loadContext) {
-        // Never mark exceptions from chrome windows as having come from
-        // private windows, since we always want them to be reported.
-        nsIPrincipal* winPrincipal = window->GetPrincipal();
-        mIsFromPrivateWindow = loadContext->UsePrivateBrowsing() &&
-                               !nsContentUtils::IsSystemPrincipal(winPrincipal);
-      }
+      mIsFromPrivateWindow = ComputeIsFromPrivateWindow(window);
     }
   }
 
   mInitializedOnMainThread = true;
 }
 
 // nsIConsoleMessage methods
 NS_IMETHODIMP
@@ -384,16 +375,25 @@ nsScriptErrorBase::GetNotes(nsIArray** a
 
   uint32_t len = mNotes.Length();
   for (uint32_t i = 0; i < len; i++) array->AppendElement(mNotes[i]);
   array.forget(aNotes);
 
   return NS_OK;
 }
 
+/* static */ bool nsScriptErrorBase::ComputeIsFromPrivateWindow(
+    nsGlobalWindowInner* aWindow) {
+  // Never mark exceptions from chrome windows as having come from private
+  // windows, since we always want them to be reported.
+  nsIPrincipal* winPrincipal = aWindow->GetPrincipal();
+  return aWindow->IsPrivateBrowsing() &&
+         !nsContentUtils::IsSystemPrincipal(winPrincipal);
+}
+
 NS_IMPL_ISUPPORTS(nsScriptError, nsIConsoleMessage, nsIScriptError)
 
 nsScriptErrorNote::nsScriptErrorNote()
     : mMessage(), mSourceName(), mLineNumber(0), mColumnNumber(0) {}
 
 nsScriptErrorNote::~nsScriptErrorNote() {}
 
 void nsScriptErrorNote::Init(const nsAString& message,
--- a/dom/bindings/nsScriptError.h
+++ b/dom/bindings/nsScriptError.h
@@ -14,16 +14,18 @@
 #include "jsapi.h"
 #include "js/RootingAPI.h"
 
 #include "nsCOMArray.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIScriptError.h"
 #include "nsString.h"
 
+class nsGlobalWindowInner;
+
 class nsScriptErrorNote final : public nsIScriptErrorNote {
  public:
   nsScriptErrorNote();
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSISCRIPTERRORNOTE
 
   void Init(const nsAString& message, const nsAString& sourceName,
@@ -44,16 +46,18 @@ class nsScriptErrorBase : public nsIScri
  public:
   nsScriptErrorBase();
 
   NS_DECL_NSICONSOLEMESSAGE
   NS_DECL_NSISCRIPTERROR
 
   void AddNote(nsIScriptErrorNote* note);
 
+  static bool ComputeIsFromPrivateWindow(nsGlobalWindowInner* aWindow);
+
  protected:
   virtual ~nsScriptErrorBase();
 
   void InitializeOnMainThread();
 
   void InitializationHelper(const nsAString& message,
                             const nsAString& sourceLine, uint32_t lineNumber,
                             uint32_t columnNumber, uint32_t flags,
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -5133,16 +5133,20 @@ class IDLMethod(IDLInterfaceMember, IDLS
         elif identifier == "Exposed":
             convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames)
         elif (identifier == "CrossOriginCallable" or
               identifier == "WebGLHandlesContextLoss"):
             # Known no-argument attributes.
             if not attr.noArguments():
                 raise WebIDLError("[%s] must take no arguments" % identifier,
                                   [attr.location])
+            if identifier == "CrossOriginCallable" and self.isStatic():
+                raise WebIDLError("[CrossOriginCallable] is only allowed on non-static "
+                                  "attributes"
+                                  [attr.location, self.location])
         elif identifier == "Pure":
             if not attr.noArguments():
                 raise WebIDLError("[Pure] must take no arguments",
                                   [attr.location])
             self._setDependsOn("DOMState")
             self._setAffects("Nothing")
         elif identifier == "Affects":
             if not attr.hasValue():
--- a/dom/browser-element/BrowserElementParent.cpp
+++ b/dom/browser-element/BrowserElementParent.cpp
@@ -13,16 +13,17 @@
 #ifdef CreateEvent
 #undef CreateEvent
 #endif
 
 #include "BrowserElementParent.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/dom/HTMLIFrameElement.h"
 #include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/WindowProxyHolder.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsVariant.h"
 #include "mozilla/dom/BrowserElementDictionariesBinding.h"
 #include "mozilla/dom/CustomEvent.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::layers;
@@ -198,48 +199,50 @@ BrowserElementParent::OpenWindowResult B
   popupFrameElement->AllowCreateFrameLoader();
   popupFrameElement->CreateRemoteFrameLoader(aPopupTabParent);
 
   return opened;
 }
 
 /* static */
 BrowserElementParent::OpenWindowResult
-BrowserElementParent::OpenWindowInProcess(nsPIDOMWindowOuter* aOpenerWindow,
+BrowserElementParent::OpenWindowInProcess(BrowsingContext* aOpenerWindow,
                                           nsIURI* aURI, const nsAString& aName,
                                           const nsACString& aFeatures,
                                           bool aForceNoOpener,
                                           mozIDOMWindowProxy** aReturnWindow) {
   *aReturnWindow = nullptr;
 
   // If we call window.open from an <iframe> inside an <iframe mozbrowser>,
   // it's as though the top-level document inside the <iframe mozbrowser>
   // called window.open.  (Indeed, in the OOP case, the inner <iframe> lives
   // out-of-process, so we couldn't touch it if we tried.)
   //
   // GetScriptableTop gets us the <iframe mozbrowser>'s window; we'll use its
   // frame element, rather than aOpenerWindow's frame element, as our "opener
   // frame element" below.
-  nsCOMPtr<nsPIDOMWindowOuter> win = aOpenerWindow->GetScriptableTop();
+  nsCOMPtr<nsPIDOMWindowOuter> win =
+      aOpenerWindow->GetDOMWindow()->GetScriptableTop();
 
   nsCOMPtr<Element> openerFrameElement = win->GetFrameElementInternal();
   NS_ENSURE_TRUE(openerFrameElement, BrowserElementParent::OPEN_WINDOW_IGNORED);
 
   RefPtr<HTMLIFrameElement> popupFrameElement =
       CreateIframe(openerFrameElement, aName, /* aRemote = */ false);
   NS_ENSURE_TRUE(popupFrameElement, BrowserElementParent::OPEN_WINDOW_IGNORED);
 
   nsAutoCString spec;
   if (aURI) {
     aURI->GetSpec(spec);
   }
 
   if (!aForceNoOpener) {
     ErrorResult res;
-    popupFrameElement->PresetOpenerWindow(aOpenerWindow, res);
+    popupFrameElement->PresetOpenerWindow(WindowProxyHolder(aOpenerWindow),
+                                          res);
     MOZ_ASSERT(!res.Failed());
   }
 
   OpenWindowResult opened = DispatchOpenWindowEvent(
       openerFrameElement, popupFrameElement, NS_ConvertUTF8toUTF16(spec), aName,
       NS_ConvertUTF8toUTF16(aFeatures));
 
   if (opened != BrowserElementParent::OPEN_WINDOW_ADDED) {
--- a/dom/browser-element/BrowserElementParent.h
+++ b/dom/browser-element/BrowserElementParent.h
@@ -14,16 +14,17 @@
 #include "mozilla/dom/Element.h"
 
 class nsIDOMWindow;
 class nsIURI;
 
 namespace mozilla {
 
 namespace dom {
+class BrowsingContext;
 class TabParent;
 }  // namespace dom
 
 /**
  * BrowserElementParent implements a portion of the parent-process side of
  * <iframe mozbrowser>.
  *
  * Most of the parent-process side of <iframe mozbrowser> is implemented in
@@ -98,18 +99,18 @@ class BrowserElementParent {
    * hand.  Feel free to add an override, if they are inconvenient to you.)
    *
    * @param aURI the URI the new window should load.  May be null.
    * @return an OpenWindowResult that describes whether the browser added the
    *         frame to a document or whether they called preventDefault to
    * prevent the platform from handling the open request
    */
   static OpenWindowResult OpenWindowInProcess(
-      nsPIDOMWindowOuter* aOpenerWindow, nsIURI* aURI, const nsAString& aName,
-      const nsACString& aFeatures, bool aForceNoOpener,
+      mozilla::dom::BrowsingContext* aOpenerWindow, nsIURI* aURI,
+      const nsAString& aName, const nsACString& aFeatures, bool aForceNoOpener,
       mozIDOMWindowProxy** aReturnWindow);
 
  private:
   static OpenWindowResult DispatchOpenWindowEvent(
       dom::Element* aOpenerFrameElement, dom::Element* aPopupFrameElement,
       const nsAString& aURL, const nsAString& aName,
       const nsAString& aFeatures);
 };
--- a/dom/events/DOMEventTargetHelper.h
+++ b/dom/events/DOMEventTargetHelper.h
@@ -123,17 +123,17 @@ class DOMEventTargetHelper : public dom:
   bool HasListenersFor(const nsAString& aType) const {
     return mListenerManager && mListenerManager->HasListenersFor(aType);
   }
 
   bool HasListenersFor(nsAtom* aTypeWithOn) const {
     return mListenerManager && mListenerManager->HasListenersFor(aTypeWithOn);
   }
 
-  virtual nsPIDOMWindowOuter* GetOwnerGlobalForBindings() override {
+  virtual nsPIDOMWindowOuter* GetOwnerGlobalForBindingsInternal() override {
     return nsPIDOMWindowOuter::GetFromCurrentInner(GetOwner());
   }
 
   nsresult CheckInnerWindowCorrectness() const {
     NS_ENSURE_STATE(!mHasOrHasHadOwnerWindow || mOwnerWindow);
     if (mOwnerWindow && !mOwnerWindow->IsCurrentInnerWindow()) {
       return NS_ERROR_FAILURE;
     }
--- a/dom/events/EventTarget.cpp
+++ b/dom/events/EventTarget.cpp
@@ -3,16 +3,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/dom/EventTarget.h"
 #include "mozilla/dom/EventTargetBinding.h"
 #include "mozilla/dom/ConstructibleEventTarget.h"
+#include "mozilla/dom/Nullable.h"
+#include "mozilla/dom/WindowProxyHolder.h"
 #include "nsIGlobalObject.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 /* static */
 already_AddRefed<EventTarget> EventTarget::Constructor(
@@ -177,10 +179,19 @@ void EventTarget::DispatchEvent(Event& a
 }
 
 void EventTarget::DispatchEvent(Event& aEvent, ErrorResult& aRv) {
   // The caller type doesn't really matter if we don't care about the
   // return value, but let's be safe and pass NonSystem.
   Unused << DispatchEvent(aEvent, CallerType::NonSystem, IgnoreErrors());
 }
 
+Nullable<WindowProxyHolder> EventTarget::GetOwnerGlobalForBindings() {
+  nsPIDOMWindowOuter* win = GetOwnerGlobalForBindingsInternal();
+  if (!win) {
+    return nullptr;
+  }
+
+  return WindowProxyHolder(win->GetBrowsingContext());
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/events/EventTarget.h
+++ b/dom/events/EventTarget.h
@@ -29,16 +29,19 @@ class EventListenerManager;
 namespace dom {
 
 class AddEventListenerOptionsOrBoolean;
 class Event;
 class EventListener;
 class EventListenerOptionsOrBoolean;
 class EventHandlerNonNull;
 class GlobalObject;
+template <typename>
+struct Nullable;
+class WindowProxyHolder;
 
 // IID for the dom::EventTarget interface
 #define NS_EVENTTARGET_IID                           \
   {                                                  \
     0xde651c36, 0x0053, 0x4c67, {                    \
       0xb1, 0x3d, 0x67, 0xb9, 0x40, 0xfc, 0x82, 0xe4 \
     }                                                \
   }
@@ -170,17 +173,18 @@ class EventTarget : public nsISupports, 
   virtual void EventListenerAdded(nsAtom* aType) {}
 
   // For an event 'foo' aType will be 'onfoo'.
   virtual void EventListenerRemoved(nsAtom* aType) {}
 
   // Returns an outer window that corresponds to the inner window this event
   // target is associated with.  Will return null if the inner window is not the
   // current inner or if there is no window around at all.
-  virtual nsPIDOMWindowOuter* GetOwnerGlobalForBindings() = 0;
+  Nullable<WindowProxyHolder> GetOwnerGlobalForBindings();
+  virtual nsPIDOMWindowOuter* GetOwnerGlobalForBindingsInternal() = 0;
 
   // The global object this event target is associated with, if any.
   // This may be an inner window or some other global object.  This
   // will never be an outer window.
   virtual nsIGlobalObject* GetOwnerGlobal() const = 0;
 
   /**
    * Get the event listener manager, creating it if it does not already exist.
--- a/dom/events/MessageEvent.cpp
+++ b/dom/events/MessageEvent.cpp
@@ -1,23 +1,23 @@
 /* -*- 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/MessageEvent.h"
+#include "mozilla/dom/BrowsingContext.h"
 #include "mozilla/dom/MessageEventBinding.h"
 #include "mozilla/dom/MessagePort.h"
 #include "mozilla/dom/MessagePortBinding.h"
 #include "mozilla/dom/ServiceWorker.h"
 
 #include "mozilla/HoldDropJSObjects.h"
 #include "jsapi.h"
-#include "nsGlobalWindow.h"  // So we can assign an nsGlobalWindow* to mWindowSource
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(MessageEvent)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MessageEvent, Event)
   tmp->mData.setUndefined();
@@ -103,17 +103,17 @@ void MessageEvent::GetSource(
 
   mozilla::HoldJSObjects(event.get());
 
   event->mOrigin = aParam.mOrigin;
   event->mLastEventId = aParam.mLastEventId;
 
   if (!aParam.mSource.IsNull()) {
     if (aParam.mSource.Value().IsWindowProxy()) {
-      event->mWindowSource = aParam.mSource.Value().GetAsWindowProxy();
+      event->mWindowSource = aParam.mSource.Value().GetAsWindowProxy().get();
     } else if (aParam.mSource.Value().IsMessagePort()) {
       event->mPortSource = aParam.mSource.Value().GetAsMessagePort();
     } else {
       event->mServiceWorkerSource = aParam.mSource.Value().GetAsServiceWorker();
     }
 
     MOZ_ASSERT(event->mWindowSource || event->mPortSource ||
                event->mServiceWorkerSource);
@@ -139,17 +139,17 @@ void MessageEvent::InitMessageEvent(
   mLastEventId = aLastEventId;
 
   mWindowSource = nullptr;
   mPortSource = nullptr;
   mServiceWorkerSource = nullptr;
 
   if (!aSource.IsNull()) {
     if (aSource.Value().IsWindowProxy()) {
-      mWindowSource = aSource.Value().GetAsWindowProxy();
+      mWindowSource = aSource.Value().GetAsWindowProxy().get();
     } else if (aSource.Value().IsMessagePort()) {
       mPortSource = &aSource.Value().GetAsMessagePort();
     } else {
       mServiceWorkerSource = &aSource.Value().GetAsServiceWorker();
     }
   }
 
   mPorts.Clear();
--- a/dom/events/MessageEvent.h
+++ b/dom/events/MessageEvent.h
@@ -9,16 +9,17 @@
 
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "nsCycleCollectionParticipant.h"
 
 namespace mozilla {
 namespace dom {
 
+class BrowsingContext;
 struct MessageEventInit;
 class MessagePort;
 class OwningWindowProxyOrMessagePortOrServiceWorker;
 class ServiceWorker;
 class WindowProxyOrMessagePortOrServiceWorker;
 
 /**
  * Implements the MessageEvent event, used for cross-document messaging and
@@ -75,17 +76,17 @@ class MessageEvent final : public Event 
 
  protected:
   ~MessageEvent();
 
  private:
   JS::Heap<JS::Value> mData;
   nsString mOrigin;
   nsString mLastEventId;
-  RefPtr<nsPIDOMWindowOuter> mWindowSource;
+  RefPtr<BrowsingContext> mWindowSource;
   RefPtr<MessagePort> mPortSource;
   RefPtr<ServiceWorker> mServiceWorkerSource;
 
   nsTArray<RefPtr<MessagePort>> mPorts;
 };
 
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/events/UIEvent.h
+++ b/dom/events/UIEvent.h
@@ -4,18 +4,21 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_UIEvent_h_
 #define mozilla_dom_UIEvent_h_
 
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/Event.h"
+#include "mozilla/dom/Nullable.h"
 #include "mozilla/dom/UIEventBinding.h"
+#include "mozilla/dom/WindowProxyHolder.h"
 #include "nsDeviceContext.h"
+#include "nsDocShell.h"
 #include "nsLayoutUtils.h"
 #include "nsPresContext.h"
 
 class nsINode;
 
 namespace mozilla {
 namespace dom {
 
@@ -42,17 +45,22 @@ class UIEvent : public Event {
   }
 
   UIEvent* AsUIEvent() override { return this; }
 
   void InitUIEvent(const nsAString& typeArg, bool canBubbleArg,
                    bool cancelableArg, nsGlobalWindowInner* viewArg,
                    int32_t detailArg);
 
-  nsPIDOMWindowOuter* GetView() const { return mView; }
+  Nullable<WindowProxyHolder> GetView() const {
+    if (!mView) {
+      return nullptr;
+    }
+    return WindowProxyHolder(mView->GetBrowsingContext());
+  }
 
   int32_t Detail() const { return mDetail; }
 
   int32_t LayerX() const { return GetLayerPoint().x; }
 
   int32_t LayerY() const { return GetLayerPoint().y; }
 
   int32_t PageX() const;
--- a/dom/html/HTMLObjectElement.cpp
+++ b/dom/html/HTMLObjectElement.cpp
@@ -369,21 +369,24 @@ HTMLObjectElement::SubmitNamesValues(HTM
 
   return aFormSubmission->AddNameValuePair(name, value);
 }
 
 int32_t HTMLObjectElement::TabIndexDefault() {
   return IsFocusableForTabIndex() ? 0 : -1;
 }
 
-nsPIDOMWindowOuter* HTMLObjectElement::GetContentWindow(
+Nullable<WindowProxyHolder> HTMLObjectElement::GetContentWindow(
     nsIPrincipal& aSubjectPrincipal) {
   nsIDocument* doc = GetContentDocument(aSubjectPrincipal);
   if (doc) {
-    return doc->GetWindow();
+    nsPIDOMWindowOuter* win = doc->GetWindow();
+    if (win) {
+      return WindowProxyHolder(win->GetBrowsingContext());
+    }
   }
 
   return nullptr;
 }
 
 bool HTMLObjectElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
                                        const nsAString& aValue,
                                        nsIPrincipal* aMaybeScriptedPrincipal,
--- a/dom/html/HTMLObjectElement.h
+++ b/dom/html/HTMLObjectElement.h
@@ -11,16 +11,19 @@
 #include "nsGenericHTMLElement.h"
 #include "nsObjectLoadingContent.h"
 #include "nsIConstraintValidation.h"
 
 namespace mozilla {
 namespace dom {
 
 class HTMLFormSubmission;
+template <typename T>
+struct Nullable;
+class WindowProxyHolder;
 
 class HTMLObjectElement final : public nsGenericHTMLFormElement,
                                 public nsObjectLoadingContent,
                                 public nsIConstraintValidation {
  public:
   explicit HTMLObjectElement(
       already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
       FromParser aFromParser = NOT_FROM_PARSER);
@@ -116,17 +119,17 @@ class HTMLObjectElement final : public n
     SetHTMLAttr(nsGkAtoms::width, aValue, aRv);
   }
   void GetHeight(DOMString& aValue) { GetHTMLAttr(nsGkAtoms::height, aValue); }
   void SetHeight(const nsAString& aValue, ErrorResult& aRv) {
     SetHTMLAttr(nsGkAtoms::height, aValue, aRv);
   }
   using nsObjectLoadingContent::GetContentDocument;
 
-  nsPIDOMWindowOuter* GetContentWindow(nsIPrincipal& aSubjectPrincipal);
+  Nullable<WindowProxyHolder> GetContentWindow(nsIPrincipal& aSubjectPrincipal);
 
   using nsIConstraintValidation::GetValidationMessage;
   using nsIConstraintValidation::SetCustomValidity;
   void GetAlign(DOMString& aValue) { GetHTMLAttr(nsGkAtoms::align, aValue); }
   void SetAlign(const nsAString& aValue, ErrorResult& aRv) {
     SetHTMLAttr(nsGkAtoms::align, aValue, aRv);
   }
   void GetArchive(DOMString& aValue) {
--- a/dom/html/nsGenericHTMLFrameElement.cpp
+++ b/dom/html/nsGenericHTMLFrameElement.cpp
@@ -66,70 +66,72 @@ int32_t nsGenericHTMLFrameElement::TabIn
 nsGenericHTMLFrameElement::~nsGenericHTMLFrameElement() {
   if (mFrameLoader) {
     mFrameLoader->Destroy();
   }
 }
 
 nsIDocument* nsGenericHTMLFrameElement::GetContentDocument(
     nsIPrincipal& aSubjectPrincipal) {
-  nsCOMPtr<nsPIDOMWindowOuter> win = GetContentWindow();
-  if (!win) {
+  RefPtr<BrowsingContext> bc = GetContentWindowInternal();
+  if (!bc) {
     return nullptr;
   }
 
-  nsIDocument* doc = win->GetDoc();
+  nsIDocument* doc = bc->GetDOMWindow()->GetDoc();
   if (!doc) {
     return nullptr;
   }
 
   // Return null for cross-origin contentDocument.
   if (!aSubjectPrincipal.SubsumesConsideringDomain(doc->NodePrincipal())) {
     return nullptr;
   }
   return doc;
 }
 
-already_AddRefed<nsPIDOMWindowOuter>
-nsGenericHTMLFrameElement::GetContentWindow() {
+BrowsingContext* nsGenericHTMLFrameElement::GetContentWindowInternal() {
   EnsureFrameLoader();
 
   if (!mFrameLoader) {
     return nullptr;
   }
 
   if (mFrameLoader->DepthTooGreat()) {
     // Claim to have no contentWindow
     return nullptr;
   }
 
-  nsCOMPtr<nsIDocShell> doc_shell = mFrameLoader->GetDocShell(IgnoreErrors());
+  RefPtr<nsDocShell> doc_shell = mFrameLoader->GetDocShell(IgnoreErrors());
   if (!doc_shell) {
     return nullptr;
   }
 
-  nsCOMPtr<nsPIDOMWindowOuter> win = doc_shell->GetWindow();
+  return doc_shell->GetBrowsingContext();
+}
 
-  if (!win) {
+Nullable<WindowProxyHolder> nsGenericHTMLFrameElement::GetContentWindow() {
+  RefPtr<BrowsingContext> bc = GetContentWindowInternal();
+  if (!bc) {
     return nullptr;
   }
-
-  return win.forget();
+  return WindowProxyHolder(bc);
 }
 
 void nsGenericHTMLFrameElement::EnsureFrameLoader() {
   if (!IsInComposedDoc() || mFrameLoader || mFrameLoaderCreationDisallowed) {
     // If frame loader is there, we just keep it around, cached
     return;
   }
 
   // Strangely enough, this method doesn't actually ensure that the
   // frameloader exists.  It's more of a best-effort kind of thing.
   mFrameLoader = nsFrameLoader::Create(
-      this, nsPIDOMWindowOuter::From(mOpenerWindow), mNetworkCreated);
+      this, mOpenerWindow ? mOpenerWindow->GetDOMWindow() : nullptr,
+      mNetworkCreated);
 }
 
 nsresult nsGenericHTMLFrameElement::CreateRemoteFrameLoader(
     nsITabParent* aTabParent) {
   MOZ_ASSERT(!mFrameLoader);
   EnsureFrameLoader();
   NS_ENSURE_STATE(mFrameLoader);
   mFrameLoader->SetRemoteBrowser(aTabParent);
@@ -145,20 +147,21 @@ nsresult nsGenericHTMLFrameElement::Crea
 }
 
 NS_IMETHODIMP_(already_AddRefed<nsFrameLoader>)
 nsGenericHTMLFrameElement::GetFrameLoader() {
   RefPtr<nsFrameLoader> loader = mFrameLoader;
   return loader.forget();
 }
 
-void nsGenericHTMLFrameElement::PresetOpenerWindow(mozIDOMWindowProxy* aWindow,
-                                                   ErrorResult& aRv) {
+void nsGenericHTMLFrameElement::PresetOpenerWindow(
+    const Nullable<WindowProxyHolder>& aOpenerWindow, ErrorResult& aRv) {
   MOZ_ASSERT(!mFrameLoader);
-  mOpenerWindow = nsPIDOMWindowOuter::From(aWindow);
+  mOpenerWindow =
+      aOpenerWindow.IsNull() ? nullptr : aOpenerWindow.Value().get();
 }
 
 void nsGenericHTMLFrameElement::InternalSetFrameLoader(
     nsFrameLoader* aNewFrameLoader) {
   mFrameLoader = aNewFrameLoader;
 }
 
 void nsGenericHTMLFrameElement::SwapFrameLoaders(
--- a/dom/html/nsGenericHTMLFrameElement.h
+++ b/dom/html/nsGenericHTMLFrameElement.h
@@ -14,18 +14,21 @@
 #include "nsFrameLoader.h"
 #include "nsGenericHTMLElement.h"
 #include "nsIDOMEventListener.h"
 #include "nsIFrameLoaderOwner.h"
 #include "nsIMozBrowserFrame.h"
 
 namespace mozilla {
 namespace dom {
+template <typename>
+struct Nullable;
+class WindowProxyHolder;
 class XULFrameElement;
-}
+}  // namespace dom
 }  // namespace mozilla
 
 #define NS_GENERICHTMLFRAMEELEMENT_IID               \
   {                                                  \
     0x8190db72, 0xdab0, 0x4d72, {                    \
       0x94, 0x26, 0x87, 0x5f, 0x5a, 0x8a, 0x2a, 0xe5 \
     }                                                \
   }
@@ -79,17 +82,18 @@ class nsGenericHTMLFrameElement : public
                         mozilla::ErrorResult& aError);
 
   void SwapFrameLoaders(mozilla::dom::XULFrameElement& aOtherLoaderOwner,
                         mozilla::ErrorResult& aError);
 
   void SwapFrameLoaders(nsIFrameLoaderOwner* aOtherLoaderOwner,
                         mozilla::ErrorResult& rv);
 
-  void PresetOpenerWindow(mozIDOMWindowProxy* aOpenerWindow,
+  void PresetOpenerWindow(const mozilla::dom::Nullable<
+                              mozilla::dom::WindowProxyHolder>& aOpenerWindow,
                           mozilla::ErrorResult& aRv);
 
   static void InitStatics();
   static bool BrowserFramesEnabled();
 
   /**
    * Helper method to map a HTML 'scrolling' attribute value to a nsIScrollable
    * enum value.  scrolling="no" (and its synonyms) maps to
@@ -107,29 +111,29 @@ class nsGenericHTMLFrameElement : public
  protected:
   virtual ~nsGenericHTMLFrameElement();
 
   // This doesn't really ensure a frame loader in all cases, only when
   // it makes sense.
   void EnsureFrameLoader();
   void LoadSrc();
   nsIDocument* GetContentDocument(nsIPrincipal& aSubjectPrincipal);
-  already_AddRefed<nsPIDOMWindowOuter> GetContentWindow();
+  mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> GetContentWindow();
 
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
                                 const nsAttrValue* aValue,
                                 const nsAttrValue* aOldValue,
                                 nsIPrincipal* aSubjectPrincipal,
                                 bool aNotify) override;
   virtual nsresult OnAttrSetButNotChanged(int32_t aNamespaceID, nsAtom* aName,
                                           const nsAttrValueOrString& aValue,
                                           bool aNotify) override;
 
   RefPtr<nsFrameLoader> mFrameLoader;
-  nsCOMPtr<nsPIDOMWindowOuter> mOpenerWindow;
+  RefPtr<mozilla::dom::BrowsingContext> mOpenerWindow;
 
   nsCOMPtr<nsIPrincipal> mSrcTriggeringPrincipal;
 
   /**
    * True if we have already loaded the frame's original src
    */
   bool mSrcLoadHappened;
 
@@ -160,14 +164,16 @@ class nsGenericHTMLFrameElement : public
    * @param aName the localname of the attribute being set
    * @param aValue the value being set or null if the value is being unset
    * @param aNotify Whether we plan to notify document observers.
    */
   void AfterMaybeChangeAttr(int32_t aNamespaceID, nsAtom* aName,
                             const nsAttrValueOrString* aValue,
                             nsIPrincipal* aMaybeScriptedPrincipal,
                             bool aNotify);
+
+  mozilla::dom::BrowsingContext* GetContentWindowInternal();
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsGenericHTMLFrameElement,
                               NS_GENERICHTMLFRAMEELEMENT_IID)
 
 #endif  // nsGenericHTMLFrameElement_h
--- a/dom/html/nsHTMLDocument.cpp
+++ b/dom/html/nsHTMLDocument.cpp
@@ -1133,17 +1133,17 @@ void nsHTMLDocument::SetCookie(const nsA
       }
     }
 
     NS_ConvertUTF16toUTF8 cookie(aCookie);
     service->SetCookieString(codebaseURI, nullptr, cookie.get(), channel);
   }
 }
 
-already_AddRefed<nsPIDOMWindowOuter> nsHTMLDocument::Open(
+mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> nsHTMLDocument::Open(
     JSContext* /* unused */, const nsAString& aURL, const nsAString& aName,
     const nsAString& aFeatures, bool aReplace, ErrorResult& rv) {
   MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
              "XOW should have caught this!");
 
   nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
   if (!window) {
     rv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
@@ -1154,17 +1154,20 @@ already_AddRefed<nsPIDOMWindowOuter> nsH
   if (!outer) {
     rv.Throw(NS_ERROR_NOT_INITIALIZED);
     return nullptr;
   }
   RefPtr<nsGlobalWindowOuter> win = nsGlobalWindowOuter::Cast(outer);
   nsCOMPtr<nsPIDOMWindowOuter> newWindow;
   // XXXbz We ignore aReplace for now.
   rv = win->OpenJS(aURL, aName, aFeatures, getter_AddRefs(newWindow));
-  return newWindow.forget();
+  if (!newWindow) {
+    return nullptr;
+  }
+  return WindowProxyHolder(newWindow->GetBrowsingContext());
 }
 
 already_AddRefed<nsIDocument> nsHTMLDocument::Open(
     JSContext* cx, const Optional<nsAString>& /* unused */,
     const nsAString& aReplace, ErrorResult& aError) {
   // Implements the "When called with two arguments (or fewer)" steps here:
   // https://html.spec.whatwg.org/multipage/webappapis.html#opening-the-input-stream
 
--- a/dom/html/nsHTMLDocument.h
+++ b/dom/html/nsHTMLDocument.h
@@ -25,16 +25,19 @@ class nsIURI;
 class nsIDocShell;
 class nsICachingChannel;
 class nsIWyciwygChannel;
 class nsILoadGroup;
 
 namespace mozilla {
 namespace dom {
 class HTMLAllCollection;
+template <typename T>
+struct Nullable;
+class WindowProxyHolder;
 }  // namespace dom
 }  // namespace mozilla
 
 class nsHTMLDocument : public nsIDocument, public nsIHTMLDocument {
   typedef mozilla::net::ReferrerPolicy ReferrerPolicy;
 
  public:
   using nsIDocument::GetPlugins;
@@ -145,17 +148,17 @@ class nsHTMLDocument : public nsIDocumen
     if ((aFound = ResolveName(cx, aName, &v, rv))) {
       aRetval.set(v.toObjectOrNull());
     }
   }
   void GetSupportedNames(nsTArray<nsString>& aNames);
   already_AddRefed<nsIDocument> Open(
       JSContext* cx, const mozilla::dom::Optional<nsAString>& /* unused */,
       const nsAString& aReplace, mozilla::ErrorResult& aError);
-  already_AddRefed<nsPIDOMWindowOuter> Open(
+  mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> Open(
       JSContext* cx, const nsAString& aURL, const nsAString& aName,
       const nsAString& aFeatures, bool aReplace, mozilla::ErrorResult& rv);
   void Close(mozilla::ErrorResult& rv);
   void Write(JSContext* cx, const mozilla::dom::Sequence<nsString>& aText,
              mozilla::ErrorResult& rv);
   void Writeln(JSContext* cx, const mozilla::dom::Sequence<nsString>& aText,
                mozilla::ErrorResult& rv);
   void GetDesignMode(nsAString& aDesignMode);
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -40,16 +40,17 @@
 #include "mozilla/dom/DocGroup.h"
 #include "mozilla/dom/ExternalHelperAppChild.h"
 #include "mozilla/dom/FileCreatorHelper.h"
 #include "mozilla/dom/GetFilesHelper.h"
 #include "mozilla/dom/IPCBlobUtils.h"
 #include "mozilla/dom/LSObject.h"
 #include "mozilla/dom/MemoryReportRequest.h"
 #include "mozilla/dom/PLoginReputationChild.h"
+#include "mozilla/dom/PostMessageEvent.h"
 #include "mozilla/dom/PushNotifier.h"
 #include "mozilla/dom/RemoteWorkerService.h"
 #include "mozilla/dom/ServiceWorkerManager.h"
 #include "mozilla/dom/TabGroup.h"
 #include "mozilla/dom/nsIContentChild.h"
 #include "mozilla/dom/URLClassifierChild.h"
 #include "mozilla/dom/WorkerDebugger.h"
 #include "mozilla/dom/WorkerDebuggerManager.h"
@@ -3484,16 +3485,105 @@ PContentChild::Result ContentChild::OnMe
     MOZ_ASSERT(!aMsg.is_reply());
 
     LSObject::OnSyncMessageHandled();
   }
 
   return result;
 }
 
+mozilla::ipc::IPCResult ContentChild::RecvWindowClose(
+    const BrowsingContextId& aContextId, const bool& aTrustedCaller) {
+  RefPtr<BrowsingContext> bc = BrowsingContext::Get(aContextId);
+  if (!bc) {
+    MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug,
+            ("ChildIPC: Trying to send a message to dead or detached context "
+             "0x%08" PRIx64,
+             (uint64_t)aContextId));
+    return IPC_OK();
+  }
+
+  nsCOMPtr<nsPIDOMWindowOuter> window = bc->GetDOMWindow();
+  nsGlobalWindowOuter::Cast(window)->CloseOuter(aTrustedCaller);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult ContentChild::RecvWindowFocus(
+    const BrowsingContextId& aContextId) {
+  RefPtr<BrowsingContext> bc = BrowsingContext::Get(aContextId);
+  if (!bc) {
+    MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug,
+            ("ChildIPC: Trying to send a message to dead or detached context "
+             "0x%08" PRIx64,
+             (uint64_t)aContextId));
+    return IPC_OK();
+  }
+
+  nsCOMPtr<nsPIDOMWindowOuter> window = bc->GetDOMWindow();
+  nsGlobalWindowOuter::Cast(window)->FocusOuter();
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult ContentChild::RecvWindowBlur(
+    const BrowsingContextId& aContextId) {
+  RefPtr<BrowsingContext> bc = BrowsingContext::Get(aContextId);
+  if (!bc) {
+    MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug,
+            ("ChildIPC: Trying to send a message to dead or detached context "
+             "0x%08" PRIx64,
+             (uint64_t)aContextId));
+    return IPC_OK();
+  }
+
+  nsCOMPtr<nsPIDOMWindowOuter> window = bc->GetDOMWindow();
+  nsGlobalWindowOuter::Cast(window)->BlurOuter();
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult ContentChild::RecvWindowPostMessage(
+    const BrowsingContextId& aContextId, const ClonedMessageData& aMessage,
+    const PostMessageData& aData) {
+  RefPtr<BrowsingContext> bc = BrowsingContext::Get(aContextId);
+  if (!bc) {
+    MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug,
+            ("ChildIPC: Trying to send a message to dead or detached context "
+             "0x%08" PRIx64,
+             (uint64_t)aContextId));
+    return IPC_OK();
+  }
+
+  RefPtr<nsGlobalWindowOuter> window =
+      nsGlobalWindowOuter::Cast(bc->GetDOMWindow());
+  nsCOMPtr<nsIPrincipal> providedPrincipal;
+  if (!window->GetPrincipalForPostMessage(
+          aData.targetOrigin(), aData.targetOriginURI(),
+          aData.callerPrincipal(), *aData.subjectPrincipal(),
+          getter_AddRefs(providedPrincipal))) {
+    return IPC_OK();
+  }
+
+  RefPtr<BrowsingContext> sourceBc = BrowsingContext::Get(aData.source());
+  if (!sourceBc) {
+    MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug,
+            ("ChildIPC: Trying to use a dead or detached context 0x%08" PRIx64,
+             (uint64_t)aData.source()));
+    return IPC_OK();
+  }
+
+  // Create and asynchronously dispatch a runnable which will handle actual DOM
+  // event creation and dispatch.
+  RefPtr<PostMessageEvent> event = new PostMessageEvent(
+      sourceBc, aData.origin(), window, providedPrincipal,
+      aData.callerDocumentURI(), aData.isFromPrivateWindow());
+  event->UnpackFrom(aMessage);
+
+  window->Dispatch(TaskCategory::Other, event.forget());
+  return IPC_OK();
+}
+
 }  // namespace dom
 
 #if defined(__OpenBSD__) && defined(MOZ_CONTENT_SANDBOX)
 #include <unistd.h>
 
 static LazyLogModule sPledgeLog("SandboxPledge");
 
 bool StartOpenBSDSandbox(GeckoProcessType type) {
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -721,16 +721,26 @@ class ContentChild final : public PConte
   virtual already_AddRefed<nsIEventTarget> GetConstructedEventTarget(
       const Message& aMsg) override;
 
   virtual already_AddRefed<nsIEventTarget> GetSpecificMessageEventTarget(
       const Message& aMsg) override;
 
   virtual void OnChannelReceivedMessage(const Message& aMsg) override;
 
+  virtual mozilla::ipc::IPCResult RecvWindowClose(
+      const BrowsingContextId& aContextId, const bool& aTrustedCaller) override;
+  virtual mozilla::ipc::IPCResult RecvWindowFocus(
+      const BrowsingContextId& aContextId) override;
+  virtual mozilla::ipc::IPCResult RecvWindowBlur(
+      const BrowsingContextId& aContextId) override;
+  virtual mozilla::ipc::IPCResult RecvWindowPostMessage(
+      const BrowsingContextId& aContextId, const ClonedMessageData& aMessage,
+      const PostMessageData& aData) override;
+
 #ifdef NIGHTLY_BUILD
   virtual PContentChild::Result OnMessageReceived(const Message& aMsg) override;
 #else
   using PContentChild::OnMessageReceived;
 #endif
 
   virtual PContentChild::Result OnMessageReceived(const Message& aMsg,
                                                   Message*& aReply) override;
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -5122,29 +5122,16 @@ mozilla::ipc::IPCResult ContentParent::R
   RefPtr<IHandlerControl> proxy(aHandlerControl.Get());
   a11y::AccessibleWrap::SetHandlerControl(aPid, std::move(proxy));
   return IPC_OK();
 #else
   return IPC_FAIL_NO_REASON(this);
 #endif
 }
 
-}  // namespace dom
-}  // namespace mozilla
-
-NS_IMPL_ISUPPORTS(ParentIdleListener, nsIObserver)
-
-NS_IMETHODIMP
-ParentIdleListener::Observe(nsISupports*, const char* aTopic,
-                            const char16_t* aData) {
-  mozilla::Unused << mParent->SendNotifyIdleObserver(
-      mObserver, nsDependentCString(aTopic), nsDependentString(aData));
-  return NS_OK;
-}
-
 bool ContentParent::HandleWindowsMessages(const Message& aMsg) const {
   MOZ_ASSERT(aMsg.is_sync());
 
   // a11y messages can be triggered by windows messages, which means if we
   // allow handling windows messages while we wait for the response to a sync
   // a11y message we can reenter the ipc message sending code.
   if (a11y::PDocAccessible::PDocAccessibleStart < aMsg.type() &&
       a11y::PDocAccessible::PDocAccessibleEnd > aMsg.type()) {
@@ -5772,8 +5759,105 @@ void ContentParent::UnregisterRemoveWork
       !ShouldKeepProcessAlive() && !TryToRecycle()) {
     // In the case of normal shutdown, send a shutdown message to child to
     // allow it to perform shutdown tasks.
     MessageLoop::current()->PostTask(NewRunnableMethod<ShutDownMethod>(
         "dom::ContentParent::ShutDownProcess", this,
         &ContentParent::ShutDownProcess, SEND_SHUTDOWN_MESSAGE));
   }
 }
+
+mozilla::ipc::IPCResult ContentParent::RecvWindowClose(
+    const BrowsingContextId& aContextId, const bool& aTrustedCaller) {
+  RefPtr<ChromeBrowsingContext> bc = ChromeBrowsingContext::Get(aContextId);
+  if (!bc) {
+    MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug,
+            ("ParentIPC: Trying to send a message to dead or detached context "
+             "0x%08" PRIx64,
+             (uint64_t)aContextId));
+    return IPC_OK();
+  }
+
+  // FIXME Need to check that the sending process has access to the unit of
+  // related
+  //       browsing contexts of bc.
+
+  ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
+  ContentParent* cp =
+      cpm->GetContentProcessById(ContentParentId(bc->OwnerProcessId()));
+  Unused << cp->SendWindowClose(aContextId, aTrustedCaller);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult ContentParent::RecvWindowFocus(
+    const BrowsingContextId& aContextId) {
+  RefPtr<ChromeBrowsingContext> bc = ChromeBrowsingContext::Get(aContextId);
+  if (!bc) {
+    MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug,
+            ("ParentIPC: Trying to send a message to dead or detached context "
+             "0x%08" PRIx64,
+             (uint64_t)aContextId));
+    return IPC_OK();
+  }
+
+  ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
+  ContentParent* cp =
+      cpm->GetContentProcessById(ContentParentId(bc->OwnerProcessId()));
+  Unused << cp->SendWindowFocus(aContextId);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult ContentParent::RecvWindowBlur(
+    const BrowsingContextId& aContextId) {
+  RefPtr<ChromeBrowsingContext> bc = ChromeBrowsingContext::Get(aContextId);
+  if (!bc) {
+    MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug,
+            ("ParentIPC: Trying to send a message to dead or detached context "
+             "0x%08" PRIx64,
+             (uint64_t)aContextId));
+    return IPC_OK();
+  }
+
+  ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
+  ContentParent* cp =
+      cpm->GetContentProcessById(ContentParentId(bc->OwnerProcessId()));
+  Unused << cp->SendWindowBlur(aContextId);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult ContentParent::RecvWindowPostMessage(
+    const BrowsingContextId& aContextId, const ClonedMessageData& aMessage,
+    const PostMessageData& aData) {
+  RefPtr<ChromeBrowsingContext> bc = ChromeBrowsingContext::Get(aContextId);
+  if (!bc) {
+    MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug,
+            ("ParentIPC: Trying to send a message to dead or detached context "
+             "0x%08" PRIx64,
+             (uint64_t)aContextId));
+    return IPC_OK();
+  }
+
+  ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
+  ContentParent* cp =
+      cpm->GetContentProcessById(ContentParentId(bc->OwnerProcessId()));
+  StructuredCloneData messageFromChild;
+  UnpackClonedMessageDataForParent(aMessage, messageFromChild);
+  ClonedMessageData message;
+  if (!BuildClonedMessageDataForParent(cp, messageFromChild, message)) {
+    // FIXME Logging?
+    return IPC_OK();
+  }
+  Unused << cp->SendWindowPostMessage(aContextId, message, aData);
+  return IPC_OK();
+}
+
+}  // namespace dom
+}  // namespace mozilla
+
+NS_IMPL_ISUPPORTS(ParentIdleListener, nsIObserver)
+
+NS_IMETHODIMP
+ParentIdleListener::Observe(nsISupports*, const char* aTopic,
+                            const char16_t* aData) {
+  mozilla::Unused << mParent->SendNotifyIdleObserver(
+      mObserver, nsDependentCString(aTopic), nsDependentString(aData));
+  return NS_OK;
+}
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -632,16 +632,26 @@ class ContentParent final : public PCont
 
   virtual mozilla::ipc::IPCResult RecvDetachBrowsingContext(
       const BrowsingContextId& aContextId, const bool& aMoveToBFCache) override;
 
   virtual mozilla::ipc::IPCResult RecvSetOpenerBrowsingContext(
       const BrowsingContextId& aContextId,
       const BrowsingContextId& aOpenerContextId) override;
 
+  virtual mozilla::ipc::IPCResult RecvWindowClose(
+      const BrowsingContextId& aContextId, const bool& aTrustedCaller) override;
+  virtual mozilla::ipc::IPCResult RecvWindowFocus(
+      const BrowsingContextId& aContextId) override;
+  virtual mozilla::ipc::IPCResult RecvWindowBlur(
+      const BrowsingContextId& aContextId) override;
+  virtual mozilla::ipc::IPCResult RecvWindowPostMessage(
+      const BrowsingContextId& aContextId, const ClonedMessageData& aMessage,
+      const PostMessageData& aData) override;
+
  protected:
   void OnChannelConnected(int32_t pid) override;
 
   virtual void ActorDestroy(ActorDestroyReason why) override;
 
   bool ShouldContinueFromReplyTimeout() override;
 
   void OnVarChanged(const GfxVarUpdate& aVar) override;
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -317,16 +317,28 @@ struct NotificationEventData
     nsString lang;
     nsString body;
     nsString tag;
     nsString icon;
     nsString data;
     nsString behavior;
 };
 
+struct PostMessageData
+{
+    BrowsingContextId source;
+    nsString origin;
+    nsString targetOrigin;
+    nsIURI targetOriginURI;
+    nsIPrincipal callerPrincipal;
+    nsIPrincipal subjectPrincipal;
+    nsIURI callerDocumentURI;
+    bool isFromPrivateWindow;
+};
+
 /**
  * The PContent protocol is a top-level protocol between the UI process
  * and a content process. There is exactly one PContentParent/PContentChild pair
  * for each content process.
  */
 nested(upto inside_cpow) sync protocol PContent
 {
     manages PBrowser;
@@ -1252,12 +1264,19 @@ both:
      async AsyncMessage(nsString aMessage, CpowEntry[] aCpows,
                         Principal aPrincipal, ClonedMessageData aData);
 
     /**
      * Notify `push-subscription-modified` observers in the parent and child.
      */
     async NotifyPushSubscriptionModifiedObservers(nsCString scope,
                                                   Principal principal);
+
+    async WindowClose(BrowsingContextId aContextId,
+                      bool aTrustedCaller);
+    async WindowFocus(BrowsingContextId aContextId);
+    async WindowBlur(BrowsingContextId aContextId);
+    async WindowPostMessage(BrowsingContextId aContextId, ClonedMessageData aMessage,
+                            PostMessageData aData);
 };
 
 }
 }
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -20,17 +20,19 @@
 #include "mozilla/BrowserElementParent.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/dom/DataTransfer.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/indexedDB/PIndexedDBPermissionRequestChild.h"
 #include "mozilla/dom/MessageManagerBinding.h"
 #include "mozilla/dom/MouseEventBinding.h"
+#include "mozilla/dom/Nullable.h"
 #include "mozilla/dom/PaymentRequestChild.h"
+#include "mozilla/dom/WindowProxyHolder.h"
 #include "mozilla/gfx/CrossProcessPaint.h"
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "mozilla/layers/APZChild.h"
 #include "mozilla/layers/APZCCallbackHelper.h"
 #include "mozilla/layers/APZCTreeManagerChild.h"
 #include "mozilla/layers/APZEventState.h"
 #include "mozilla/layers/ContentProcessController.h"
@@ -3254,25 +3256,23 @@ void TabChildMessageManager::MarkForCC()
   }
   EventListenerManager* elm = GetExistingListenerManager();
   if (elm) {
     elm->MarkForCC();
   }
   MessageManagerGlobal::MarkForCC();
 }
 
-already_AddRefed<nsPIDOMWindowOuter> TabChildMessageManager::GetContent(
+Nullable<WindowProxyHolder> TabChildMessageManager::GetContent(
     ErrorResult& aError) {
-  if (!mTabChild) {
-    aError.Throw(NS_ERROR_NULL_POINTER);
+  nsCOMPtr<nsIDocShell> docShell = GetDocShell(aError);
+  if (!docShell) {
     return nullptr;
   }
-  nsCOMPtr<nsPIDOMWindowOuter> window =
-      do_GetInterface(mTabChild->WebNavigation());
-  return window.forget();
+  return WindowProxyHolder(nsDocShell::Cast(docShell)->GetBrowsingContext());
 }
 
 already_AddRefed<nsIDocShell> TabChildMessageManager::GetDocShell(
     ErrorResult& aError) {
   if (!mTabChild) {
     aError.Throw(NS_ERROR_NULL_POINTER);
     return nullptr;
   }
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -89,18 +89,17 @@ class TabChildMessageManager : public Co
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TabChildMessageManager,
                                            DOMEventTargetHelper)
 
   void MarkForCC();
 
   JSObject* WrapObject(JSContext* aCx,
                        JS::Handle<JSObject*> aGivenProto) override;
 
-  virtual already_AddRefed<nsPIDOMWindowOuter> GetContent(
-      ErrorResult& aError) override;
+  virtual Nullable<WindowProxyHolder> GetContent(ErrorResult& aError) override;
   virtual already_AddRefed<nsIDocShell> GetDocShell(
       ErrorResult& aError) override;
   virtual already_AddRefed<nsIEventTarget> GetTabEventTarget() override;
   virtual uint64_t ChromeOuterWindowID() override;
 
   NS_FORWARD_SAFE_NSIMESSAGESENDER(mMessageManager)
 
   void GetEventTargetParent(EventChainPreVisitor& aVisitor) override {
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -1767,17 +1767,17 @@ class GetUserMediaRunnableWrapper : publ
  * EnumerateRawDevices - Enumerate a list of audio & video devices that
  * satisfy passed-in constraints. List contains raw id's.
  */
 
 RefPtr<MediaManager::MgrPromise> MediaManager::EnumerateRawDevices(
     uint64_t aWindowId, MediaSourceEnum aVideoInputType,
     MediaSourceEnum aAudioInputType, MediaSinkEnum aAudioOutputType,
     DeviceEnumerationType aVideoInputEnumType,
-    DeviceEnumerationType aAudioInputEnumType,
+    DeviceEnumerationType aAudioInputEnumType, bool aForceNoPermRequest,
     const RefPtr<MediaDeviceSetRefCnt>& aOutDevices) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aVideoInputType != MediaSourceEnum::Other ||
              aAudioInputType != MediaSourceEnum::Other ||
              aAudioOutputType != MediaSinkEnum::Other);
   // Since the enums can take one of several values, the following asserts rely
   // on short circuting behavior. E.g. aVideoInputEnumType != Fake will be true
   // if the requested device is not fake and thus the assert will pass. However,
@@ -1874,17 +1874,17 @@ RefPtr<MediaManager::MgrPromise> MediaMa
       realBackend->EnumerateDevices(aWindowId, MediaSourceEnum::Other,
                                     MediaSinkEnum::Speaker, &outputs);
       aOutDevices->AppendElements(outputs);
     }
 
     holder->Resolve(false, __func__);
   });
 
-  if (realDeviceRequested &&
+  if (realDeviceRequested && aForceNoPermRequest &&
       Preferences::GetBool("media.navigator.permission.device", false)) {
     // Need to ask permission to retrieve list of all devices;
     // notify frontend observer and wait for callback notification to post task.
     const char16_t* const type =
         (aVideoInputType != MediaSourceEnum::Camera)
             ? u"audio"
             : (aAudioInputType != MediaSourceEnum::Microphone) ? u"video"
                                                                : u"all";
@@ -2193,17 +2193,17 @@ void MediaManager::OnDeviceChange() {
         // On some Windows machine, if we call EnumerateRawDevices immediately
         // after receiving devicechange event, sometimes we would get outdated
         // devices list.
         PR_Sleep(PR_MillisecondsToInterval(100));
         auto devices = MakeRefPtr<MediaDeviceSetRefCnt>();
         self->EnumerateRawDevices(
                 0, MediaSourceEnum::Camera, MediaSourceEnum::Microphone,
                 MediaSinkEnum::Speaker, DeviceEnumerationType::Normal,
-                DeviceEnumerationType::Normal, devices)
+                DeviceEnumerationType::Normal, false, devices)
             ->Then(GetCurrentThreadSerialEventTarget(), __func__,
                    [self, devices](bool) {
                      if (!MediaManager::GetIfExists()) {
                        return;
                      }
 
                      nsTArray<nsString> deviceIDs;
 
@@ -2736,17 +2736,17 @@ RefPtr<MediaManager::StreamPromise> Medi
       static_cast<uint8_t>(videoEnumerationType),
       static_cast<uint8_t>(audioEnumerationType),
       askPermission ? "true" : "false");
 
   RefPtr<MediaManager> self = this;
   auto devices = MakeRefPtr<MediaDeviceSetRefCnt>();
   return EnumerateDevicesImpl(windowID, videoType, audioType,
                               MediaSinkEnum::Other, videoEnumerationType,
-                              audioEnumerationType, devices)
+                              audioEnumerationType, true, devices)
       ->Then(
           GetCurrentThreadSerialEventTarget(), __func__,
           [self, windowID, c, windowListener, isChrome, devices](bool) {
             LOG("GetUserMedia: post enumeration promise success callback "
                 "starting");
             // Ensure that our windowID is still good.
             auto* globalWindow =
                 nsGlobalWindowInner::GetInnerWindowWithId(windowID);
@@ -3054,17 +3054,17 @@ already_AddRefed<nsIWritableVariant> Med
   }
   return var.forget();
 }
 
 RefPtr<MediaManager::MgrPromise> MediaManager::EnumerateDevicesImpl(
     uint64_t aWindowId, MediaSourceEnum aVideoInputType,
     MediaSourceEnum aAudioInputType, MediaSinkEnum aAudioOutputType,
     DeviceEnumerationType aVideoInputEnumType,
-    DeviceEnumerationType aAudioInputEnumType,
+    DeviceEnumerationType aAudioInputEnumType, bool aForceNoPermRequest,
     const RefPtr<MediaDeviceSetRefCnt>& aOutDevices) {
   MOZ_ASSERT(NS_IsMainThread());
 
   LOG("%s: aWindowId=%" PRIu64 ", aVideoInputType=%" PRIu8
       ", aAudioInputType=%" PRIu8 ", aVideoInputEnumType=%" PRIu8
       ", aAudioInputEnumType=%" PRIu8,
       __func__, aWindowId, static_cast<uint8_t>(aVideoInputType),
       static_cast<uint8_t>(aAudioInputType),
@@ -3096,30 +3096,31 @@ RefPtr<MediaManager::MgrPromise> MediaMa
   // pass in a lambda to run back on this same thread later once
   // GetPrincipalKey resolves. Needed variables are "captured"
   // (passed by value) safely into the lambda.
   auto originKey = MakeRefPtr<Refcountable<nsCString>>();
   return media::GetPrincipalKey(principalInfo, persist)
       ->Then(
           GetMainThreadSerialEventTarget(), __func__,
           [aWindowId, aVideoInputType, aAudioInputType, aAudioOutputType,
-           aVideoInputEnumType, aAudioInputEnumType, aOutDevices,
-           originKey](const nsCString& aOriginKey) {
+           aVideoInputEnumType, aAudioInputEnumType, aForceNoPermRequest,
+           aOutDevices, originKey](const nsCString& aOriginKey) {
             MOZ_ASSERT(NS_IsMainThread());
             originKey->Assign(aOriginKey);
             MediaManager* mgr = MediaManager::GetIfExists();
             MOZ_ASSERT(mgr);
             if (!mgr->IsWindowStillActive(aWindowId)) {
               return MgrPromise::CreateAndReject(
                   MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError),
                   __func__);
             }
             return mgr->EnumerateRawDevices(
                 aWindowId, aVideoInputType, aAudioInputType, aAudioOutputType,
-                aVideoInputEnumType, aAudioInputEnumType, aOutDevices);
+                aVideoInputEnumType, aAudioInputEnumType, aForceNoPermRequest,
+                aOutDevices);
           },
           [](nsresult rs) {
             NS_WARNING(
                 "EnumerateDevicesImpl failed to get Principal Key. Enumeration "
                 "will not continue.");
             return MgrPromise::CreateAndReject(
                 MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError),
                 __func__);
@@ -3227,17 +3228,17 @@ RefPtr<MediaManager::DevicesPromise> Med
 
   MediaSinkEnum audioOutputType = MediaSinkEnum::Other;
   if (Preferences::GetBool("media.setsinkid.enabled")) {
     audioOutputType = MediaSinkEnum::Speaker;
   }
   auto devices = MakeRefPtr<MediaDeviceSetRefCnt>();
   return EnumerateDevicesImpl(windowId, MediaSourceEnum::Camera,
                               MediaSourceEnum::Microphone, audioOutputType,
-                              videoEnumerationType, audioEnumerationType,
+                              videoEnumerationType, audioEnumerationType, false,
                               devices)
       ->Then(GetCurrentThreadSerialEventTarget(), __func__,
              [windowListener, sourceListener, devices](bool) {
                DebugOnly<bool> rv = windowListener->Remove(sourceListener);
                MOZ_ASSERT(rv);
                return DevicesPromise::CreateAndResolve(devices, __func__);
              },
              [windowListener, sourceListener](RefPtr<MediaMgrError>&& aError) {
@@ -3276,17 +3277,17 @@ RefPtr<SinkInfoPromise> MediaManager::Ge
   auto sourceListener = MakeRefPtr<SourceListener>();
   windowListener->Register(sourceListener);
 
   bool isSecure = aWindow->IsSecureContext();
   auto devices = MakeRefPtr<MediaDeviceSetRefCnt>();
   return EnumerateDevicesImpl(aWindow->WindowID(), MediaSourceEnum::Other,
                               MediaSourceEnum::Other, MediaSinkEnum::Speaker,
                               DeviceEnumerationType::Normal,
-                              DeviceEnumerationType::Normal, devices)
+                              DeviceEnumerationType::Normal, true, devices)
       ->Then(GetCurrentThreadSerialEventTarget(), __func__,
              [aDeviceId, isSecure, devices](bool) {
                for (RefPtr<MediaDevice>& device : *devices) {
                  if (aDeviceId.IsEmpty() && device->mSinkInfo->Preferred()) {
                    return SinkInfoPromise::CreateAndResolve(device->mSinkInfo,
                                                             __func__);
                  }
                  if (device->mID.Equals(aDeviceId)) {
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -286,24 +286,24 @@ class MediaManager final : public nsIMed
     Loopback /* Enumeration should return loopback device(s) (possibly in
              addition to normal devices) */
   };
 
   RefPtr<MgrPromise> EnumerateRawDevices(
       uint64_t aWindowId, dom::MediaSourceEnum aVideoInputType,
       dom::MediaSourceEnum aAudioInputType, MediaSinkEnum aAudioOutputType,
       DeviceEnumerationType aVideoInputEnumType,
-      DeviceEnumerationType aAudioInputEnumType,
+      DeviceEnumerationType aAudioInputEnumType, bool aForceNoPermRequest,
       const RefPtr<MediaDeviceSetRefCnt>& aOutDevices);
 
   RefPtr<MgrPromise> EnumerateDevicesImpl(
       uint64_t aWindowId, dom::MediaSourceEnum aVideoInputType,
       dom::MediaSourceEnum aAudioInputType, MediaSinkEnum aAudioOutputType,
       DeviceEnumerationType aVideoInputEnumType,
-      DeviceEnumerationType aAudioInputEnumType,
+      DeviceEnumerationType aAudioInputEnumType, bool aForceNoPermRequest,
       const RefPtr<MediaDeviceSetRefCnt>& aOutDevices);
 
   RefPtr<BadConstraintsPromise> SelectSettings(
       const dom::MediaStreamConstraints& aConstraints, bool aIsChrome,
       const RefPtr<MediaDeviceSetRefCnt>& aSources);
 
   void GetPref(nsIPrefBranch* aBranch, const char* aPref, const char* aData,
                int32_t* aVal);
--- a/dom/smil/TimeEvent.h
+++ b/dom/smil/TimeEvent.h
@@ -2,18 +2,21 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_TimeEvent_h_
 #define mozilla_dom_TimeEvent_h_
 
+#include "nsDocShell.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/TimeEventBinding.h"
+#include "mozilla/dom/Nullable.h"
+#include "mozilla/dom/WindowProxyHolder.h"
 
 class nsGlobalWindowInner;
 
 namespace mozilla {
 namespace dom {
 
 class TimeEvent final : public Event {
  public:
@@ -29,17 +32,22 @@ class TimeEvent final : public Event {
     return TimeEvent_Binding::Wrap(aCx, this, aGivenProto);
   }
 
   void InitTimeEvent(const nsAString& aType, nsGlobalWindowInner* aView,
                      int32_t aDetail);
 
   int32_t Detail() const { return mDetail; }
 
-  nsPIDOMWindowOuter* GetView() const { return mView; }
+  Nullable<WindowProxyHolder> GetView() const {
+    if (!mView) {
+      return nullptr;
+    }
+    return WindowProxyHolder(mView->GetBrowsingContext());
+  }
 
   TimeEvent* AsTimeEvent() final { return this; }
 
  private:
   ~TimeEvent() {}
 
   nsCOMPtr<nsPIDOMWindowOuter> mView;
   int32_t mDetail;
deleted file mode 100644
--- a/dom/tests/mochitest/bugs/file2_bug504862.html
+++ /dev/null
@@ -1,1 +0,0 @@
-<html><body onload="opener.is(window.dialogArguments, 'my args', 'subsequent dialog document did not get the right arguments.'); close();"></html>
deleted file mode 100644
--- a/dom/tests/mochitest/bugs/file_bug406375.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<script>
-  var w = window.open("about:blank", "", "width=200,height=200,dialog");
-  w.close();
-  window.close();
-</script>
--- a/dom/tests/mochitest/bugs/mochitest.ini
+++ b/dom/tests/mochitest/bugs/mochitest.ini
@@ -5,17 +5,16 @@ support-files =
   bug346659-opener-echoer.html
   bug346659-opener.html
   bug346659-parent-echoer.html
   bug346659-parent.html
   bug458091_child.html
   child_bug260264.html
   devicemotion_inner.html
   devicemotion_outer.html
-  file2_bug504862.html
   file_bug593174_1.html
   file_bug593174_2.html
   file_bug809290_b1.html
   file_bug809290_b2.html
   file_bug809290_c.html
   file_empty.html
   file_window_bar.html
   grandchild_bug260264.html
--- a/dom/webbrowserpersist/WebBrowserPersistLocalDocument.cpp
+++ b/dom/webbrowserpersist/WebBrowserPersistLocalDocument.cpp
@@ -128,17 +128,17 @@ WebBrowserPersistLocalDocument::GetTitle
 NS_IMETHODIMP
 WebBrowserPersistLocalDocument::GetReferrer(nsAString& aReferrer) {
   mDocument->GetReferrer(aReferrer);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 WebBrowserPersistLocalDocument::GetContentDisposition(nsAString& aCD) {
-  nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetDefaultView();
+  nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
   if (NS_WARN_IF(!window)) {
     aCD.SetIsVoid(true);
     return NS_OK;
   }
   nsCOMPtr<nsIDOMWindowUtils> utils =
       nsGlobalWindowOuter::Cast(window)->WindowUtils();
   nsresult rv =
       utils->GetDocumentMetadata(NS_LITERAL_STRING("content-disposition"), aCD);
@@ -171,17 +171,17 @@ WebBrowserPersistLocalDocument::GetPostD
 NS_IMETHODIMP
 WebBrowserPersistLocalDocument::GetPrincipal(nsIPrincipal** aPrincipal) {
   nsCOMPtr<nsIPrincipal> nodePrincipal = mDocument->NodePrincipal();
   nodePrincipal.forget(aPrincipal);
   return NS_OK;
 }
 
 already_AddRefed<nsISHEntry> WebBrowserPersistLocalDocument::GetHistory() {
-  nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetDefaultView();
+  nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
   if (NS_WARN_IF(!window)) {
     return nullptr;
   }
   nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(window);
   if (NS_WARN_IF(!webNav)) {
     return nullptr;
   }
   nsCOMPtr<nsIWebPageDescriptor> desc = do_QueryInterface(webNav);
--- a/dom/webidl/Window.webidl
+++ b/dom/webidl/Window.webidl
@@ -25,33 +25,33 @@ interface nsIDOMWindowUtils;
 
 typedef OfflineResourceList ApplicationCache;
 
 // http://www.whatwg.org/specs/web-apps/current-work/
 [PrimaryGlobal, LegacyUnenumerableNamedProperties, NeedResolve]
 /*sealed*/ interface Window : EventTarget {
   // the current browsing context
   [Unforgeable, Constant, StoreInSlot,
-   CrossOriginReadable] readonly attribute Window window;
+   CrossOriginReadable] readonly attribute WindowProxy window;
   [Replaceable, Constant, StoreInSlot,
-   CrossOriginReadable] readonly attribute Window self;
+   CrossOriginReadable] readonly attribute WindowProxy self;
   [Unforgeable, StoreInSlot, Pure] readonly attribute Document? document;
   [Throws] attribute DOMString name;
-  [PutForwards=href, Unforgeable, BinaryName="getLocation",
-   CrossOriginReadable, CrossOriginWritable] readonly attribute Location location;
+  [PutForwards=href, Unforgeable, CrossOriginReadable,
+   CrossOriginWritable] readonly attribute Location location;
   [Throws] readonly attribute History history;
   readonly attribute CustomElementRegistry customElements;
   [Replaceable, Throws] readonly attribute BarProp locationbar;
   [Replaceable, Throws] readonly attribute BarProp menubar;
   [Replaceable, Throws] readonly attribute BarProp personalbar;
   [Replaceable, Throws] readonly attribute BarProp scrollbars;
   [Replaceable, Throws] readonly attribute BarProp statusbar;
   [Replaceable, Throws] readonly attribute BarProp toolbar;
   [Throws] attribute DOMString status;
-  [Throws, CrossOriginCallable] void close();
+  [Throws, CrossOriginCallable, NeedsCallerType] void close();
   [Throws, CrossOriginReadable] readonly attribute boolean closed;
   [Throws] void stop();
   [Throws, CrossOriginCallable] void focus();
   [Throws, CrossOriginCallable] void blur();
   [Replaceable, Pref="dom.window.event.enabled"] readonly attribute any event;
 
   // other browsing contexts
   [Replaceable, Throws, CrossOriginReadable] readonly attribute WindowProxy frames;
--- a/dom/workers/WorkerDebugger.cpp
+++ b/dom/workers/WorkerDebugger.cpp
@@ -453,16 +453,21 @@ RefPtr<PerformanceInfoPromise> WorkerDeb
         windowID = top->WindowID();
         isTopLevel = outer->IsTopLevelWindow();
       }
     }
   }
 
   // getting the worker URL
   RefPtr<nsIURI> scriptURI = mWorkerPrivate->GetResolvedScriptURI();
+  if (NS_WARN_IF(!scriptURI)) {
+    // This can happen at shutdown, let's stop here.
+    return PerformanceInfoPromise::CreateAndReject(NS_ERROR_FAILURE,
+                                                   __func__);
+  }
   nsCString url = scriptURI->GetSpecOrDefault();
 
   // Workers only produce metrics for a single category -
   // DispatchCategory::Worker. We still return an array of CategoryDispatch so
   // the PerformanceInfo struct is common to all performance counters throughout
   // Firefox.
   FallibleTArray<CategoryDispatch> items;
   uint64_t duration = 0;
--- a/dom/xul/XULFrameElement.cpp
+++ b/dom/xul/XULFrameElement.cpp
@@ -32,66 +32,72 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(XULFrameElement, nsXULElement,
                                              nsIFrameLoaderOwner)
 
 JSObject* XULFrameElement::WrapNode(JSContext* aCx,
                                     JS::Handle<JSObject*> aGivenProto) {
   return XULFrameElement_Binding::Wrap(aCx, this, aGivenProto);
 }
 
-nsIDocShell* XULFrameElement::GetDocShell() {
+nsDocShell* XULFrameElement::GetDocShell() {
   RefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
   return frameLoader ? frameLoader->GetDocShell(IgnoreErrors()) : nullptr;
 }
 
 already_AddRefed<nsIWebNavigation> XULFrameElement::GetWebNavigation() {
   nsCOMPtr<nsIDocShell> docShell = GetDocShell();
   nsCOMPtr<nsIWebNavigation> webnav = do_QueryInterface(docShell);
   return webnav.forget();
 }
 
-already_AddRefed<nsPIDOMWindowOuter> XULFrameElement::GetContentWindow() {
-  nsCOMPtr<nsIDocShell> docShell = GetDocShell();
+Nullable<WindowProxyHolder> XULFrameElement::GetContentWindow() {
+  RefPtr<nsDocShell> docShell = GetDocShell();
   if (docShell) {
-    nsCOMPtr<nsPIDOMWindowOuter> win = docShell->GetWindow();
-    return win.forget();
+    return WindowProxyHolder(docShell->GetWindowProxy());
   }
 
   return nullptr;
 }
 
 nsIDocument* XULFrameElement::GetContentDocument() {
-  nsCOMPtr<nsPIDOMWindowOuter> win = GetContentWindow();
-  return win ? win->GetDoc() : nullptr;
+  nsCOMPtr<nsIDocShell> docShell = GetDocShell();
+  if (docShell) {
+    nsCOMPtr<nsPIDOMWindowOuter> win = docShell->GetWindow();
+    if (win) {
+      return win->GetDoc();
+    }
+  }
+  return nullptr;
 }
 
 void XULFrameElement::LoadSrc() {
   if (!IsInUncomposedDoc() || !OwnerDoc()->GetRootElement()) {
     return;
   }
   RefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
   if (!frameLoader) {
     // Check if we have an opener we need to be setting
-    nsCOMPtr<nsPIDOMWindowOuter> opener = mOpener;
+    RefPtr<BrowsingContext> opener = mOpener;
     if (!opener) {
       // If we are a primary xul-browser, we want to take the opener property!
       nsCOMPtr<nsPIDOMWindowOuter> window = OwnerDoc()->GetWindow();
       if (AttrValueIs(kNameSpaceID_None, nsGkAtoms::primary, nsGkAtoms::_true,
                       eIgnoreCase) &&
           window) {
         opener = window->TakeOpenerForInitialContentBrowser();
       }
     }
     mOpener = nullptr;
 
     // false as the last parameter so that xul:iframe/browser/editor
     // session history handling works like dynamic html:iframes.
     // Usually xul elements are used in chrome, which doesn't have
     // session history at all.
-    mFrameLoader = nsFrameLoader::Create(this, opener, false);
+    mFrameLoader = nsFrameLoader::Create(
+        this, opener ? opener->GetDOMWindow() : nullptr, false);
     if (NS_WARN_IF(!mFrameLoader)) {
       return;
     }
 
     (new AsyncEventDispatcher(this, NS_LITERAL_STRING("XULFrameLoaderCreated"),
                               CanBubble::eYes))
         ->RunDOMEventWhenSafe();
   }
--- a/dom/xul/XULFrameElement.h
+++ b/dom/xul/XULFrameElement.h
@@ -4,54 +4,59 @@
  * 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 XULFrameElement_h__
 #define XULFrameElement_h__
 
 #include "mozilla/Attributes.h"
 #include "mozilla/ErrorResult.h"
+#include "mozilla/dom/Nullable.h"
+#include "mozilla/dom/WindowProxyHolder.h"
 #include "js/TypeDecls.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsWrapperCache.h"
 #include "nsString.h"
 #include "nsXULElement.h"
 
 class nsIWebNavigation;
 class nsFrameLoader;
 
 namespace mozilla {
 namespace dom {
 
+class BrowsingContext;
+
 class XULFrameElement final : public nsXULElement, public nsIFrameLoaderOwner {
  public:
   explicit XULFrameElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
       : nsXULElement(std::move(aNodeInfo)) {}
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(XULFrameElement, nsXULElement)
 
   // XULFrameElement.webidl
-  nsIDocShell* GetDocShell();
+  nsDocShell* GetDocShell();
   already_AddRefed<nsIWebNavigation> GetWebNavigation();
-  already_AddRefed<nsPIDOMWindowOuter> GetContentWindow();
+  Nullable<WindowProxyHolder> GetContentWindow();
   nsIDocument* GetContentDocument();
 
   // nsIFrameLoaderOwner / MozFrameLoaderOwner
   NS_IMETHOD_(already_AddRefed<nsFrameLoader>) GetFrameLoader() override {
     return do_AddRef(mFrameLoader);
   }
 
   NS_IMETHOD_(void)
   InternalSetFrameLoader(nsFrameLoader* aFrameLoader) override {
     mFrameLoader = aFrameLoader;
   }
 
-  void PresetOpenerWindow(mozIDOMWindowProxy* aWindow, ErrorResult& aRv) {
-    mOpener = do_QueryInterface(aWindow);
+  void PresetOpenerWindow(const Nullable<WindowProxyHolder>& aWindow,
+                          ErrorResult& aRv) {
+    mOpener = aWindow.IsNull() ? nullptr : aWindow.Value().get();
   }
 
   void SwapFrameLoaders(mozilla::dom::HTMLIFrameElement& aOtherLoaderOwner,
                         mozilla::ErrorResult& rv);
   void SwapFrameLoaders(XULFrameElement& aOtherLoaderOwner,
                         mozilla::ErrorResult& rv);
   void SwapFrameLoaders(nsIFrameLoaderOwner* aOtherLoaderOwner,
                         mozilla::ErrorResult& rv);
@@ -67,17 +72,17 @@ class XULFrameElement final : public nsX
                                 const nsAttrValue* aOldValue,
                                 nsIPrincipal* aSubjectPrincipal,
                                 bool aNotify) override;
 
  protected:
   virtual ~XULFrameElement() {}
 
   RefPtr<nsFrameLoader> mFrameLoader;
-  nsCOMPtr<nsPIDOMWindowOuter> mOpener;
+  RefPtr<BrowsingContext> mOpener;
 
   JSObject* WrapNode(JSContext* aCx,
                      JS::Handle<JSObject*> aGivenProto) override;
 
   void LoadSrc();
 };
 
 }  // namespace dom
--- a/extensions/cookie/nsCookiePermission.cpp
+++ b/extensions/cookie/nsCookiePermission.cpp
@@ -108,33 +108,39 @@ nsCookiePermission::SetAccess(nsIURI *aU
   //
   return mPermMgr->Add(aURI, kPermissionType, aAccess,
                        nsIPermissionManager::EXPIRE_NEVER, 0);
 }
 
 NS_IMETHODIMP
 nsCookiePermission::CanAccess(nsIPrincipal *aPrincipal,
                               nsCookieAccess *aResult) {
-  // Check this protocol doesn't allow cookies
-  bool hasFlags;
-  nsCOMPtr<nsIURI> uri;
-  aPrincipal->GetURI(getter_AddRefs(uri));
-  nsresult rv = NS_URIChainHasFlags(
-      uri, nsIProtocolHandler::URI_FORBIDS_COOKIE_ACCESS, &hasFlags);
-  if (NS_FAILED(rv) || hasFlags) {
-    *aResult = ACCESS_DENY;
-    return NS_OK;
-  }
-
   // Lazily initialize ourselves
   if (!EnsureInitialized()) return NS_ERROR_UNEXPECTED;
 
   // finally, check with permission manager...
-  rv = mPermMgr->TestPermissionFromPrincipal(aPrincipal, kPermissionType,
-                                             (uint32_t *)aResult);
+  nsresult rv = mPermMgr->TestPermissionFromPrincipal(
+      aPrincipal, kPermissionType, (uint32_t *)aResult);
+  if (NS_SUCCEEDED(rv)) {
+    if (*aResult == nsICookiePermission::ACCESS_SESSION) {
+      *aResult = nsICookiePermission::ACCESS_ALLOW;
+    }
+  }
+
+  return rv;
+}
+
+NS_IMETHODIMP
+nsCookiePermission::CanAccessURI(nsIURI *aURI, nsCookieAccess *aResult) {
+  // Lazily initialize ourselves
+  if (!EnsureInitialized()) return NS_ERROR_UNEXPECTED;
+
+  // finally, check with permission manager...
+  nsresult rv =
+      mPermMgr->TestPermission(aURI, kPermissionType, (uint32_t *)aResult);
   if (NS_SUCCEEDED(rv)) {
     if (*aResult == nsICookiePermission::ACCESS_SESSION) {
       *aResult = nsICookiePermission::ACCESS_ALLOW;
     }
   }
 
   return rv;
 }
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -964,16 +964,17 @@ void XPCJSRuntime::CustomGCCallback(JSGC
   // once for each compartment that is being swept.
   CompartmentPrivate* xpcComp = CompartmentPrivate::Get(comp);
   if (xpcComp) {
     xpcComp->UpdateWeakPointersAfterGC();
   }
 }
 
 void CompartmentPrivate::UpdateWeakPointersAfterGC() {
+  mRemoteProxies.sweep();
   mWrappedJSMap->UpdateWeakPointersAfterGC();
 }
 
 void XPCJSRuntime::CustomOutOfMemoryCallback() {
   if (!Preferences::GetBool("memory.dump_reports_on_oom")) {
     return;
   }
 
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -2753,18 +2753,35 @@ class CompartmentPrivate {
 
   JSObject2WrappedJSMap* GetWrappedJSMap() const { return mWrappedJSMap; }
   void UpdateWeakPointersAfterGC();
 
   void SystemIsBeingShutDown();
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
 
+  struct SweepPolicy {
+    static bool needsSweep(const void* /* unused */,
+                           JS::Heap<JSObject*>* value) {
+      return JS::GCPolicy<JS::Heap<JSObject*>>::needsSweep(value);
+    }
+  };
+
+  typedef JS::GCHashMap<const void*, JS::Heap<JSObject*>,
+                        mozilla::PointerHasher<const void*>,
+                        js::SystemAllocPolicy, SweepPolicy>
+      RemoteProxyMap;
+  RemoteProxyMap& GetRemoteProxyMap() { return mRemoteProxies; }
+
  private:
   JSObject2WrappedJSMap* mWrappedJSMap;
+
+  // Cache holding proxy objects for Window objects (and their Location oject)
+  // that are loaded in a different process.
+  RemoteProxyMap mRemoteProxies;
 };
 
 bool IsUniversalXPConnectEnabled(JS::Compartment* compartment);
 bool IsUniversalXPConnectEnabled(JSContext* cx);
 bool EnableUniversalXPConnect(JSContext* cx);
 
 inline void CrashIfNotInAutomation() { MOZ_RELEASE_ASSERT(IsInAutomation()); }
 
--- a/js/xpconnect/src/xpcpublic.h
+++ b/js/xpconnect/src/xpcpublic.h
@@ -718,16 +718,25 @@ class JSStackFrameBase {
  public:
   virtual void Clear() = 0;
 };
 
 void RegisterJSStackFrame(JS::Realm* aRealm, JSStackFrameBase* aStackFrame);
 void UnregisterJSStackFrame(JS::Realm* aRealm, JSStackFrameBase* aStackFrame);
 void NukeJSStackFrames(JS::Realm* aRealm);
 
+// Check whether the given jsid is a property name (string or symbol) whose
+// value can be gotten cross-origin.  Cross-origin gets always return undefined
+// as the value, unless the Xray actually provides a different value.
+bool IsCrossOriginWhitelistedProp(JSContext* cx, JS::HandleId id);
+
+// Appends to props the jsids for property names (strings or symbols) whose
+// value can be gotten cross-origin.
+bool AppendCrossOriginWhitelistedPropNames(JSContext* cx,
+                                           JS::AutoIdVector& props);
 }  // namespace xpc
 
 namespace mozilla {
 namespace dom {
 
 /**
  * A test for whether WebIDL methods that should only be visible to
  * chrome or XBL scopes should be exposed.
--- a/js/xpconnect/wrappers/FilteringWrapper.cpp
+++ b/js/xpconnect/wrappers/FilteringWrapper.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "FilteringWrapper.h"
 #include "AccessCheck.h"
 #include "ChromeObjectWrapper.h"
 #include "XrayWrapper.h"
 #include "nsJSUtils.h"
 #include "mozilla/ErrorResult.h"
+#include "xpcpublic.h"
 
 #include "jsapi.h"
 #include "js/Symbol.h"
 
 using namespace JS;
 using namespace js;
 
 namespace xpc {
@@ -38,16 +39,47 @@ static bool IsCrossOriginWhitelistedSymb
   return false;
 }
 
 bool IsCrossOriginWhitelistedProp(JSContext* cx, JS::HandleId id) {
   return id == GetJSIDByIndex(cx, XPCJSContext::IDX_THEN) ||
          IsCrossOriginWhitelistedSymbol(cx, id);
 }
 
+bool AppendCrossOriginWhitelistedPropNames(JSContext* cx,
+                                           JS::AutoIdVector& props) {
+  // Add "then" if it's not already in the list.
+  AutoIdVector thenProp(cx);
+  if (!thenProp.append(GetJSIDByIndex(cx, XPCJSContext::IDX_THEN))) {
+    return false;
+  }
+
+  if (!AppendUnique(cx, props, thenProp)) {
+    return false;
+  }
+
+  // Now add the three symbol-named props cross-origin objects have.
+#ifdef DEBUG
+  for (size_t n = 0; n < props.length(); ++n) {
+    MOZ_ASSERT(!JSID_IS_SYMBOL(props[n]),
+               "Unexpected existing symbol-name prop");
+  }
+#endif
+  if (!props.reserve(props.length() +
+                     ArrayLength(sCrossOriginWhitelistedSymbolCodes))) {
+    return false;
+  }
+
+  for (auto code : sCrossOriginWhitelistedSymbolCodes) {
+    props.infallibleAppend(SYMBOL_TO_JSID(JS::GetWellKnownSymbol(cx, code)));
+  }
+
+  return true;
+}
+
 template <typename Policy>
 static bool Filter(JSContext* cx, HandleObject wrapper, AutoIdVector& props) {
   size_t w = 0;
   RootedId id(cx);
   for (size_t n = 0; n < props.length(); ++n) {
     id = props[n];
     if (Policy::check(cx, wrapper, id, Wrapper::GET) ||
         Policy::check(cx, wrapper, id, Wrapper::SET)) {
@@ -256,43 +288,17 @@ bool CrossOriginXrayWrapper::ownProperty
                                              JS::AutoIdVector& props) const {
   // All properties on cross-origin objects are supposed |own|, despite what
   // the underlying native object may report. Override the inherited trap to
   // avoid passing JSITER_OWNONLY as a flag.
   if (!SecurityXrayDOM::getPropertyKeys(cx, wrapper, JSITER_HIDDEN, props)) {
     return false;
   }
 
-  // Add "then" if it's not already in the list.
-  AutoIdVector thenProp(cx);
-  if (!thenProp.append(GetJSIDByIndex(cx, XPCJSContext::IDX_THEN))) {
-    return false;
-  }
-
-  if (!AppendUnique(cx, props, thenProp)) {
-    return false;
-  }
-
-  // Now add the three symbol-named props cross-origin objects have.
-#ifdef DEBUG
-  for (size_t n = 0; n < props.length(); ++n) {
-    MOZ_ASSERT(!JSID_IS_SYMBOL(props[n]),
-               "Unexpected existing symbol-name prop");
-  }
-#endif
-  if (!props.reserve(props.length() +
-                     ArrayLength(sCrossOriginWhitelistedSymbolCodes))) {
-    return false;
-  }
-
-  for (auto code : sCrossOriginWhitelistedSymbolCodes) {
-    props.infallibleAppend(SYMBOL_TO_JSID(JS::GetWellKnownSymbol(cx, code)));
-  }
-
-  return true;
+  return AppendCrossOriginWhitelistedPropNames(cx, props);
 }
 
 bool CrossOriginXrayWrapper::defineProperty(JSContext* cx,
                                             JS::Handle<JSObject*> wrapper,
                                             JS::Handle<jsid> id,
                                             JS::Handle<PropertyDescriptor> desc,
                                             JS::ObjectOpResult& result) const {
   AccessCheck::reportCrossOriginDenial(cx, id, NS_LITERAL_CSTRING("define"));
--- a/js/xpconnect/wrappers/FilteringWrapper.h
+++ b/js/xpconnect/wrappers/FilteringWrapper.h
@@ -80,16 +80,11 @@ class CrossOriginXrayWrapper : public Se
       JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<jsid> id,
       JS::MutableHandle<JS::PropertyDescriptor> desc) const override;
 
   virtual bool setPrototype(JSContext* cx, JS::HandleObject wrapper,
                             JS::HandleObject proto,
                             JS::ObjectOpResult& result) const override;
 };
 
-// Check whether the given jsid is a property name (string or symbol) whose
-// value can be gotten cross-origin.  Cross-origin gets always return undefined
-// as the value, unless the Xray actually provides a different value.
-bool IsCrossOriginWhitelistedProp(JSContext* cx, JS::HandleId id);
-
 }  // namespace xpc
 
 #endif /* __FilteringWrapper_h__ */
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -17,16 +17,17 @@
 #include "xpcprivate.h"
 
 #include "jsapi.h"
 #include "nsJSUtils.h"
 #include "nsPrintfCString.h"
 
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/BrowsingContext.h"
 #include "mozilla/dom/WindowBinding.h"
 #include "mozilla/dom/XrayExpandoClass.h"
 #include "nsGlobalWindow.h"
 
 using namespace mozilla::dom;
 using namespace JS;
 using namespace mozilla;
 
@@ -1922,18 +1923,19 @@ bool XrayWrapper<Base, Traits>::getPrope
   // only relevant for CrossOriginXrayWrapper, which calls
   // getPropertyDescriptor from getOwnPropertyDescriptor.
   nsGlobalWindowInner* win = nullptr;
   if (!desc.object() && JSID_IS_STRING(id) && (win = AsWindow(cx, wrapper))) {
     nsAutoJSString name;
     if (!name.init(cx, JSID_TO_STRING(id))) {
       return false;
     }
-    if (nsCOMPtr<nsPIDOMWindowOuter> childDOMWin = win->GetChildWindow(name)) {
-      auto* cwin = nsGlobalWindowOuter::Cast(childDOMWin);
+    RefPtr<BrowsingContext> childDOMWin(win->GetChildWindow(name));
+    if (childDOMWin) {
+      auto* cwin = nsGlobalWindowOuter::Cast(childDOMWin->GetDOMWindow());
       JSObject* childObj = cwin->FastGetGlobalJSObject();
       if (MOZ_UNLIKELY(!childObj)) {
         return xpc::Throw(cx, NS_ERROR_FAILURE);
       }
       ExposeObjectToActiveJS(childObj);
       FillPropertyDescriptor(desc, wrapper, ObjectValue(*childObj),
                              /* readOnly = */ true);
       return JS_WrapPropertyDescriptor(cx, desc);
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -418,23 +418,22 @@ nsresult PeerConnectionImpl::Initialize(
   NS_ENSURE_STATE(mWindow);
 
   PRTime timestamp = PR_Now();
   // Ok if we truncate this.
   char temp[128];
 
   nsAutoCString locationCStr;
 
-  if (RefPtr<Location> location = mWindow->GetLocation()) {
-    nsAutoString locationAStr;
-    res = location->ToString(locationAStr);
-    NS_ENSURE_SUCCESS(res, res);
-
-    CopyUTF16toUTF8(locationAStr, locationCStr);
-  }
+  RefPtr<Location> location = mWindow->Location();
+  nsAutoString locationAStr;
+  res = location->ToString(locationAStr);
+  NS_ENSURE_SUCCESS(res, res);
+
+  CopyUTF16toUTF8(locationAStr, locationCStr);
 
   SprintfLiteral(temp, "%" PRIu64 " (id=%" PRIu64 " url=%s)",
                  static_cast<uint64_t>(timestamp),
                  static_cast<uint64_t>(mWindow ? mWindow->WindowID() : 0),
                  locationCStr.get() ? locationCStr.get() : "NULL");
 
   mName = temp;
 
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/PermissionDelegateTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/PermissionDelegateTest.kt
@@ -45,29 +45,16 @@ class PermissionDelegateTest : BaseSessi
 
         // Media test is relatively resource-intensive. Clean up resources from previous tests
         // first to improve the stability of this test.
         sessionRule.forceGarbageCollection()
 
         mainSession.loadTestPath(HELLO_HTML_PATH)
         mainSession.waitForPageStop()
 
-
-        mainSession.delegateDuringNextWait(object : Callbacks.PermissionDelegate {
-            @AssertCalled(count = 1)
-            override fun onAndroidPermissionsRequest(
-                    session: GeckoSession, permissions: Array<out String>?,
-                    callback: GeckoSession.PermissionDelegate.Callback) {
-                assertThat("Permissions list should be correct",
-                           listOf(*permissions!!), hasItems(Manifest.permission.CAMERA,
-                                                            Manifest.permission.RECORD_AUDIO))
-                callback.grant()
-            }
-        })
-
         val devices = mainSession.waitForJS(
                 "window.navigator.mediaDevices.enumerateDevices()")
 
         assertThat("Device list should contain camera device",
                    devices.asJSList<Any>(), hasItem(hasEntry("kind", "videoinput")))
         assertThat("Device list should contain microphone device",
                    devices.asJSList<Any>(), hasItem(hasEntry("kind", "audioinput")))
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
@@ -1103,27 +1103,29 @@ public class GeckoAppShell
             if (milliseconds.length == 1) {
                 vibrate(milliseconds[0]);
             } else {
                 vibrate(convertIntToLongArray(milliseconds), -1);
             }
         }
     }
 
+    @SuppressLint("MissingPermission")
     @WrapForJNI(calledFrom = "gecko")
     private static void vibrate(long milliseconds) {
         sVibrationEndTime = System.nanoTime() + milliseconds * 1000000;
         sVibrationMaybePlaying = true;
         try {
             vibrator().vibrate(milliseconds);
         } catch (final SecurityException ignore) {
             Log.w(LOGTAG, "No VIBRATE permission");
         }
     }
 
+    @SuppressLint("MissingPermission")
     @WrapForJNI(calledFrom = "gecko")
     private static void vibrate(long[] pattern, int repeat) {
         // If pattern.length is odd, the last element in the pattern is a
         // meaningless delay, so don't include it in vibrationDuration.
         long vibrationDuration = 0;
         int iterLen = pattern.length & ~1;
         for (int i = 0; i < iterLen; i++) {
           vibrationDuration += pattern[i];
@@ -1133,16 +1135,17 @@ public class GeckoAppShell
         sVibrationMaybePlaying = true;
         try {
             vibrator().vibrate(pattern, repeat);
         } catch (final SecurityException ignore) {
             Log.w(LOGTAG, "No VIBRATE permission");
         }
     }
 
+    @SuppressLint("MissingPermission")
     @WrapForJNI(calledFrom = "gecko")
     private static void cancelVibrate() {
         sVibrationMaybePlaying = false;
         sVibrationEndTime = 0;
         try {
             vibrator().cancel();
         } catch (final SecurityException ignore) {
             Log.w(LOGTAG, "No VIBRATE permission");
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoNetworkManager.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoNetworkManager.java
@@ -10,16 +10,17 @@ import org.mozilla.gecko.annotation.Wrap
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.NetworkUtils;
 import org.mozilla.gecko.util.NetworkUtils.ConnectionSubType;
 import org.mozilla.gecko.util.NetworkUtils.ConnectionType;
 import org.mozilla.gecko.util.NetworkUtils.NetworkStatus;
 
+import android.annotation.SuppressLint;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.net.ConnectivityManager;
 import android.net.DhcpInfo;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
@@ -384,36 +385,37 @@ public class GeckoNetworkManager extends
     }
 
     private static int wifiDhcpGatewayAddress(final Context context) {
         if (context == null) {
             return 0;
         }
 
         try {
-            WifiManager mgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+            WifiManager mgr = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
             if (mgr == null) {
                 return 0;
             }
 
-            DhcpInfo d = mgr.getDhcpInfo();
+            @SuppressLint("MissingPermission") DhcpInfo d = mgr.getDhcpInfo();
             if (d == null) {
                 return 0;
             }
 
             return d.gateway;
 
         } catch (Exception ex) {
             // getDhcpInfo() is not documented to require any permissions, but on some devices
             // requires android.permission.ACCESS_WIFI_STATE. Just catch the generic exception
             // here and returning 0. Not logging because this could be noisy.
             return 0;
         }
     }
 
+    @SuppressLint("MissingPermission")
     @Override // BundleEventListener
     /**
      * Handles native messages, not part of the state machine flow.
      */
     public void handleMessage(final String event, final GeckoBundle message,
                               final EventCallback callback) {
         final Context applicationContext = GeckoAppShell.getApplicationContext();
         switch (event) {
@@ -445,17 +447,17 @@ public class GeckoNetworkManager extends
     private void getWifiIPAddress(final EventCallback callback) {
         final WifiManager mgr = (WifiManager) GeckoAppShell.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
 
         if (mgr == null) {
             callback.sendError("Cannot get WifiManager");
             return;
         }
 
-        final WifiInfo info = mgr.getConnectionInfo();
+        @SuppressLint("MissingPermission") final WifiInfo info = mgr.getConnectionInfo();
         if (info == null) {
             callback.sendError("Cannot get connection info");
             return;
         }
 
         int ip = info.getIpAddress();
         if (ip == 0) {
             callback.sendError("Cannot get IPv4 address");
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
@@ -1,42 +1,101 @@
 ---
 layout: geckoview
 ---
 
 <h1> GeckoView API Changelog. </h1>
 
 ## v66
-- Added `@NonNull` or `@Nullable` to all APIs.
+- Added [`@NonNull`][66.1] or [`@Nullable`][66.2] to all APIs.
+
+[66.1]: https://developer.android.com/reference/android/support/annotation/NonNull
+[66.2]: https://developer.android.com/reference/android/support/annotation/Nullable
 
 ## v65
-- Moved `CompositorController`, `DynamicToolbarAnimator`,
-  `OverscrollEdgeEffect`, `PanZoomController` from `org.mozilla.gecko.gfx` to
-  `org.mozilla.geckoview`
-- Added `@UiThread`, `@AnyThread` annotations to all APIs
-- Changed `GeckoRuntime#getLocale` to `GeckoRuntime#getLocales` and related APIs.
-- Merged `org.mozilla.gecko.gfx.LayerSession` into `GeckoSession`
-- Added `GeckoSession.MediaDelegate` and `MediaElement`. This allow monitoring
-  and control of web media elements (play, pause, seek, etc).
+- Moved [`CompositorController`][65.1], [`DynamicToolbarAnimator`][65.2],
+  [`OverscrollEdgeEffect`][65.3], [`PanZoomController`][65.4] from
+  `org.mozilla.gecko.gfx` to [`org.mozilla.geckoview`][65.5]
+
+[65.1]: ../CompositorController.html
+[65.2]: ../DynamicToolbarAnimator.html
+[65.3]: ../OverscrollEdgeEffect.html
+[65.4]: ../PanZoomController.html
+[65.5]: ../package-summary.html
+
+- Added [`@UiThread`][65.6], [`@AnyThread`][65.7] annotations to all APIs
+
+[65.6]: https://developer.android.com/reference/android/support/annotation/UiThread
+[65.7]: https://developer.android.com/reference/android/support/annotation/AnyThread
+
+- Changed `GeckoRuntimeSettings#getLocale` to [`getLocales`][65.8] and related
+  APIs.
+
+[65.8]: ../GeckoRuntimeSettings.html#getLocales--
+
+- Merged `org.mozilla.gecko.gfx.LayerSession` into [`GeckoSession`][65.9]
+
+[65.9]: ../GeckoSession.html
+
+- Added [`GeckoSession.MediaDelegate`][65.10] and [`MediaElement`][65.11]. This
+  allow monitoring and control of web media elements (play, pause, seek, etc).
+
+[65.10]: ../GeckoSession.MediaDelegate.html
+[65.11]: ../MediaElement.html
+
 - Removed unused `access` parameter from
-  `GeckoSession.PermissionDelegate#onContentPermissionRequest`
-- Added `WebMessage`, `WebRequest`, `WebResponse`, and `GeckoWebExecutor`. This
-  exposes Gecko networking to apps. It includes speculative connections, name
-  resolution, and a Fetch-like HTTP API.
-- Added `GeckoSession.HistoryDelegate`. This allows apps to implement their own
-  history storage system and provide visited link status.
-- Added `ContentDelegate#onFirstComposite` to get first composite callback
-  after a compositor start.
-- Changed `LoadRequest.isUserTriggered` to `isRedirect`.
-- Added `GeckoSession.LOAD_FLAGS_BYPASS_CLASSIFIER` to bypass the URI
+  [`GeckoSession.PermissionDelegate#onContentPermissionRequest`][65.12]
+
+[65.12]: ../GeckoSession.PermissionDelegate.html#onContentPermissionRequest-org.mozilla.geckoview.GeckoSession-java.lang.String-int-org.mozilla.geckoview.GeckoSession.PermissionDelegate.Callback-
+
+- Added [`WebMessage`][65.13], [`WebRequest`][65.14], [`WebResponse`][65.15],
+  and [`GeckoWebExecutor`][65.16]. This exposes Gecko networking to apps. It
+  includes speculative connections, name resolution, and a Fetch-like HTTP API.
+
+[65.13]: ../WebMessage.html
+[65.14]: ../WebRequest.html
+[65.15]: ../WebResponse.html
+[65.16]: ../GeckoWebExecutor.html
+
+- Added [`GeckoSession.HistoryDelegate`][65.17]. This allows apps to implement
+  their own history storage system and provide visited link status.
+
+[65.17]: ../GeckoSession.HistoryDelegate.html
+
+- Added [`ContentDelegate#onFirstComposite`][65.18] to get first composite
+  callback after a compositor start.
+
+[65.18]: ../GeckoSession.ContentDelegate.html#onFirstComposite-org.mozilla.geckoview.GeckoSession-
+
+- Changed `LoadRequest.isUserTriggered` to [`isRedirect`][65.19].
+
+[65.19]: ../GeckoSession.NavigationDelegate.LoadRequest.html#isRedirect
+
+- Added [`GeckoSession.LOAD_FLAGS_BYPASS_CLASSIFIER`][65.20] to bypass the URI
   classifier.
+
+[65.20]: ../GeckoSession.html#LOAD_FLAGS_BYPASS_CLASSIFIER
+
 - Added a `protected` empty constructor to all field-only classes so that apps
   can mock these classes in tests.
-- Added `ContentDelegate.ContextElement` to extend the information passed to
-  `ContentDelegate#onContextMenu`. Extended information includes the element's
-  title and alt attributes.
-- Changed `ContentDelegate.ContextElement` TYPE_ constants to public access.
-- Changed `ContentDelegate.ContextElement`, `GeckoSession.FinderResult` to
-  non-final class.
-- Update `CrashReporter.sendCrashReport()` to return the crash ID as a
-  GeckoResult<String>.
+
+- Added [`ContentDelegate.ContextElement`][65.21] to extend the information
+  passed to [`ContentDelegate#onContextMenu`][65.22]. Extended information
+  includes the element's title and alt attributes.
+
+[65.21]: ../GeckoSession.ContentDelegate.ContextElement.html
+[65.22]: ../GeckoSession.ContentDelegate.html#onContextMenu-org.mozilla.geckoview.GeckoSession-int-int-org.mozilla.geckoview.GeckoSession.ContentDelegate.ContextElement-
+
+- Changed [`ContentDelegate.ContextElement`][65.21] `TYPE_` constants to public
+  access.
+
+- Changed [`ContentDelegate.ContextElement`][65.21],
+  [`GeckoSession.FinderResult`][65.23] to non-final class.
+
+[65.23]: ../GeckoSession.FinderResult.html
+
+- Update [`CrashReporter#sendCrashReport`][65.24] to return the crash ID as a
+  [`GeckoResult<String>`][65.25].
+
+[65.24]: ../CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
+[65.25]: ../GeckoResult.html
 
 [api-version]: cdbaa3fa639126d2d45a0cd8e9508f95a9e98e33
--- a/netwerk/base/nsIProtocolHandler.idl
+++ b/netwerk/base/nsIProtocolHandler.idl
@@ -279,61 +279,55 @@ interface nsIProtocolHandler : nsISuppor
 
     /**
      * Loading channels from this protocol has side-effects that make
      * it unsuitable for saving to a local file.
      */
     const unsigned long URI_NON_PERSISTABLE = (1<<14);
 
     /**
-     * This protocol handler forbids accessing cookies e.g. for mail related
-     * protocols.
-     */
-    const unsigned long URI_FORBIDS_COOKIE_ACCESS = (1<<15);
-
-    /**
      * URIs for this protocol require the webapps permission on the principal
      * when opening URIs for a different domain. See bug#773886
      */
-    const unsigned long URI_CROSS_ORIGIN_NEEDS_WEBAPPS_PERM = (1<<16);
+    const unsigned long URI_CROSS_ORIGIN_NEEDS_WEBAPPS_PERM = (1<<15);
 
     /**
      * Channels for this protocol don't need to spin the event loop to handle
      * Open() and reads on the resulting stream.
      */
-    const unsigned long URI_SYNC_LOAD_IS_OK = (1<<17);
+    const unsigned long URI_SYNC_LOAD_IS_OK = (1<<16);
 
     /**
      * All the origins whose URI has this scheme are considered potentially
      * trustworthy.
      * Per the SecureContext spec, https: and wss: should be considered
      * a priori secure, and implementations may consider other,
      * implementation-specific URI schemes as secure.
      */
-    const unsigned long URI_IS_POTENTIALLY_TRUSTWORTHY = (1<<18);
+    const unsigned long URI_IS_POTENTIALLY_TRUSTWORTHY = (1<<17);
 
     /**
      * This URI may be fetched and the contents are visible to anyone. This is
      * semantically equivalent to the resource being served with all-access CORS
      * headers.
      */
-    const unsigned long URI_FETCHABLE_BY_ANYONE = (1 << 19);
+    const unsigned long URI_FETCHABLE_BY_ANYONE = (1 << 18);
 
     /**
      * If this flag is set, then the origin for this protocol is the full URI
      * spec, not just the scheme + host + port.
      *
      * Note: this is not supported in Firefox.  It is currently only available
      * in Thunderbird and SeaMonkey.
      */
-    const unsigned long ORIGIN_IS_FULL_SPEC = (1 << 20);
+    const unsigned long ORIGIN_IS_FULL_SPEC = (1 << 19);
 
     /**
      * If this flag is set, the URI does not always allow content using the same
      * protocol to link to it.
      */
-    const unsigned long URI_SCHEME_NOT_SELF_LINKABLE = (1 << 21);
+    const unsigned long URI_SCHEME_NOT_SELF_LINKABLE = (1 << 20);
 
     /**
      * The URIs for this protocol can be loaded by extensions.
      */
-    const unsigned long URI_LOADABLE_BY_EXTENSIONS = (1 << 22);
+    const unsigned long URI_LOADABLE_BY_EXTENSIONS = (1 << 21);
 };
--- a/netwerk/cookie/nsICookiePermission.idl
+++ b/netwerk/cookie/nsICookiePermission.idl
@@ -60,16 +60,34 @@ interface nsICookiePermission : nsISuppo
    *
    * @return one of the following nsCookieAccess values:
    *         ACCESS_DEFAULT, ACCESS_ALLOW, ACCESS_DENY, or
    *         ACCESS_ALLOW_FIRST_PARTY_ONLY
    */
   nsCookieAccess canAccess(in nsIPrincipal aPrincipal);
 
   /**
+   * canAccessURI
+   *
+   * this method is called to test whether or not the given principal may
+   * access the cookie database, either to set or get cookies.
+   *
+   * Be careful when calling this function, you probably want the principal
+   * based version instead of this one unless if performance is an issue.
+   *
+   * @param aURI
+   *        the URI trying to access cookies.
+   *
+   * @return one of the following nsCookieAccess values:
+   *         ACCESS_DEFAULT, ACCESS_ALLOW, ACCESS_DENY, or
+   *         ACCESS_ALLOW_FIRST_PARTY_ONLY
+   */
+  nsCookieAccess canAccessURI(in nsIURI aURI);
+
+  /**
    * canSetCookie
    *
    * this method is called to test whether or not the given URI/channel may
    * set a specific cookie.  this method is always preceded by a call to
    * canAccess. it may modify the isSession and expiry attributes of the
    * cookie via the aIsSession and aExpiry parameters, in order to limit
    * or extend the lifetime of the cookie. this is useful, for instance, to
    * downgrade a cookie to session-only if it fails to meet certain criteria.
--- a/python/mach/mach/test/test_telemetry.py
+++ b/python/mach/mach/test/test_telemetry.py
@@ -32,18 +32,24 @@ def run_mach(tmpdir):
 
     def run(*args, **kwargs):
         # Run mach with the provided arguments
         subprocess.check_output([sys.executable, mach] + list(args),
                                 stderr=subprocess.STDOUT,
                                 env=env,
                                 **kwargs)
         # Load any telemetry data that was written
-        path = unicode(tmpdir.join('telemetry', 'outgoing'))
-        return [json.load(open(os.path.join(path, f), 'rb')) for f in os.listdir(path)]
+        path = tmpdir.join('telemetry', 'outgoing')
+        try:
+            return [json.load(f.open('rb')) for f in path.listdir()]
+        except EnvironmentError:
+            for p in path.parts(reverse=True):
+                if not p.check(dir=1):
+                    print('Path does not exist: "%s"' % p, file=sys.stderr)
+            raise
     return run
 
 
 def test_simple(run_mach, tmpdir):
     data = run_mach('python', '-c', 'pass')
     assert len(data) == 1
     d = data[0]
     assert d['command'] == 'python'
--- a/taskcluster/docker/funsize-update-generator/Pipfile.lock
+++ b/taskc