Bug 1540839 - Add Cross Origin Opener Policy case for BC preservation; r=nika
authorKyle Machulis <kyle@nonpolynomial.com>
Fri, 05 Apr 2019 17:18:08 -0700
changeset 532665 955bbe4b223ebf67260b928365d0ac49bbba54cf
parent 532664 91db28a0bc41d47ecfabba24ea9f8d51023e0a71
child 532666 5863b49f009a640f3820e8b31d029cfc61b1636c
push id11270
push userrgurzau@mozilla.com
push dateWed, 15 May 2019 15:07:19 +0000
treeherdermozilla-beta@571bc76da583 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnika
bugs1540839
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1540839 - Add Cross Origin Opener Policy case for BC preservation; r=nika If we're doing a process switch due to the cross origin opener policy being mismatched, we don't want to preserve the browsing context. Differential Revision: https://phabricator.services.mozilla.com/D26392
browser/base/content/tabbrowser.js
browser/components/sessionstore/SessionStore.jsm
dom/base/nsFrameLoaderOwner.cpp
dom/ipc/BrowserChild.h
dom/webidl/MozFrameLoaderOwner.webidl
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -1601,16 +1601,17 @@ window._gBrowser = {
   },
 
   updateBrowserRemoteness(aBrowser, {
     newFrameloader,
     opener,
     remoteType,
     sameProcessAsFrameLoader,
     recordExecution,
+    replaceBrowsingContext,
   } = {}) {
     let isRemote = aBrowser.getAttribute("remote") == "true";
 
     // We have to be careful with this here, as the "no remote type" is null,
     // not a string. Make sure to check only for undefined, since null is
     // allowed.
     if (remoteType === undefined) {
       throw new Error("Remote type must be set!");
@@ -1721,17 +1722,17 @@ window._gBrowser = {
     }
 
     if (!Services.prefs.getBoolPref("fission.rebuild_frameloaders_on_remoteness_change", false)) {
       parent.appendChild(aBrowser);
     } else {
       // This call actually switches out our frameloaders. Do this as late as
       // possible before rebuilding the browser, as we'll need the new browser
       // state set up completely first.
-      aBrowser.changeRemoteness({ remoteType });
+      aBrowser.changeRemoteness({ remoteType, replaceBrowsingContext });
       // Once we have new frameloaders, this call sets the browser back up.
       //
       // FIXME(emilio): Shouldn't we call destroy() first? What hides the
       // select pop-ups and such otherwise?
       aBrowser.construct();
     }
 
     aBrowser.userTypedValue = oldUserTypedValue;
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -2291,30 +2291,34 @@ var SessionStoreInternal = {
         if (now - data.closedAt > TIME_TO_LIVE) {
           array.splice(i, 1);
           this._closedObjectsChanged = true;
         }
       }
     }
   },
 
