Merge mozilla-central to autoland. a=merge CLOSED TREE
authorBrindusan Cristian <cbrindusan@mozilla.com>
Wed, 15 May 2019 00:52:00 +0300
changeset 532673 1db216f33d399e886e6d2e425a5e5e1019c5c704
parent 532658 304607e12723d4f602602b4eba9232f27f4b6d64 (current diff)
parent 532672 6f732caaed60783f57944a66f7ea494f5fd78d6c (diff)
child 532674 7005da6ab266f015214f06ba9e4f3fcf248408df
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)
reviewersmerge
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
Merge mozilla-central to autoland. a=merge CLOSED TREE
modules/libpref/init/all.js
--- 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/docshell/base/BrowsingContext.cpp
+++ b/docshell/base/BrowsingContext.cpp
@@ -808,16 +808,31 @@ void BrowsingContext::Transaction::Apply
     aBrowsingContext->WillSet##name(*m##name, aSource); \
     aBrowsingContext->m##name = std::move(*m##name);    \
     aBrowsingContext->DidSet##name(aSource);            \
     m##name.reset();                                    \
   }
 #include "mozilla/dom/BrowsingContextFieldList.h"
 }
 
+BrowsingContext::IPCInitializer BrowsingContext::GetIPCInitializer() {
+  MOZ_ASSERT(
+      !mozilla::Preferences::GetBool("fission.preserve_browsing_contexts", false) ||
+      IsContent());
+
+  IPCInitializer init;
+  init.mId = Id();
+  init.mParentId = mParent ? mParent->Id() : 0;
+  init.mCached = IsCached();
+
+#define MOZ_BC_FIELD(name, type) init.m##name = m##name;
+#include "mozilla/dom/BrowsingContextFieldList.h"
+  return init;
+}
+
 already_AddRefed<BrowsingContext> BrowsingContext::IPCInitializer::GetParent() {
   RefPtr<BrowsingContext> parent;
   if (mParentId != 0) {
     parent = BrowsingContext::Get(mParentId);
     MOZ_RELEASE_ASSERT(parent);
   }
   return parent.forget();
 }
--- a/docshell/base/BrowsingContext.h
+++ b/docshell/base/BrowsingContext.h
@@ -342,26 +342,17 @@ class BrowsingContext : public nsWrapper
     bool mCached;
     // Include each field, skipping mOpener, as we want to handle it
     // separately.
 #define MOZ_BC_FIELD(name, type) type m##name;
 #include "mozilla/dom/BrowsingContextFieldList.h"
   };
 
   // Create an IPCInitializer object for this BrowsingContext.
-  IPCInitializer GetIPCInitializer() {
-    IPCInitializer init;
-    init.mId = Id();
-    init.mParentId = mParent ? mParent->Id() : 0;
-    init.mCached = IsCached();
-
-#define MOZ_BC_FIELD(name, type) init.m##name = m##name;
-#include "mozilla/dom/BrowsingContextFieldList.h"
-    return init;
-  }
+  IPCInitializer GetIPCInitializer();
 
   // Create a BrowsingContext object from over IPC.
   static already_AddRefed<BrowsingContext> CreateFromIPC(
       IPCInitializer&& aInitializer, BrowsingContextGroup* aGroup,
       ContentParent* aOriginProcess);
 
  protected:
   virtual ~BrowsingContext();
--- a/docshell/base/BrowsingContextGroup.cpp
+++ b/docshell/base/BrowsingContextGroup.cpp
@@ -6,16 +6,22 @@
 
 #include "mozilla/dom/BrowsingContextGroup.h"
 #include "mozilla/dom/BrowsingContextBinding.h"
 #include "mozilla/dom/BindingUtils.h"
 
 namespace mozilla {
 namespace dom {
 
+BrowsingContextGroup::BrowsingContextGroup() {
+  if (XRE_IsContentProcess()) {
+    ContentChild::GetSingleton()->HoldBrowsingContextGroup(this);
+  }
+}
+
 bool BrowsingContextGroup::Contains(BrowsingContext* aBrowsingContext) {
   return aBrowsingContext->Group() == this;
 }
 
 void BrowsingContextGroup::Register(BrowsingContext* aBrowsingContext) {
   MOZ_DIAGNOSTIC_ASSERT(aBrowsingContext);
   mContexts.PutEntry(aBrowsingContext);
 }
--- a/docshell/base/BrowsingContextGroup.h
+++ b/docshell/base/BrowsingContextGroup.h
@@ -57,17 +57,17 @@ class BrowsingContextGroup final : publi
   void GetToplevels(BrowsingContext::Children& aToplevels) {
     aToplevels.AppendElements(mToplevels);
   }
 
   nsISupports* GetParentObject() const;
   JSObject* WrapObject(JSContext* aCx,
                        JS::Handle<JSObject*> aGivenProto) override;
 
-  BrowsingContextGroup() = default;
+  BrowsingContextGroup();
 
   static already_AddRefed<BrowsingContextGroup> Select(
       BrowsingContext* aParent, BrowsingContext* aOpener) {
     if (aParent) {
       return do_AddRef(aParent->Group());
     }
     if (aOpener) {
       return do_AddRef(aOpener->Group());
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -380,17 +380,18 @@ nsDocShell::nsDocShell(BrowsingContext* 
       mIsPrintingOrPP(false),
       mSavingOldViewer(false),
       mDynamicallyCreated(false),
       mAffectPrivateSessionLifetime(true),
       mInvisible(false),
       mHasLoadedNonBlankURI(false),
       mBlankTiming(false),
       mTitleValidForCurrentURI(false),
-      mIsFrame(false) {
+      mIsFrame(false),
+      mSkipBrowsingContextDetachOnDestroy(false) {
   mHistoryID.m0 = 0;
   mHistoryID.m1 = 0;
   mHistoryID.m2 = 0;
   AssertOriginAttributesMatchPrivateBrowsing();
 
   nsContentUtils::GenerateUUIDInPlace(mHistoryID);
 
   if (gDocShellCount++ == 0) {
@@ -5031,17 +5032,21 @@ nsDocShell::Destroy() {
   if (mSessionHistory) {
     // We want to destroy these content viewers now rather than
     // letting their destruction wait for the session history
     // entries to get garbage collected.  (Bug 488394)
     mSessionHistory->EvictLocalContentViewers();
     mSessionHistory = nullptr;
   }
 
-  mBrowsingContext->Detach();
+  // This will be skipped in cases where we want to preserve the browsing
+  // context between loads.
+  if (!mSkipBrowsingContextDetachOnDestroy) {
+    mBrowsingContext->Detach();
+  }
 
   SetTreeOwner(nullptr);
 
   mBrowserChild = nullptr;
 
   mChromeEventHandler = nullptr;
 
   mOnePermittedSandboxedNavigator = nullptr;
@@ -12900,20 +12905,18 @@ nsresult nsDocShell::CharsetChangeStopDo
   if (eCharsetReloadRequested != mCharsetReloadState) {
     Stop(nsIWebNavigation::STOP_ALL);
     return NS_OK;
   }
   // return failer if this request is not accepted due to mCharsetReloadState
   return NS_ERROR_DOCSHELL_REQUEST_REJECTED;
 }
 
-NS_IMETHODIMP
-nsDocShell::SetIsPrinting(bool aIsPrinting) {
+void nsDocShell::SetIsPrinting(bool aIsPrinting) {
   mIsPrintingOrPP = aIsPrinting;
-  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::GetPrintPreview(nsIWebBrowserPrint** aPrintPreview) {
   *aPrintPreview = nullptr;
 #if NS_PRINT_PREVIEW
   nsCOMPtr<nsIDocumentViewerPrint> print = do_QueryInterface(mContentViewer);
   if (!print || !print->IsInitializedForPrintPreview()) {
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -400,16 +400,20 @@ class nsDocShell final : public nsDocLoa
    */
   MOZ_CAN_RUN_SCRIPT_BOUNDARY
   nsresult InternalLoad(nsDocShellLoadState* aLoadState,
                         nsIDocShell** aDocShell, nsIRequest** aRequest);
 
   // Clear the document's storage access flag if needed.
   void MaybeClearStorageAccessFlag();
 
+  void SkipBrowsingContextDetach() {
+    mSkipBrowsingContextDetachOnDestroy = true;
+  }
+
  private:  // member functions
   friend class nsDSURIContentListener;
   friend class FramingChecker;
   friend class OnLinkClickEvent;
   friend class nsIDocShell;
   friend class mozilla::dom::BrowsingContext;
 
   // It is necessary to allow adding a timeline marker wherever a docshell
@@ -1231,11 +1235,16 @@ class nsDocShell final : public nsDocLoa
   // We will check the innerWin's timing before creating a new one
   // in MaybeInitTiming()
   bool mBlankTiming : 1;
 
   // This flag indicates when the title is valid for the current URI.
   bool mTitleValidForCurrentURI : 1;
 
   bool mIsFrame : 1;
+
+  // If mSkipBrowsingContextDetachOnDestroy is set to true, then when the
+  // docshell is destroyed, the browsing context will not be detached. This is
+  // for cases where we want to preserve the BC for future use.
+  bool mSkipBrowsingContextDetachOnDestroy : 1;
 };
 
 #endif /* nsDocShell_h__ */
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -541,17 +541,17 @@ interface nsIDocShell : nsIDocShellTreeI
    * and should be treated accordingly.
    **/
   attribute boolean isOffScreenBrowser;
 
   /**
    * Allows nsDocumentViewer to tell the top-level same-type docshell that
    * one of the documents under it is printing.
    */
-  void setIsPrinting(in boolean aIsPrinting);
+  [noscript, notxpcom] void setIsPrinting(in boolean aIsPrinting);
 
   /**
    * If the current content viewer isn't initialized for print preview,
    * it is replaced with one which is and to which an about:blank document
    * is loaded.
    */
   readonly attribute nsIWebBrowserPrint printPreview;
 
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -359,34 +359,39 @@ nsFrameLoader* nsFrameLoader::Create(Ele
 
   RefPtr<BrowsingContext> context = CreateBrowsingContext(aOwner, aOpener);
   NS_ENSURE_TRUE(context, nullptr);
   return new nsFrameLoader(aOwner, context, aNetworkCreated);
 }
 
 /* static */
 nsFrameLoader* nsFrameLoader::Create(
-    mozilla::dom::Element* aOwner,
+    mozilla::dom::Element* aOwner, BrowsingContext* aPreservedBrowsingContext,
     const mozilla::dom::RemotenessOptions& aOptions) {
   NS_ENSURE_TRUE(aOwner, nullptr);
   // This version of Create is only called for Remoteness updates, so we can
   // assume we need a FrameLoader here and skip the check in the other Create.
 
   bool hasOpener =
       aOptions.mOpener.WasPassed() && !aOptions.mOpener.Value().IsNull();
   MOZ_ASSERT(!aOptions.mRemoteType.WasPassed() ||
                  aOptions.mRemoteType.Value().IsVoid() || !hasOpener,
              "Cannot pass aOpener for a remote frame!");
 
   // This seems slightly unwieldy.
   RefPtr<BrowsingContext> opener;
   if (hasOpener) {
     opener = aOptions.mOpener.Value().Value().get();
   }
-  RefPtr<BrowsingContext> context = CreateBrowsingContext(aOwner, opener);
+  RefPtr<BrowsingContext> context;
+  if (aPreservedBrowsingContext) {
+    context = aPreservedBrowsingContext;
+  } else {
+    context = CreateBrowsingContext(aOwner, opener);
+  }
   NS_ENSURE_TRUE(context, nullptr);
   return new nsFrameLoader(aOwner, context, aOptions);
 }
 
 void nsFrameLoader::LoadFrame(bool aOriginalSrc) {
   if (NS_WARN_IF(!mOwnerContent)) {
     return;
   }
@@ -3485,8 +3490,27 @@ ProcessMessageManager* nsFrameLoader::Ge
 };
 
 JSObject* nsFrameLoader::WrapObject(JSContext* cx,
                                     JS::Handle<JSObject*> aGivenProto) {
   JS::RootedObject result(cx);
   FrameLoader_Binding::Wrap(cx, this, this, aGivenProto, &result);
   return result;
 }
+
+void nsFrameLoader::SkipBrowsingContextDetach() {
+  if (IsRemoteFrame()) {
+    // OOP Browser - Go directly over Browser Parent
+    if (mBrowserParent) {
+      Unused << mBrowserParent->SendSkipBrowsingContextDetach();
+    }
+    // OOP IFrame - Through Browser Bridge Parent, set on browser child
+    else if (mBrowserBridgeChild) {
+      Unused << mBrowserBridgeChild->SendSkipBrowsingContextDetach();
+    }
+    return;
+  }
+
+  // In process
+  RefPtr<nsDocShell> docshell = GetDocShell();
+  MOZ_ASSERT(docshell);
+  docshell->SkipBrowsingContextDetach();
+}
--- a/dom/base/nsFrameLoader.h
+++ b/dom/base/nsFrameLoader.h
@@ -90,27 +90,29 @@ typedef struct _GtkWidget GtkWidget;
 class nsFrameLoader final : public nsStubMutationObserver,
                             public mozilla::dom::ipc::MessageManagerCallback,
                             public nsWrapperCache {
   friend class AutoResetInShow;
   friend class AutoResetInFrameSwap;
   typedef mozilla::dom::PBrowserParent PBrowserParent;
   typedef mozilla::dom::Document Document;
   typedef mozilla::dom::BrowserParent BrowserParent;
+  typedef mozilla::dom::BrowsingContext BrowsingContext;
   typedef mozilla::layout::RenderFrame RenderFrame;
 
  public:
   // Called by Frame Elements to create a new FrameLoader.
   static nsFrameLoader* Create(mozilla::dom::Element* aOwner,
                                mozilla::dom::BrowsingContext* aOpener,
                                bool aNetworkCreated);
 
   // Called by nsFrameLoaderOwner::ChangeRemoteness when switching out
   // FrameLoaders.
   static nsFrameLoader* Create(mozilla::dom::Element* aOwner,
+                               BrowsingContext* aPreservedBrowsingContext,
                                const mozilla::dom::RemotenessOptions& aOptions);
 
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_FRAMELOADER_IID)
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsFrameLoader)
 
   NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
@@ -380,16 +382,18 @@ class nsFrameLoader final : public nsStu
   // public because a callback needs these.
   RefPtr<mozilla::dom::ChromeMessageSender> mMessageManager;
   RefPtr<mozilla::dom::InProcessBrowserChildMessageManager>
       mChildMessageManager;
 
   virtual JSObject* WrapObject(JSContext* cx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
+  void SkipBrowsingContextDetach();
+
  private:
   nsFrameLoader(mozilla::dom::Element* aOwner,
                 mozilla::dom::BrowsingContext* aBrowsingContext,
                 bool aNetworkCreated);
   nsFrameLoader(mozilla::dom::Element* aOwner,
                 mozilla::dom::BrowsingContext* aBrowsingContext,
                 const mozilla::dom::RemotenessOptions& aOptions);
   ~nsFrameLoader();
--- a/dom/base/nsFrameLoaderOwner.cpp
+++ b/dom/base/nsFrameLoaderOwner.cpp
@@ -27,28 +27,47 @@ nsFrameLoaderOwner::GetBrowsingContext()
   if (mFrameLoader) {
     return mFrameLoader->GetBrowsingContext();
   }
   return nullptr;
 }
 
 void nsFrameLoaderOwner::ChangeRemoteness(
     const mozilla::dom::RemotenessOptions& aOptions, mozilla::ErrorResult& rv) {
-  // If we already have a Frameloader, destroy it.
+  RefPtr<mozilla::dom::BrowsingContext> bc;
+
+  // If we already have a Frameloader, destroy it, possibly preserving its
+  // browsing context.
   if (mFrameLoader) {
+    // Don't preserve contexts if this is a chrome (parent process) window that
+    // is changing from remote to local.
+    bool isChromeRemoteToLocal =
+        XRE_IsParentProcess() && (!aOptions.mRemoteType.WasPassed() ||
+                                  aOptions.mRemoteType.Value().IsVoid());
+
+    // 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 && !isChromeRemoteToLocal &&
+        mozilla::Preferences::GetBool("fission.preserve_browsing_contexts", false)) {
+      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.
   RefPtr<Element> owner = do_QueryObject(this);
   MOZ_ASSERT(owner);
-  mFrameLoader = nsFrameLoader::Create(owner, aOptions);
+  mFrameLoader = nsFrameLoader::Create(owner, bc, aOptions);
+
   if (NS_WARN_IF(!mFrameLoader)) {
     return;
   }
 
   if (aOptions.mPendingSwitchID.WasPassed()) {
     mFrameLoader->ResumeLoad(aOptions.mPendingSwitchID.Value());
   } else {
     mFrameLoader->LoadFrame(false);
@@ -65,14 +84,13 @@ void nsFrameLoaderOwner::ChangeRemotenes
       fm->ActivateRemoteFrameIfNeeded(*owner);
     }
   }
 
   // Assuming this element is a XULFrameElement, once we've reset our
   // FrameLoader, fire an event to act like we've recreated ourselves, similar
   // to what XULFrameElement does after rebinding to the tree.
   // ChromeOnlyDispatch is turns on to make sure this isn't fired into content.
-  (new mozilla::AsyncEventDispatcher(owner,
-                                     NS_LITERAL_STRING("XULFrameLoaderCreated"),
-                                     mozilla::CanBubble::eYes,
-                                     mozilla::ChromeOnlyDispatch::eYes))
+  (new mozilla::AsyncEventDispatcher(
+       owner, NS_LITERAL_STRING("XULFrameLoaderCreated"),
+       mozilla::CanBubble::eYes, mozilla::ChromeOnlyDispatch::eYes))
       ->RunDOMEventWhenSafe();
 }
--- a/dom/ipc/BrowserBridgeParent.cpp
+++ b/dom/ipc/BrowserBridgeParent.cpp
@@ -177,16 +177,21 @@ IPCResult BrowserBridgeParent::RecvDispa
   layers::InputAPZContext context(
       layers::ScrollableLayerGuid(event.mLayersId, 0,
                                   layers::ScrollableLayerGuid::NULL_SCROLL_ID),
       0, nsEventStatus_eIgnore);
   mBrowserParent->SendRealMouseEvent(event);
   return IPC_OK();
 }
 
+IPCResult BrowserBridgeParent::RecvSkipBrowsingContextDetach() {
+  mBrowserParent->SkipBrowsingContextDetach();
+  return IPC_OK();
+}
+
 IPCResult BrowserBridgeParent::RecvActivate() {
   mBrowserParent->Activate();
   return IPC_OK();
 }
 
 IPCResult BrowserBridgeParent::RecvDeactivate() {
   mBrowserParent->Deactivate();
   return IPC_OK();
--- a/dom/ipc/BrowserBridgeParent.h
+++ b/dom/ipc/BrowserBridgeParent.h
@@ -54,16 +54,18 @@ class BrowserBridgeParent : public PBrow
                                            const LayersObserverEpoch& aEpoch);
 
   mozilla::ipc::IPCResult RecvNavigateByKey(const bool& aForward,
                                             const bool& aForDocumentNavigation);
 
   mozilla::ipc::IPCResult RecvDispatchSynthesizedMouseEvent(
       const WidgetMouseEvent& aEvent);
 
+  mozilla::ipc::IPCResult RecvSkipBrowsingContextDetach();
+
   mozilla::ipc::IPCResult RecvActivate();
 
   mozilla::ipc::IPCResult RecvDeactivate();
 
   mozilla::ipc::IPCResult RecvSetIsUnderHiddenEmbedderElement(
       const bool& aIsUnderHiddenEmbedderElement);
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
--- a/dom/ipc/BrowserChild.cpp
+++ b/dom/ipc/BrowserChild.cpp
@@ -1047,16 +1047,27 @@ BrowserChild::~BrowserChild() {
   nsCOMPtr<nsIWebBrowser> webBrowser = do_QueryInterface(WebNavigation());
   if (webBrowser) {
     webBrowser->SetContainerWindow(nullptr);
   }
 
   mozilla::DropJSObjects(this);
 }
 
+mozilla::ipc::IPCResult BrowserChild::RecvSkipBrowsingContextDetach() {
+  nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation());
+  if (!docShell) {
+    return IPC_OK();
+  }
+  RefPtr<nsDocShell> docshell = nsDocShell::Cast(docShell);
+  MOZ_ASSERT(docshell);
+  docshell->SkipBrowsingContextDetach();
+  return IPC_OK();
+}
+
 mozilla::ipc::IPCResult BrowserChild::RecvLoadURL(const nsCString& aURI,
                                                   const ShowInfo& aInfo) {
   if (!mDidLoadURLInit) {
     mDidLoadURLInit = true;
     if (!InitBrowserChildMessageManager()) {
       return IPC_FAIL_NO_REASON(this);
     }
 
--- a/dom/ipc/BrowserChild.h
+++ b/dom/ipc/BrowserChild.h
@@ -550,16 +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);
 
+  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/ipc/BrowserParent.cpp
+++ b/dom/ipc/BrowserParent.cpp
@@ -531,17 +531,17 @@ void BrowserParent::SetOwnerElement(Elem
   }
 
   if (mRenderFrame.IsInitialized()) {
     mRenderFrame.OwnerContentChanged();
   }
 
   // Set our BrowsingContext's embedder if we're not embedded within a
   // BrowserBridgeParent.
-  if (!GetBrowserBridgeParent() && mBrowsingContext) {
+  if (!GetBrowserBridgeParent() && mBrowsingContext && mFrameElement) {
     mBrowsingContext->SetEmbedderElement(mFrameElement);
   }
 
   VisitChildren([aElement](BrowserBridgeParent* aBrowser) {
     aBrowser->GetBrowserParent()->SetOwnerElement(aElement);
   });
 }
 
@@ -3739,16 +3739,22 @@ BrowserParent::StopApzAutoscroll(nsViewI
                                gfxUtils::GetContentRenderRoot());
 
       widget->StopAsyncAutoscroll(guid);
     }
   }
   return NS_OK;
 }
 
+void BrowserParent::SkipBrowsingContextDetach() {
+  RefPtr<nsFrameLoader> fl = GetFrameLoader();
+  MOZ_ASSERT(fl);
+  fl->SkipBrowsingContextDetach();
+}
+
 mozilla::ipc::IPCResult BrowserParent::RecvLookUpDictionary(
     const nsString& aText, nsTArray<FontRange>&& aFontRangeArray,
     const bool& aIsVertical, const LayoutDeviceIntPoint& aPoint) {
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
     return IPC_OK();
   }
 
--- a/dom/ipc/BrowserParent.h
+++ b/dom/ipc/BrowserParent.h
@@ -673,16 +673,18 @@ class BrowserParent final : public PBrow
   void LiveResizeStarted() override;
   void LiveResizeStopped() override;
 
   void SetReadyToHandleInputEvents() { mIsReadyToHandleInputEvents = true; }
   bool IsReadyToHandleInputEvents() { return mIsReadyToHandleInputEvents; }
 
   void NavigateByKey(bool aForward, bool aForDocumentNavigation);
 
+  void SkipBrowsingContextDetach();
+
  protected:
   bool ReceiveMessage(
       const nsString& aMessage, bool aSync, ipc::StructuredCloneData* aData,
       mozilla::jsipc::CpowHolder* aCpows, nsIPrincipal* aPrincipal,
       nsTArray<ipc::StructuredCloneData>* aJSONRetVal = nullptr);
 
   mozilla::ipc::IPCResult RecvAsyncAuthPrompt(const nsCString& aUri,
                                               const nsString& aRealm,
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -2293,16 +2293,18 @@ void ContentChild::ActorDestroy(ActorDes
   BlobURLProtocolHandler::RemoveDataEntries();
 
   mSharedData = nullptr;
 
   mAlertObservers.Clear();
 
   mIdleObservers.Clear();
 
+  mBrowsingContextGroupHolder.Clear();
+
   nsCOMPtr<nsIConsoleService> svc(do_GetService(NS_CONSOLESERVICE_CONTRACTID));
   if (svc) {
     svc->UnregisterListener(mConsoleListener);
     mConsoleListener->mChild = nullptr;
   }
   mIsAlive = false;
 
   CrashReporterClient::DestroySingleton();
@@ -3793,17 +3795,16 @@ mozilla::ipc::IPCResult ContentChild::Re
   aContext->RestoreChildren(std::move(children), /* aFromIPC */ true);
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult ContentChild::RecvRegisterBrowsingContextGroup(
     nsTArray<BrowsingContext::IPCInitializer>&& aInits) {
   RefPtr<BrowsingContextGroup> group = new BrowsingContextGroup();
-
   // Each of the initializers in aInits is sorted in pre-order, so our parent
   // should always be available before the element itself.
   for (auto& init : aInits) {
 #ifdef DEBUG
     RefPtr<BrowsingContext> existing = BrowsingContext::Get(init.mId);
     MOZ_ASSERT(!existing, "BrowsingContext must not exist yet!");
 
     RefPtr<BrowsingContext> parent = init.GetParent();
@@ -3906,16 +3907,21 @@ mozilla::ipc::IPCResult ContentChild::Re
     BrowsingContext* aContext, BrowsingContext::Transaction&& aTransaction,
     BrowsingContext::FieldEpochs&& aEpochs) {
   if (aContext) {
     aTransaction.Apply(aContext, nullptr, &aEpochs);
   }
   return IPC_OK();
 }
 
+void ContentChild::HoldBrowsingContextGroup(BrowsingContextGroup* aBCG) {
+  RefPtr<BrowsingContextGroup> bcgPtr(aBCG);
+  mBrowsingContextGroupHolder.AppendElement(bcgPtr);
+}
+
 }  // namespace dom
 
 #if defined(__OpenBSD__) && defined(MOZ_SANDBOX)
 #  include <unistd.h>
 
 static LazyLogModule sPledgeLog("SandboxPledge");
 
 bool StartOpenBSDSandbox(GeckoProcessType type) {
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -669,16 +669,18 @@ class ContentChild final : public PConte
       const uint32_t& aRegistrarId, nsIURI* aURI, const uint32_t& aNewLoadFlags,
       const Maybe<LoadInfoArgs>& aLoadInfoForwarder, const uint64_t& aChannelId,
       nsIURI* aOriginalURI, const uint64_t& aIdentifier,
       const uint32_t& aRedirectMode);
 
   mozilla::ipc::IPCResult RecvStartDelayedAutoplayMediaComponents(
       BrowsingContext* aContext);
 
+  void HoldBrowsingContextGroup(BrowsingContextGroup* aBCG);
+
 #ifdef NIGHTLY_BUILD
   // Fetch the current number of pending input events.
   //
   // NOTE: This method performs an atomic read, and is safe to call from all
   // threads.
   uint32_t GetPendingInputEvents() { return mPendingInputEvents; }
 #endif
 
@@ -809,16 +811,18 @@ class ContentChild final : public PConte
 #ifdef NIGHTLY_BUILD
   // NOTE: This member is atomic because it can be accessed from
   // off-main-thread.
   mozilla::Atomic<uint32_t> mPendingInputEvents;
 #endif
 
   uint32_t mNetworkLinkType = 0;
 
+  nsTArray<RefPtr<BrowsingContextGroup>> mBrowsingContextGroupHolder;
+
   DISALLOW_EVIL_CONSTRUCTORS(ContentChild);
 };
 
 uint64_t NextWindowID();
 
 }  // namespace dom
 }  // namespace mozilla
 
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -943,16 +943,17 @@ child:
      */
     async SetWidgetNativeData(WindowsHandle aHandle);
 
     /**
      * Requests the content blocking log, which is sent back in response.
      */
     async GetContentBlockingLog() returns(nsCString log, bool success);
 
+    async SkipBrowsingContextDetach();
 parent:
     /** Records a history visit. */
     async VisitURI(URIParams aURI, URIParams? aLastVisitedURI,
                    uint32_t aFlags);
 
     /** Fetches the visited status for an array of URIs (Android-only). */
     async QueryVisitedState(URIParams[] aURIs);
 
--- a/dom/ipc/PBrowserBridge.ipdl
+++ b/dom/ipc/PBrowserBridge.ipdl
@@ -69,12 +69,14 @@ parent:
   /**
    * Sending an activate message moves focus to the iframe.
    */
   async Activate();
 
   async Deactivate();
 
   async SetIsUnderHiddenEmbedderElement(bool aIsUnderHiddenEmbedderElement);
+
+  async SkipBrowsingContextDetach();
 };
 
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/ipc/WindowGlobalParent.cpp
+++ b/dom/ipc/WindowGlobalParent.cpp
@@ -77,20 +77,16 @@ void WindowGlobalParent::Init(const Wind
   ContentParentId processId(0);
   if (!mInProcess) {
     processId = static_cast<ContentParent*>(Manager()->Manager())->ChildID();
   }
 
   mBrowsingContext = CanonicalBrowsingContext::Cast(aInit.browsingContext());
   MOZ_ASSERT(mBrowsingContext);
 
-  // XXX(nika): This won't be the case soon, but for now this is a good
-  // assertion as we can't switch processes. We should relax this eventually.
-  MOZ_ASSERT(mBrowsingContext->IsOwnedByProcess(processId));
-
   // Attach ourself to the browsing context.
   mBrowsingContext->RegisterWindowGlobal(this);
 
   // If there is no current window global, assume we're about to become it
   // optimistically.
   if (!mBrowsingContext->GetCurrentWindowGlobal()) {
     mBrowsingContext->SetCurrentWindowGlobal(this);
   }
--- 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]
--- a/layout/printing/nsPrintJob.cpp
+++ b/layout/printing/nsPrintJob.cpp
@@ -1882,22 +1882,22 @@ nsresult nsPrintJob::InitPrintDocConstru
   // So, we should grab it with local variable.
   RefPtr<nsPrintData> printData = mPrt;
   rv = ReflowDocList(printData->mPrintObject, DoSetPixelScale());
   NS_ENSURE_SUCCESS(rv, rv);
 
   FirePrintPreviewUpdateEvent();
 
   if (mLoadCounter == 0) {
-    AfterNetworkPrint(aHandleError);
+    ResumePrintAfterResourcesLoaded(aHandleError);
   }
   return rv;
 }
 
-nsresult nsPrintJob::AfterNetworkPrint(bool aHandleError) {
+nsresult nsPrintJob::ResumePrintAfterResourcesLoaded(bool aCleanupOnError) {
   // If Destroy() has already been called, mPtr is nullptr.  Then, the instance
   // needs to do nothing anymore in this method.
   // Note: it shouldn't be possible for mPrt->mPrintObject to be null; we
   // just check it for good measure, as we check its owner.
   // Note: it shouldn't be possible for mPrt->mPrintObject->mDocShell to be
   // null; we just check it for good measure, as we check its owner.
   if (!mPrt || NS_WARN_IF(!mPrt->mPrintObject) ||
       NS_WARN_IF(!mPrt->mPrintObject->mDocShell)) {
@@ -1913,19 +1913,19 @@ nsresult nsPrintJob::AfterNetworkPrint(b
   nsresult rv;
   if (mIsDoingPrinting) {
     rv = DocumentReadyForPrinting();
   } else {
     rv = FinishPrintPreview();
   }
 
   /* cleaup on failure + notify user */
-  if (aHandleError && NS_FAILED(rv)) {
+  if (aCleanupOnError && NS_FAILED(rv)) {
     NS_WARNING_ASSERTION(rv == NS_ERROR_ABORT,
-                         "nsPrintJob::AfterNetworkPrint failed");
+                         "nsPrintJob::ResumePrintAfterResourcesLoaded failed");
     CleanupOnFailure(rv, !mIsDoingPrinting);
   }
 
   return rv;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsIWebProgressListener
@@ -1942,17 +1942,17 @@ nsPrintJob::OnStateChange(nsIWebProgress
     ++mLoadCounter;
   } else if (aStateFlags & STATE_STOP) {
     mDidLoadDataForPrinting = true;
     --mLoadCounter;
 
     // If all resources are loaded, then do a small timeout and if there
     // are still no new requests, then another reflow.
     if (mLoadCounter == 0) {
-      AfterNetworkPrint(true);
+      ResumePrintAfterResourcesLoaded(/* aCleanupOnError */ true);
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsPrintJob::OnProgressChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
                              int32_t aCurSelfProgress, int32_t aMaxSelfProgress,
--- a/layout/printing/nsPrintJob.h
+++ b/layout/printing/nsPrintJob.h
@@ -195,17 +195,26 @@ class nsPrintJob final : public nsIObser
   nsresult DoCommonPrint(bool aIsPrintPreview, nsIPrintSettings* aPrintSettings,
                          nsIWebProgressListener* aWebProgressListener,
                          mozilla::dom::Document* aDoc);
 
   void FirePrintCompletionEvent();
 
   void DisconnectPagePrintTimer();
 
-  nsresult AfterNetworkPrint(bool aHandleError);
+  /**
+   * This method is called to resume printing after all outstanding resources
+   * referenced by the static clone have finished loading.  (It is possibly
+   * called synchronously if there are no resources to load.)  While a static
+   * clone will generally just be able to reference the (already loaded)
+   * resources that the original document references, the static clone may
+   * reference additional resources that have not previously been loaded
+   * (if it has a 'print' style sheet, for example).
+   */
+  nsresult ResumePrintAfterResourcesLoaded(bool aCleanupOnError);
 
   nsresult SetRootView(nsPrintObject* aPO, bool& aDoReturn,
                        bool& aDocumentIsTopLevel, nsSize& aAdjSize);
   nsView* GetParentViewForRoot();
   bool DoSetPixelScale();
   void UpdateZoomRatio(nsPrintObject* aPO, bool aSetPixelScale);
   MOZ_CAN_RUN_SCRIPT_BOUNDARY
   nsresult ReconstructAndReflow(bool aDoSetPixelScale);
new file mode 100755
--- /dev/null
+++ b/layout/style/crashtests/1340248.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <script>
+            o1 = document.createElement('style');
+            o1.textContent = "* { position: absolute; }";
+            o1.className = 'c2';
+            document.head.appendChild(o1);
+            document.styleSheets[0].insertRule("*::first-line { border-inline-end-style: solid; }", 0);
+            document.styleSheets[0].insertRule("* { display: table-row ; }", 0);
+            document.styleSheets[0].insertRule(".c2::first-line { border-style: none ; }", 0);
+        </script>
+    </head>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/style/crashtests/1353312.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<style>
+  body { float: left }
+  div::first-line {
+    border-inline-start: 0;
+  }
+  div.boom::first-line {
+    border: 0;
+  }
+</style>
+
+<div>x</div>
+<div class="boom">x</div>
+<div>x</div>
--- a/layout/style/crashtests/crashtests.list
+++ b/layout/style/crashtests/crashtests.list
@@ -171,19 +171,21 @@ load 1314531.html
 load 1315889-1.html
 load 1315894-1.html
 load 1319072-1.html
 HTTP load 1320423-1.html
 load 1321357-1.html
 load 1328535-1.html
 load 1331272.html
 HTTP load 1333001-1.html
+load 1340248.html
 pref(dom.animations-api.core.enabled,true) pref(dom.animations-api.implicit-keyframes.enabled,true) load 1340344.html
 load 1342316-1.html
 load 1344210.html
+load 1353312.html
 load 1356601-1.html
 load 1364139-1.html
 load 1370793-1.xhtml
 skip-if(Android) load 1371450-1.html
 load 1374175-1.html
 load 1375812-1.html
 load 1377053-1.html
 load 1377256-1.html
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -6036,14 +6036,18 @@ pref("dom.datatransfer.mozAtAPIs", true)
 
 // External.AddSearchProvider is deprecated and it will be removed in the next
 // cycles.
 pref("dom.sidebar.enabled", true);
 
 // Turn on fission frameloader swapping
 pref("fission.rebuild_frameloaders_on_remoteness_change", true);
 
+// If true, preserve browsing contexts between process swaps. Should be set to
+// true in bug 1550571.
+pref("fission.preserve_browsing_contexts", false);
+
 // Support for legacy customizations that rely on checking the
 // user profile directory for these stylesheets:
 //  * userContent.css
 //  * userChrome.css
 pref("toolkit.legacyUserProfileCustomizations.stylesheets", false);
 
--- a/toolkit/components/remotebrowserutils/tests/browser/browser_oopProcessSwap.js
+++ b/toolkit/components/remotebrowserutils/tests/browser/browser_oopProcessSwap.js
@@ -40,17 +40,20 @@ add_task(async function oopProcessSwap()
 
       return {
         location: data.location,
         browsingContextId: iframe.browsingContext.id,
       };
     });
 
     is(browser.browsingContext.getChildren().length, 1);
-    todo(frameId == oopinfo.browsingContextId, "BrowsingContext should not have changed");
+
+    if (Services.prefs.getBoolPref("fission.preserve_browsing_contexts")) {
+      is(frameId, oopinfo.browsingContextId, `BrowsingContext should not have changed (${frameId} != ${oopinfo.browsingContextId})`);
+    }
     is(oopinfo.location, WEB, "correct location");
   });
 
   await BrowserTestUtils.closeWindow(win);
 });
 
 add_task(async function oopOriginProcessSwap() {
   const COM_DUMMY = httpURL("dummy_page.html", "https://example.com/");
@@ -94,15 +97,17 @@ add_task(async function oopOriginProcess
 
       return {
         location: data.location,
         browsingContextId: iframe.browsingContext.id,
       };
     });
 
     is(browser.browsingContext.getChildren().length, 1);
-    todo(frameId == oopinfo.browsingContextId, "BrowsingContext should not have changed");
+    if (Services.prefs.getBoolPref("fission.preserve_browsing_contexts")) {
+      is(frameId, oopinfo.browsingContextId, `BrowsingContext should not have changed (${frameId} != ${oopinfo.browsingContextId})`);
+    }
     is(oopinfo.location, ORG_POSTMSG, "correct location");
   });
 
   await BrowserTestUtils.closeWindow(win);
 });