-  async _doTabProcessSwitch(aBrowser, aRemoteType, aChannel, aSwitchId) {
+  async _doTabProcessSwitch(aBrowser, aRemoteType, aChannel, aSwitchId, aReplaceBrowsingContext) {
     debug(`[process-switch]: performing switch from ${aBrowser.remoteType} to ${aRemoteType}`);
 
     // Don't try to switch tabs before delayed startup is completed.
     await aBrowser.ownerGlobal.delayedStartupPromise;
 
     // Perform a navigateAndRestore to trigger the process switch.
     let tab = aBrowser.ownerGlobal.gBrowser.getTabForBrowser(aBrowser);
     let loadArguments = {
       newFrameloader: true,  // Switch even if remoteType hasn't changed.
       remoteType: aRemoteType,  // Don't derive remoteType to switch to.
 
       // Information about which channel should be performing the load.
       redirectLoadSwitchId: aSwitchId,
+
+      // True if this is a process switch due to a policy mismatch, means we
+      // shouldn't preserve our browsing context.
+      replaceBrowsingContext: aReplaceBrowsingContext,
     };
 
     await SessionStore.navigateAndRestore(tab, loadArguments, -1);
 
     // If the process switch seems to have failed, send an error over to our
     // caller, to give it a chance to kill our channel.
     if (aBrowser.remoteType != aRemoteType ||
         !aBrowser.frameLoader || !aBrowser.frameLoader.remoteTab) {
@@ -2327,26 +2331,26 @@ var SessionStoreInternal = {
     return remoteTab;
   },
 
   /**
    * Perform a destructive process switch into a distinct process.
    * This method is asynchronous, as it requires multiple calls into content
    * processes.
    */
-  async _doProcessSwitch(aBrowsingContext, aRemoteType, aChannel, aSwitchId) {
+  async _doProcessSwitch(aBrowsingContext, aRemoteType, aChannel, aSwitchId, aReplaceBrowsingContext) {
     // There are two relevant cases when performing a process switch for a
     // browsing context: in-process and out-of-process embedders.
 
     // If our embedder is in-process (e.g. we're a xul:browser element embedded
     // within <tabbrowser>), then we can perform a process switch using the
     // traditional mechanism.
     if (aBrowsingContext.embedderElement) {
       return this._doTabProcessSwitch(aBrowsingContext.embedderElement,
-                                      aRemoteType, aChannel, aSwitchId);
+                                      aRemoteType, aChannel, aSwitchId, aReplaceBrowsingContext);
     }
 
     let wg = aBrowsingContext.embedderWindowGlobal;
     return wg.changeFrameRemoteness(aBrowsingContext, aRemoteType, aSwitchId);
   },
 
   // Examine the channel response to see if we should change the process
   // performing the given load.
@@ -2438,23 +2442,27 @@ var SessionStoreInternal = {
     }
 
     if (remoteType == E10SUtils.NOT_REMOTE ||
         currentRemoteType == E10SUtils.NOT_REMOTE) {
       debug(`[process-switch]: non-remote source/target - ignoring`);
       return;
     }
 
+    const isCOOPSwitch = E10SUtils.useCrossOriginOpenerPolicy() &&
+          aChannel.hasCrossOriginOpenerPolicyMismatch();
+
     // ------------------------------------------------------------------------
     // DANGER ZONE: Perform a process switch into the new process. This is
     // destructive.
     // ------------------------------------------------------------------------
     let identifier = ++this._switchIdMonotonic;
     let tabPromise = this._doProcessSwitch(browsingContext, remoteType,
-                                           aChannel, identifier);
+                                           aChannel, identifier,
+                                           isCOOPSwitch);
     aChannel.switchProcessTo(tabPromise, identifier);
   },
 
   /* ........ nsISessionStore API .............. */
 
   getBrowserState: function ssi_getBrowserState() {
     let state = this.getCurrentState();
 
@@ -3291,16 +3299,17 @@ var SessionStoreInternal = {
     let tabState = TabState.clone(tab, TAB_CUSTOM_VALUES.get(tab));
     let options = {
       restoreImmediately: true,
       // We want to make sure that this information is passed to restoreTab
       // whether or not a historyIndex is passed in. Thus, we extract it from
       // the loadArguments.
       newFrameloader: loadArguments.newFrameloader,
       remoteType: loadArguments.remoteType,
+      replaceBrowsingContext: loadArguments.replaceBrowsingContext,
       // Make sure that SessionStore knows that this restoration is due
       // to a navigation, as opposed to us restoring a closed window or tab.
       restoreContentReason: RESTORE_TAB_CONTENT_REASON.NAVIGATE_AND_RESTORE,
     };
 
     if (historyIndex >= 0) {
       tabState.index = historyIndex + 1;
       tabState.index = Math.max(1, Math.min(tabState.index, tabState.entries.length));
@@ -4253,28 +4262,31 @@ var SessionStoreInternal = {
       if (loadArguments.userContextId) {
         browser.setAttribute("usercontextid", loadArguments.userContextId);
       }
     }
 
     this.markTabAsRestoring(aTab);
 
     let newFrameloader = aOptions.newFrameloader;
-
+    let replaceBrowsingContext = aOptions.replaceBrowsingContext;
     let isRemotenessUpdate;
     if (aOptions.remoteType !== undefined) {
       // We already have a selected remote type so we update to that.
       isRemotenessUpdate =
         tabbrowser.updateBrowserRemoteness(browser,
                                            { remoteType: aOptions.remoteType,
-                                             newFrameloader });
+                                             newFrameloader,
+                                             replaceBrowsingContext,
+                                           });
     } else {
       isRemotenessUpdate =
         tabbrowser.updateBrowserRemotenessByURL(browser, uri, {
           newFrameloader,
+          replaceBrowsingContext,
         });
     }
 
     if (isRemotenessUpdate) {
       // We updated the remoteness, so we need to send the history down again.
       //
       // Start a new epoch to discard all frame script messages relating to a
       // previous epoch. All async messages that are still on their way to chrome
--- a/dom/base/nsFrameLoaderOwner.cpp
+++ b/dom/base/nsFrameLoaderOwner.cpp
@@ -28,22 +28,27 @@ nsFrameLoaderOwner::GetBrowsingContext()
     return mFrameLoader->GetBrowsingContext();
   }
   return nullptr;
 }
 
 void nsFrameLoaderOwner::ChangeRemoteness(
     const mozilla::dom::RemotenessOptions& aOptions, mozilla::ErrorResult& rv) {
   RefPtr<mozilla::dom::BrowsingContext> bc;
-  // If we already have a Frameloader, destroy it.
+
+  // If we already have a Frameloader, destroy it, possibly preserving its
+  // browsing context.
   if (mFrameLoader) {
-    bc = mFrameLoader->GetBrowsingContext();
-
-    // TODO pass in Cross-Origin-Load-Policy rules
-    mFrameLoader->SkipBrowsingContextDetach();
+    // If this is a process switch due to a difference in Cross Origin Opener
+    // Policy, do not preserve the browsing context. Otherwise, save off the
+    // browsing context and use it when creating our new FrameLoader.
+    if (!aOptions.mReplaceBrowsingContext) {
+      bc = mFrameLoader->GetBrowsingContext();
+      mFrameLoader->SkipBrowsingContextDetach();
+    }
 
     mFrameLoader->Destroy();
     mFrameLoader = nullptr;
   }
 
   // In this case, we're not reparenting a frameloader, we're just destroying
   // our current one and creating a new one, so we can use ourselves as the
   // owner.
--- a/dom/ipc/BrowserChild.h
+++ b/dom/ipc/BrowserChild.h
@@ -550,17 +550,17 @@ class BrowserChild final : public Browse
       const mozilla::NativeEventData& aKeyEventData, const bool& aIsConsumed);
 
   mozilla::ipc::IPCResult RecvPrint(const uint64_t& aOuterWindowID,
                                     const PrintData& aPrintData);
 
   mozilla::ipc::IPCResult RecvUpdateNativeWindowHandle(
       const uintptr_t& aNewHandle);
 
-  virtual mozilla::ipc::IPCResult RecvSkipBrowsingContextDetach() override;
+  mozilla::ipc::IPCResult RecvSkipBrowsingContextDetach();
   /**
    * Native widget remoting protocol for use with windowed plugins with e10s.
    */
   PPluginWidgetChild* AllocPPluginWidgetChild();
 
   bool DeallocPPluginWidgetChild(PPluginWidgetChild* aActor);
 
 #ifdef XP_WIN
--- a/dom/webidl/MozFrameLoaderOwner.webidl
+++ b/dom/webidl/MozFrameLoaderOwner.webidl
@@ -8,16 +8,17 @@ dictionary RemotenessOptions {
   DOMString? remoteType;
   FrameLoader? sameProcessAsFrameLoader;
   WindowProxy? opener;
 
   // Used to resume a given channel load within the target process. If present,
   // it will be used rather than the `src` & `srcdoc` attributes on the
   // frameloader to control the load behaviour.
   unsigned long long pendingSwitchID;
+  boolean replaceBrowsingContext = false;
 };
 
 [NoInterfaceObject]
 interface MozFrameLoaderOwner {
   [ChromeOnly]
   readonly attribute FrameLoader? frameLoader;
 
   [ChromeOnly]