Bug 1629866 - Store ChildSHistory on the BrowsingContext. r=nika,smaug
☠☠ backed out by 02f46362fdb0 ☠ ☠
authorPeter Van der Beken <peterv@propagandism.org>
Wed, 13 May 2020 13:41:16 +0000
changeset 529614 fe2d4790b73a0c404e2516c7c4aa510807fc318f
parent 529613 9b1d094b12a50baa38415260ab6b624a3b7ac3ad
child 529615 efc42c73c3beebfc4044379554e0ec6b8f9846d9
push id115809
push userpvanderbeken@mozilla.com
push dateWed, 13 May 2020 13:52:00 +0000
treeherderautoland@fe2d4790b73a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnika, smaug
bugs1629866
milestone78.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 1629866 - Store ChildSHistory on the BrowsingContext. r=nika,smaug Differential Revision: https://phabricator.services.mozilla.com/D70997
docshell/base/BrowsingContext.cpp
docshell/base/BrowsingContext.h
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
docshell/base/nsIDocShell.idl
docshell/shistory/ChildSHistory.cpp
docshell/shistory/ChildSHistory.h
docshell/shistory/SHistoryChild.cpp
docshell/shistory/SHistoryParent.cpp
docshell/shistory/SHistoryParent.h
docshell/shistory/nsISHistory.idl
docshell/shistory/nsSHistory.cpp
docshell/shistory/nsSHistory.h
dom/base/nsFrameLoader.cpp
toolkit/components/browser/nsWebBrowser.cpp
--- a/docshell/base/BrowsingContext.cpp
+++ b/docshell/base/BrowsingContext.cpp
@@ -399,16 +399,19 @@ BrowsingContext::BrowsingContext(WindowC
 void BrowsingContext::SetDocShell(nsIDocShell* aDocShell) {
   // XXX(nika): We should communicate that we are now an active BrowsingContext
   // process to the parent & do other validation here.
   MOZ_DIAGNOSTIC_ASSERT(mEverAttached);
   MOZ_RELEASE_ASSERT(aDocShell->GetBrowsingContext() == this);
   mDocShell = aDocShell;
   mDanglingRemoteOuterProxies = !mIsInProcess;
   mIsInProcess = true;
+  if (mChildSessionHistory) {
+    mChildSessionHistory->SetIsInProcess(true);
+  }
 }
 
 // This class implements a callback that will return the remote window proxy for
 // mBrowsingContext in that compartment, if it has one. It also removes the
 // proxy from the map, because the object will be transplanted into another kind
 // of object.
 class MOZ_STACK_CLASS CompartmentRemoteProxyTransplantCallback
     : public js::CompartmentTransplantCallback {
@@ -512,16 +515,20 @@ void BrowsingContext::Attach(bool aFromI
   } else {
     mGroup->Toplevels().AppendElement(this);
   }
 
   if (GetIsPopupSpam()) {
     PopupBlocker::RegisterOpenPopupSpam();
   }
 
+  if (IsTop() && GetHasSessionHistory()) {
+    CreateChildSHistory();
+  }
+
   if (XRE_IsContentProcess() && !aFromIPC) {
     // Send attach to our parent if we need to.
     ContentChild::GetSingleton()->SendCreateBrowsingContext(
         mGroup->Id(), GetIPCInitializer());
   } else if (XRE_IsParentProcess()) {
     mGroup->EachOtherParent(aOriginProcess, [&](ContentParent* aParent) {
       MOZ_DIAGNOSTIC_ASSERT(IsContent(),
                             "chrome BCG cannot be synced to content process");
@@ -621,16 +628,21 @@ void BrowsingContext::PrepareForProcessC
 
   mIsInProcess = false;
   mUserGestureStart = TimeStamp();
 
   // NOTE: For now, clear our nsDocShell reference, as we're primarily in a
   // different process now. This may need to change in the future with
   // Cross-Process BFCache.
   mDocShell = nullptr;
+  if (mChildSessionHistory) {
+    // This can be removed once session history is stored exclusively in the
+    // parent process.
+    mChildSessionHistory->SetIsInProcess(false);
+  }
 
   if (!mWindowProxy) {
     return;
   }
 
   // We have to go through mWindowProxy rather than calling GetDOMWindow() on
   // mDocShell because the mDocshell reference gets cleared immediately after
   // the window is closed.
@@ -1381,26 +1393,26 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Br
 
   if (tmp->GetIsPopupSpam()) {
     PopupBlocker::UnregisterOpenPopupSpam();
     // NOTE: Doesn't use SetIsPopupSpam, as it will be set all processes
     // automatically.
     tmp->mFields.SetWithoutSyncing<IDX_IsPopupSpam>(false);
   }
 
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell, mParentWindow, mGroup,
-                                  mEmbedderElement, mWindowContexts,
-                                  mCurrentWindowContext, mSessionStorageManager)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(
+      mDocShell, mParentWindow, mGroup, mEmbedderElement, mWindowContexts,
+      mCurrentWindowContext, mSessionStorageManager, mChildSessionHistory)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(BrowsingContext)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
       mDocShell, mParentWindow, mGroup, mEmbedderElement, mWindowContexts,
-      mCurrentWindowContext, mSessionStorageManager)
+      mCurrentWindowContext, mSessionStorageManager, mChildSessionHistory)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 class RemoteLocationProxy
     : public RemoteObjectProxy<BrowsingContext::LocationProxy,
                                Location_Binding::sCrossOriginAttributes,
                                Location_Binding::sCrossOriginMethods> {
  public:
   typedef RemoteObjectProxy Base;
@@ -2194,16 +2206,61 @@ void BrowsingContext::AddDeprioritizedLo
 
   RefPtr<DeprioritizedLoadRunner> runner = new DeprioritizedLoadRunner(aRunner);
   mDeprioritizedLoadRunner.insertBack(runner);
   NS_DispatchToCurrentThreadQueue(
       runner.forget(), StaticPrefs::page_load_deprioritization_period(),
       EventQueuePriority::Idle);
 }
 
+void BrowsingContext::InitSessionHistory() {
+  MOZ_ASSERT(!IsDiscarded());
+  MOZ_ASSERT(IsTop());
+  MOZ_ASSERT(EverAttached());
+
+  if (!GetHasSessionHistory()) {
+    SetHasSessionHistory(true);
+
+    // If the top browsing context (this one) is loaded in this process then we
+    // also create the session history implementation for the child process.
+    // This can be removed once session history is stored exclusively in the
+    // parent process.
+    mChildSessionHistory->SetIsInProcess(mDocShell);
+  }
+}
+
+ChildSHistory* BrowsingContext::GetChildSessionHistory() {
+  // For now we're checking that the session history object for the child
+  // process is available before returning the ChildSHistory object, because
+  // it is the actual implementation that ChildSHistory forwards to. This can
+  // be removed once session history is stored exclusively in the parent
+  // process.
+  return mChildSessionHistory && mChildSessionHistory->IsInProcess()
+             ? mChildSessionHistory.get()
+             : nullptr;
+}
+
+void BrowsingContext::CreateChildSHistory() {
+  MOZ_ASSERT(IsTop());
+
+  // Because session history is global in a browsing context tree, every process
+  // that has access to a browsing context tree needs access to its session
+  // history. That is why we create the ChildSHistory object in every process
+  // where we have access to this browsing context (which is the top one).
+  mChildSessionHistory = new ChildSHistory(this);
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_HasSessionHistory>,
+                             bool aOldValue) {
+  MOZ_ASSERT(GetHasSessionHistory() || !aOldValue,
+             "We don't support turning off session history.");
+
+  CreateChildSHistory();
+}
+
 }  // namespace dom
 
 namespace ipc {
 
 void IPDLParamTraits<dom::MaybeDiscarded<dom::BrowsingContext>>::Write(
     IPC::Message* aMsg, IProtocol* aActor,
     const dom::MaybeDiscarded<dom::BrowsingContext>& aParam) {
   MOZ_DIAGNOSTIC_ASSERT(!aParam.GetMaybeDiscarded() ||
--- a/docshell/base/BrowsingContext.h
+++ b/docshell/base/BrowsingContext.h
@@ -56,16 +56,17 @@ class IProtocol;
 template <typename T>
 struct IPDLParamTraits;
 }  // namespace ipc
 
 namespace dom {
 class BrowsingContent;
 class BrowsingContextGroup;
 class CanonicalBrowsingContext;
+class ChildSHistory;
 class ContentParent;
 class Element;
 template <typename>
 struct Nullable;
 template <typename T>
 class Sequence;
 class StructuredCloneHolder;
 class WindowContext;
@@ -127,17 +128,21 @@ class WindowProxyHolder;
   FIELD(UserAgentOverride, nsString)                                         \
   FIELD(EmbedderElementType, Maybe<nsString>)                                \
   FIELD(MessageManagerGroup, nsString)                                       \
   FIELD(MaxTouchPointsOverride, uint8_t)                                     \
   FIELD(FullZoom, float)                                                     \
   FIELD(WatchedByDevToolsInternal, bool)                                     \
   FIELD(TextZoom, float)                                                     \
   /* See nsIRequest for possible flags. */                                   \
-  FIELD(DefaultLoadFlags, uint32_t)
+  FIELD(DefaultLoadFlags, uint32_t)                                          \
+  /* Signals that session history is enabled for this browsing context tree. \
+   * This is only ever set to true on the top BC, so consumers need to get   \
+   * the value from the top BC! */                                           \
+  FIELD(HasSessionHistory, bool)
 
 // BrowsingContext, in this context, is the cross process replicated
 // environment in which information about documents is stored. In
 // particular the tree structure of nested browsing contexts is
 // represented by the tree of BrowsingContexts.
 //
 // The tree of BrowsingContexts is created in step with its
 // corresponding nsDocShell, and when nsDocShells are connected
@@ -592,16 +597,23 @@ class BrowsingContext : public nsILoadCo
   RefPtr<SessionStorageManager> GetSessionStorageManager();
 
   bool PendingInitialization() const { return mPendingInitialization; };
   void SetPendingInitialization(bool aVal) { mPendingInitialization = aVal; };
 
   const OriginAttributes& OriginAttributesRef() { return mOriginAttributes; }
   nsresult SetOriginAttributes(const OriginAttributes& aAttrs);
 
+  // This should only be called on the top browsing context.
+  void InitSessionHistory();
+
+  // This will only ever return a non-null value if called on the top browsing
+  // context.
+  ChildSHistory* GetChildSessionHistory();
+
  protected:
   virtual ~BrowsingContext();
   BrowsingContext(WindowContext* aParentWindow, BrowsingContextGroup* aGroup,
                   uint64_t aBrowsingContextId, Type aType,
                   FieldTuple&& aFields);
 
  private:
   void Attach(bool aFromIPC, ContentParent* aOriginProcess);
@@ -732,16 +744,18 @@ class BrowsingContext : public nsILoadCo
 
   bool CanSet(FieldIndex<IDX_DefaultLoadFlags>,
               const uint32_t& aDefaultLoadFlags, ContentParent* aSource);
   void DidSet(FieldIndex<IDX_DefaultLoadFlags>);
 
   bool CanSet(FieldIndex<IDX_UseGlobalHistory>, const bool& aUseGlobalHistory,
               ContentParent* aSource);
 
+  void DidSet(FieldIndex<IDX_HasSessionHistory>, bool aOldValue);
+
   template <size_t I, typename T>
   bool CanSet(FieldIndex<I>, const T&, ContentParent*) {
     return true;
   }
 
   // Overload `DidSet` to get notifications for a particular field being set.
   //
   // You can also overload the variant that gets the old value if you need it.
@@ -756,16 +770,18 @@ class BrowsingContext : public nsILoadCo
   // True if the process attemping to set field is the same as the owning
   // process.
   bool CheckOnlyOwningProcessCanSet(ContentParent* aSource);
 
   // True if the process attempting to set field is the same as the embedder's
   // process.
   bool CheckOnlyEmbedderCanSet(ContentParent* aSource);
 
+  void CreateChildSHistory();
+
   // Type of BrowsingContent
   const Type mType;
 
   // Unique id identifying BrowsingContext
   const uint64_t mBrowsingContextId;
 
   RefPtr<BrowsingContextGroup> mGroup;
   RefPtr<WindowContext> mParentWindow;
@@ -850,17 +866,18 @@ class BrowsingContext : public nsILoadCo
     }
 
    private:
     RefPtr<nsIRunnable> mInner;
   };
 
   mozilla::LinkedList<DeprioritizedLoadRunner> mDeprioritizedLoadRunner;
 
-  RefPtr<dom::SessionStorageManager> mSessionStorageManager;
+  RefPtr<SessionStorageManager> mSessionStorageManager;
+  RefPtr<ChildSHistory> mChildSessionHistory;
 };
 
 /**
  * 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
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -418,20 +418,16 @@ nsDocShell::nsDocShell(BrowsingContext* 
 nsDocShell::~nsDocShell() {
   MOZ_ASSERT(!mObserved);
 
   // Avoid notifying observers while we're in the dtor.
   mIsBeingDestroyed = true;
 
   Destroy();
 
-  if (mSessionHistory) {
-    mSessionHistory->LegacySHistory()->ClearRootBrowsingContext();
-  }
-
   if (mContentViewer) {
     mContentViewer->Close(nullptr);
     mContentViewer->Destroy();
     mContentViewer = nullptr;
   }
 
   MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, ("DOCSHELL %p destroyed\n", this));
 
@@ -539,17 +535,17 @@ void nsDocShell::DestroyChildren() {
     }
   }
 
   nsDocLoader::DestroyChildren();
 }
 
 NS_IMPL_CYCLE_COLLECTION_WEAK_PTR_INHERITED(nsDocShell, nsDocLoader,
                                             mScriptGlobal, mInitialClientSource,
-                                            mSessionHistory, mBrowsingContext,
+                                            mBrowsingContext,
                                             mChromeEventHandler)
 
 NS_IMPL_ADDREF_INHERITED(nsDocShell, nsDocLoader)
 NS_IMPL_RELEASE_INHERITED(nsDocShell, nsDocLoader)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDocShell)
   NS_INTERFACE_MAP_ENTRY(nsIDocShell)
   NS_INTERFACE_MAP_ENTRY(nsIDocShellTreeItem)
@@ -2950,18 +2946,18 @@ nsDocShell::AddChildSHEntry(nsISHEntry* 
 }
 
 nsresult nsDocShell::AddChildSHEntryInternal(nsISHEntry* aCloneRef,
                                              nsISHEntry* aNewEntry,
                                              int32_t aChildOffset,
                                              uint32_t aLoadType,
                                              bool aCloneChildren) {
   nsresult rv = NS_OK;
-  if (mSessionHistory) {
-    rv = mSessionHistory->LegacySHistory()->AddChildSHEntryHelper(
+  if (GetSessionHistory()) {
+    rv = GetSessionHistory()->LegacySHistory()->AddChildSHEntryHelper(
         aCloneRef, aNewEntry, mBrowsingContext, aCloneChildren);
   } else {
     /* Just pass this along */
     nsCOMPtr<nsIDocShell> parent =
         do_QueryInterface(GetAsSupports(mParent), &rv);
     if (parent) {
       rv = static_cast<nsDocShell*>(parent.get())
                ->AddChildSHEntryInternal(aCloneRef, aNewEntry, aChildOffset,
@@ -3005,23 +3001,18 @@ nsresult nsDocShell::AddChildSHEntryToPa
     }
   }
 
   return rv;
 }
 
 NS_IMETHODIMP
 nsDocShell::RemoveFromSessionHistory() {
-  nsCOMPtr<nsIDocShellTreeItem> root;
-  GetInProcessSameTypeRootTreeItem(getter_AddRefs(root));
-  nsCOMPtr<nsIWebNavigation> rootAsWebnav = do_QueryInterface(root);
-  if (!rootAsWebnav) {
-    return NS_OK;
-  }
-  RefPtr<ChildSHistory> sessionHistory = rootAsWebnav->GetSessionHistory();
+  RefPtr<ChildSHistory> sessionHistory =
+      mBrowsingContext->Top()->GetChildSessionHistory();
   if (!sessionHistory) {
     return NS_OK;
   }
   int32_t index = sessionHistory->Index();
   AutoTArray<nsID, 16> ids({mHistoryID});
   sessionHistory->LegacySHistory()->RemoveEntries(ids, index);
   return NS_OK;
 }
@@ -4077,35 +4068,19 @@ nsDocShell::GetCurrentURI(nsIURI** aURI)
   NS_ENSURE_ARG_POINTER(aURI);
 
   nsCOMPtr<nsIURI> uri = mCurrentURI;
   uri.forget(aURI);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDocShell::InitSessionHistory() {
-  MOZ_ASSERT(!mIsBeingDestroyed);
-
-  // Make sure that we are the root DocShell, and set a handle to root docshell
-  // in the session history.
-  nsCOMPtr<nsIDocShellTreeItem> root;
-  GetInProcessSameTypeRootTreeItem(getter_AddRefs(root));
-  if (root != this) {
-    return NS_ERROR_FAILURE;
-  }
-
-  mSessionHistory = new ChildSHistory(this);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 nsDocShell::GetSessionHistoryXPCOM(nsISupports** aSessionHistory) {
   NS_ENSURE_ARG_POINTER(aSessionHistory);
-  RefPtr<ChildSHistory> shistory = mSessionHistory;
+  RefPtr<ChildSHistory> shistory = GetSessionHistory();
   shistory.forget(aSessionHistory);
   return NS_OK;
 }
 
 //*****************************************************************************
 // nsDocShell::nsIWebPageDescriptor
 //*****************************************************************************
 
@@ -4316,22 +4291,21 @@ nsDocShell::Destroy() {
   mParentWidget = nullptr;
   mCurrentURI = nullptr;
 
   if (mScriptGlobal) {
     mScriptGlobal->DetachFromDocShell(!mWillChangeProcess);
     mScriptGlobal = nullptr;
   }
 
-  if (mSessionHistory) {
+  if (GetSessionHistory()) {
     // 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;
+    GetSessionHistory()->EvictLocalContentViewers();
   }
 
   if (mWillChangeProcess) {
     mBrowsingContext->PrepareForProcessChange();
   }
 
   SetTreeOwner(nullptr);
 
@@ -7515,23 +7489,23 @@ nsresult nsDocShell::CreateContentViewer
     if (failedURI) {
       errorOnLocationChangeNeeded =
           OnNewURI(failedURI, failedChannel, triggeringPrincipal, nullptr,
                    nullptr, mLoadType, nullptr, false, false, false);
     }
 
     // Be sure to have a correct mLSHE, it may have been cleared by
     // EndPageLoad. See bug 302115.
-    if (mSessionHistory && !mLSHE) {
-      int32_t idx = mSessionHistory->LegacySHistory()->GetRequestedIndex();
+    ChildSHistory* shistory = GetSessionHistory();
+    if (shistory && !mLSHE) {
+      int32_t idx = shistory->LegacySHistory()->GetRequestedIndex();
       if (idx == -1) {
-        idx = mSessionHistory->Index();
-      }
-      mSessionHistory->LegacySHistory()->GetEntryAtIndex(idx,
-                                                         getter_AddRefs(mLSHE));
+        idx = shistory->Index();
+      }
+      shistory->LegacySHistory()->GetEntryAtIndex(idx, getter_AddRefs(mLSHE));
     }
 
     mLoadType = LOAD_ERROR_PAGE;
   }
 
   nsCOMPtr<nsIURI> finalURI;
   // If this a redirect, use the final url (uri)
   // else use the original url
@@ -8441,21 +8415,21 @@ nsresult nsDocShell::HandleSameDocumentN
 
   /* Restore the original LSHE if we were loading something
    * while same document navigation was initiated.
    */
   SetHistoryEntryAndUpdateBC(Some<nsISHEntry*>(oldLSHE), Nothing());
   /* Set the title for the SH entry for this target url. so that
    * SH menus in go/back/forward buttons won't be empty for this.
    */
-  if (mSessionHistory) {
-    int32_t index = mSessionHistory->Index();
+  ChildSHistory* shistory = GetSessionHistory();
+  if (shistory) {
+    int32_t index = shistory->Index();
     nsCOMPtr<nsISHEntry> shEntry;
-    mSessionHistory->LegacySHistory()->GetEntryAtIndex(index,
-                                                       getter_AddRefs(shEntry));
+    shistory->LegacySHistory()->GetEntryAtIndex(index, getter_AddRefs(shEntry));
     NS_ENSURE_TRUE(shEntry, NS_ERROR_FAILURE);
     shEntry->SetTitle(mTitle);
   }
 
   /* Set the title for the Global History entry for this anchor url.
    */
   UpdateGlobalHistoryTitle(aLoadState->URI());
 
@@ -10090,22 +10064,19 @@ bool nsDocShell::OnNewURI(nsIURI* aURI, 
 
   // We don't update session history on reload unless we're loading
   // an iframe in shift-reload case.
   bool updateSHistory =
       updateGHistory && (!(aLoadType & LOAD_CMD_RELOAD) ||
                          (IsForceReloadType(aLoadType) && IsFrame()));
 
   // Create SH Entry (mLSHE) only if there is a SessionHistory object in the
-  // current frame or in the root docshell.
-  RefPtr<ChildSHistory> rootSH = mSessionHistory;
-  if (!rootSH) {
-    // Get the handle to SH from the root docshell
-    rootSH = GetRootSessionHistory();
-  }
+  // in the root browsing context.
+  RefPtr<ChildSHistory> rootSH =
+      mBrowsingContext->Top()->GetChildSessionHistory();
   if (!rootSH) {
     updateSHistory = false;
     updateGHistory = false;  // XXX Why global history too?
   }
 
   // Check if the url to be loaded is the same as the one already loaded.
   if (mCurrentURI) {
     aURI->Equals(mCurrentURI, &equalUri);
@@ -10198,21 +10169,21 @@ bool nsDocShell::OnNewURI(nsIURI* aURI, 
       /* This is  a fresh page getting loaded for the first time
        *.Create a Entry for it and add it to SH, if this is the
        * rootDocShell
        */
       (void)AddToSessionHistory(aURI, aChannel, aTriggeringPrincipal,
                                 aPrincipalToInherit, aStoragePrincipalToInherit,
                                 aCsp, aCloneSHChildren, getter_AddRefs(mLSHE));
     }
-  } else if (mSessionHistory && mLSHE && mURIResultedInDocument) {
+  } else if (GetSessionHistory() && mLSHE && mURIResultedInDocument) {
     // Even if we don't add anything to SHistory, ensure the current index
     // points to the same SHEntry as our mLSHE.
 
-    mSessionHistory->LegacySHistory()->EnsureCorrectEntryAtCurrIndex(mLSHE);
+    GetSessionHistory()->LegacySHistory()->EnsureCorrectEntryAtCurrIndex(mLSHE);
   }
 
   // If this is a POST request, we do not want to include this in global
   // history.
   if (ShouldAddURIVisit(aChannel) && updateGHistory && aAddToGlobalHistory &&
       !net::ChannelIsPost(aChannel)) {
     nsCOMPtr<nsIURI> previousURI;
     uint32_t previousFlags = 0;
@@ -10841,21 +10812,21 @@ nsresult nsDocShell::AddToSessionHistory
                 cacheKey,             // CacheKey
                 mContentTypeHint,     // Content-type
                 triggeringPrincipal,  // Channel or provided principal
                 principalToInherit, storagePrincipalToInherit, csp, HistoryID(),
                 mDynamicallyCreated, originalURI, resultPrincipalURI,
                 loadReplace, referrerInfo, srcdoc, srcdocEntry, baseURI,
                 saveLayoutState, expired);
 
-  if (root == static_cast<nsIDocShellTreeItem*>(this) && mSessionHistory) {
+  if (root == static_cast<nsIDocShellTreeItem*>(this) && GetSessionHistory()) {
     bool shouldPersist = ShouldAddToSessionHistory(aURI, aChannel);
     Maybe<int32_t> previousEntryIndex;
     Maybe<int32_t> loadedEntryIndex;
-    rv = mSessionHistory->LegacySHistory()->AddToRootSessionHistory(
+    rv = GetSessionHistory()->LegacySHistory()->AddToRootSessionHistory(
         aCloneChildren, mOSHE, mBrowsingContext, entry, mLoadType,
         shouldPersist, &previousEntryIndex, &loadedEntryIndex);
 
     MOZ_ASSERT(NS_SUCCEEDED(rv), "Could not add entry to root session history");
     if (previousEntryIndex.isSome()) {
       mPreviousEntryIndex = previousEntryIndex.value();
     }
     if (loadedEntryIndex.isSome()) {
@@ -12254,19 +12225,19 @@ nsDocShell::ResumeRedirectedLoad(uint64_
                             previousURI, previousFlags);
 
         if (aTiming) {
           self->mTiming = new nsDOMNavigationTiming(self, aTiming);
         }
 
         // If we're performing a history load, locate the correct history entry,
         // and set the relevant bits on our loadState.
-        if (aHistoryIndex >= 0 && self->mSessionHistory) {
+        if (aHistoryIndex >= 0 && self->GetSessionHistory()) {
           nsCOMPtr<nsISHistory> legacySHistory =
-              self->mSessionHistory->LegacySHistory();
+              self->GetSessionHistory()->LegacySHistory();
 
           nsCOMPtr<nsISHEntry> entry;
           nsresult rv = legacySHistory->GetEntryAtIndex(aHistoryIndex,
                                                         getter_AddRefs(entry));
           if (NS_SUCCEEDED(rv)) {
             legacySHistory->InternalSetRequestedIndex(aHistoryIndex);
             aLoadState->SetLoadType(LOAD_HISTORY);
             aLoadState->SetSHEntry(entry);
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -657,16 +657,20 @@ class nsDocShell final : public nsDocLoa
   already_AddRefed<nsISHEntry> SetHistoryEntry(nsCOMPtr<nsISHEntry>* aPtr,
                                                nsISHEntry* aEntry);
 
   // This method calls SetHistoryEntry and updates mOSHE and mLSHE in BC to be
   // the same as in docshell
   void SetHistoryEntryAndUpdateBC(const Maybe<nsISHEntry*>& aLSHE,
                                   const Maybe<nsISHEntry*>& aOSHE);
 
+  mozilla::dom::ChildSHistory* GetSessionHistory() {
+    return mBrowsingContext->GetChildSessionHistory();
+  }
+
   //
   // URI Load
   //
 
   // Actually open a channel and perform a URI load. Callers need to pass a
   // non-null aLoadState->TriggeringPrincipal() which initiated the URI load.
   // Please note that the TriggeringPrincipal will be used for performing
   // security checks. If aLoadState->URI() is provided by the web, then please
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -982,22 +982,16 @@ interface nsIDocShell : nsIDocShellTreeI
   /**
    * Returns true if the current load is a forced reload,
    * e.g. started by holding shift whilst triggering reload.
    */
   readonly attribute bool isForceReloading;
 
   Array<float> getColorMatrix();
 
-  /**
-   * Initialize session history for this docshell. The docshell must be the root
-   * docshell.
-   */
-  void initSessionHistory();
-
 %{C++
   /**
    * These methods call nsDocShell::GetHTMLEditorInternal() and
    * nsDocShell::SetHTMLEditorInternal() with static_cast.
    */
   mozilla::HTMLEditor* GetHTMLEditor();
   nsresult SetHTMLEditor(mozilla::HTMLEditor* aHTMLEditor);
 %}
--- a/docshell/shistory/ChildSHistory.cpp
+++ b/docshell/shistory/ChildSHistory.cpp
@@ -15,32 +15,39 @@
 #include "nsSHEntry.h"
 #include "nsSHistory.h"
 #include "nsDocShell.h"
 #include "nsXULAppAPI.h"
 
 namespace mozilla {
 namespace dom {
 
-static already_AddRefed<nsISHistory> CreateSHistory(nsDocShell* aDocShell) {
-  if (XRE_IsContentProcess() && StaticPrefs::fission_sessionHistoryInParent()) {
-    return do_AddRef(static_cast<SHistoryChild*>(
-        ContentChild::GetSingleton()->SendPSHistoryConstructor(
-            aDocShell->GetBrowsingContext())));
+ChildSHistory::ChildSHistory(BrowsingContext* aBrowsingContext)
+    : mBrowsingContext(aBrowsingContext) {}
+
+void ChildSHistory::SetIsInProcess(bool aIsInProcess) {
+  if (!aIsInProcess) {
+    mHistory = nullptr;
+
+    return;
   }
 
-  nsCOMPtr<nsISHistory> history =
-      new nsSHistory(aDocShell->GetBrowsingContext(), aDocShell->HistoryID());
-  return history.forget();
-}
+  if (mHistory) {
+    return;
+  }
 
-ChildSHistory::ChildSHistory(nsDocShell* aDocShell)
-    : mDocShell(aDocShell), mHistory(CreateSHistory(aDocShell)) {}
+  if (XRE_IsContentProcess() && StaticPrefs::fission_sessionHistoryInParent()) {
+    mHistory = do_AddRef(static_cast<SHistoryChild*>(
+        ContentChild::GetSingleton()->SendPSHistoryConstructor(
+            mBrowsingContext)));
+    return;
+  }
 
-ChildSHistory::~ChildSHistory() {}
+  mHistory = new nsSHistory(mBrowsingContext);
+}
 
 int32_t ChildSHistory::Count() { return mHistory->GetCount(); }
 
 int32_t ChildSHistory::Index() {
   int32_t index;
   mHistory->GetIndex(&index);
   return index;
 }
@@ -82,42 +89,38 @@ void ChildSHistory::AsyncGo(int32_t aOff
 void ChildSHistory::RemovePendingHistoryNavigations() {
   mPendingNavigations.clear();
 }
 
 void ChildSHistory::EvictLocalContentViewers() {
   mHistory->EvictAllContentViewers();
 }
 
-nsISHistory* ChildSHistory::LegacySHistory() { return mHistory; }
+nsISHistory* ChildSHistory::LegacySHistory() {
+  MOZ_RELEASE_ASSERT(mHistory);
+  return mHistory;
+}
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ChildSHistory)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(ChildSHistory)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(ChildSHistory)
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ChildSHistory, mDocShell, mHistory)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ChildSHistory, mBrowsingContext, mHistory)
 
 JSObject* ChildSHistory::WrapObject(JSContext* cx,
                                     JS::Handle<JSObject*> aGivenProto) {
   return ChildSHistory_Binding::Wrap(cx, this, aGivenProto);
 }
 
 nsISupports* ChildSHistory::GetParentObject() const {
-  // We want to get the BrowserChildMessageManager, which is the
-  // messageManager on mDocShell.
-  RefPtr<ContentFrameMessageManager> mm;
-  if (mDocShell) {
-    mm = mDocShell->GetMessageManager();
-  }
-  // else we must be unlinked... can that happen here?
-  return ToSupports(mm);
+  return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
 }
 
 already_AddRefed<nsISHEntry> CreateSHEntryForDocShell(nsISHistory* aSHistory) {
   uint64_t sharedID = SHEntryChildShared::CreateSharedID();
   if (XRE_IsContentProcess() && StaticPrefs::fission_sessionHistoryInParent()) {
     return do_AddRef(static_cast<SHEntryChild*>(
         ContentChild::GetSingleton()->SendPSHEntryConstructor(
             static_cast<SHistoryChild*>(aSHistory), sharedID)));
--- a/docshell/shistory/ChildSHistory.h
+++ b/docshell/shistory/ChildSHistory.h
@@ -20,35 +20,39 @@
 #define mozilla_dom_ChildSHistory_h
 
 #include "nsCOMPtr.h"
 #include "mozilla/ErrorResult.h"
 #include "nsWrapperCache.h"
 #include "nsThreadUtils.h"
 #include "mozilla/LinkedList.h"
 
-class nsSHistory;
-class nsDocShell;
 class nsISHEntry;
 class nsISHistory;
-class nsIWebNavigation;
-class nsIGlobalObject;
 
 namespace mozilla {
 namespace dom {
 
+class BrowsingContext;
+
 class ChildSHistory : public nsISupports, public nsWrapperCache {
  public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ChildSHistory)
   nsISupports* GetParentObject() const;
   JSObject* WrapObject(JSContext* cx,
                        JS::Handle<JSObject*> aGivenProto) override;
 
-  explicit ChildSHistory(nsDocShell* aDocShell);
+  explicit ChildSHistory(BrowsingContext* aBrowsingContext);
+
+  // Create or destroy the session history implementation in the child process.
+  // This can be removed once session history is stored exclusively in the
+  // parent process.
+  void SetIsInProcess(bool aIsInProcess);
+  bool IsInProcess() { return !!mHistory; }
 
   int32_t Count();
   int32_t Index();
 
   /**
    * Reload the current entry in the session history.
    */
   void Reload(uint32_t aReloadFlags, ErrorResult& aRv);
@@ -67,17 +71,17 @@ class ChildSHistory : public nsISupports
   /**
    * Evicts all content viewers within the current process.
    */
   void EvictLocalContentViewers();
 
   nsISHistory* LegacySHistory();
 
  private:
-  virtual ~ChildSHistory();
+  virtual ~ChildSHistory() = default;
 
   class PendingAsyncHistoryNavigation
       : public Runnable,
         public mozilla::LinkedListElement<PendingAsyncHistoryNavigation> {
    public:
     PendingAsyncHistoryNavigation(ChildSHistory* aHistory, int32_t aOffset)
         : Runnable("PendingAsyncHistoryNavigation"),
           mHistory(aHistory),
@@ -91,17 +95,17 @@ class ChildSHistory : public nsISupports
       return NS_OK;
     }
 
    private:
     RefPtr<ChildSHistory> mHistory;
     int32_t mOffset;
   };
 
-  RefPtr<nsDocShell> mDocShell;
+  RefPtr<BrowsingContext> mBrowsingContext;
   nsCOMPtr<nsISHistory> mHistory;
   mozilla::LinkedList<PendingAsyncHistoryNavigation> mPendingNavigations;
 };
 
 already_AddRefed<nsISHEntry> CreateSHEntryForDocShell(nsISHistory* aSHistory);
 
 }  // namespace dom
 }  // namespace mozilla
--- a/docshell/shistory/SHistoryChild.cpp
+++ b/docshell/shistory/SHistoryChild.cpp
@@ -210,19 +210,16 @@ SHistoryChild::AddEntry(nsISHEntry* aEnt
     if (entriesPurged > 0) {
       mRootDocShell->HistoryPurged(entriesPurged);
     }
   }
 
   return NS_OK;
 }
 
-NS_IMETHODIMP_(void)
-SHistoryChild::ClearRootBrowsingContext() { mRootDocShell = nullptr; }
-
 NS_IMETHODIMP
 SHistoryChild::UpdateIndex(void) {
   return SendUpdateIndex() ? NS_OK : NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 SHistoryChild::ReplaceEntry(int32_t aIndex, nsISHEntry* aReplaceEntry) {
   nsresult rv;
--- a/docshell/shistory/SHistoryParent.cpp
+++ b/docshell/shistory/SHistoryParent.cpp
@@ -16,19 +16,18 @@
 #include "SHEntryChild.h"
 
 extern mozilla::LazyLogModule gSHistoryLog;
 
 namespace mozilla {
 namespace dom {
 
 LegacySHistory::LegacySHistory(SHistoryParent* aSHistoryParent,
-                               CanonicalBrowsingContext* aRootBC,
-                               const nsID& aDocShellID)
-    : nsSHistory(aRootBC, aDocShellID), mSHistoryParent(aSHistoryParent) {
+                               CanonicalBrowsingContext* aRootBC)
+    : nsSHistory(aRootBC), mSHistoryParent(aSHistoryParent) {
   mIsRemote = true;
   aRootBC->SetSessionHistory(this);
 }
 
 static void FillInLoadResult(
     nsresult aRv, const nsTArray<nsSHistory::LoadEntryResult>& aLoadResults,
     LoadSHEntryResult* aResult) {
   if (NS_SUCCEEDED(aRv)) {
@@ -42,17 +41,17 @@ static void FillInLoadResult(
 
     *aResult = data;
   } else {
     *aResult = aRv;
   }
 }
 
 SHistoryParent::SHistoryParent(CanonicalBrowsingContext* aContext)
-    : mHistory(new LegacySHistory(this, aContext, nsID())) {}
+    : mHistory(new LegacySHistory(this, aContext)) {}
 
 SHistoryParent::~SHistoryParent() { mHistory->mSHistoryParent = nullptr; }
 
 SHEntryParent* SHistoryParent::CreateEntry(PContentParent* aContentParent,
                                            PSHistoryParent* aSHistoryParent,
                                            uint64_t aSharedID) {
   RefPtr<LegacySHEntry> entry = new LegacySHEntry(
       aContentParent, static_cast<SHistoryParent*>(aSHistoryParent)->mHistory,
--- a/docshell/shistory/SHistoryParent.h
+++ b/docshell/shistory/SHistoryParent.h
@@ -30,17 +30,17 @@ class LegacySHistory final : public nsSH
   virtual ~LegacySHistory() = default;
 
   void EvictOutOfRangeWindowContentViewers(int32_t aIndex) override;
 
   SHistoryParent* mSHistoryParent;
 
  public:
   LegacySHistory(SHistoryParent* aSHistoryParent,
-                 CanonicalBrowsingContext* aRootBC, const nsID& aDocShellID);
+                 CanonicalBrowsingContext* aRootBC);
 
   NS_IMETHOD CreateEntry(nsISHEntry** aEntry) override;
   using nsSHistory::ReloadCurrentEntry;
   NS_IMETHOD ReloadCurrentEntry() override;
 
   SHistoryParent* GetActor() { return mSHistoryParent; }
 };
 
--- a/docshell/shistory/nsISHistory.idl
+++ b/docshell/shistory/nsISHistory.idl
@@ -146,23 +146,16 @@ interface nsISHistory: nsISupports
    * @param aPersist          If true this specifies that the entry should
    *                          persist in the list. If false, this means that
    *                          when new entries are added this element will not
    *                          appear in the session history list.
    */
   void addEntry(in nsISHEntry aEntry, in boolean aPersist);
 
   /**
-   * Clear the reference to the toplevel browsing context object that this
-   * SHistory object belongs to.
-   */
-  [noscript, notxpcom]
-  void ClearRootBrowsingContext();
-
-  /**
    * Update the index maintained by sessionHistory
    */
   void updateIndex();
 
   /**
    * Replace the nsISHEntry at a particular index
    *
    * @param aIndex            The index at which the entry should be replaced.
--- a/docshell/shistory/nsSHistory.cpp
+++ b/docshell/shistory/nsSHistory.cpp
@@ -199,22 +199,22 @@ void nsSHistory::EvictContentViewerForEn
   // When dropping bfcache, we have to remove associated dynamic entries as
   // well.
   int32_t index = GetIndexOfEntry(aEntry);
   if (index != -1) {
     RemoveDynEntries(index, aEntry);
   }
 }
 
-nsSHistory::nsSHistory(BrowsingContext* aRootBC, const nsID& aRootDocShellID)
+nsSHistory::nsSHistory(BrowsingContext* aRootBC)
     : mRootBC(aRootBC),
       mIsRemote(false),
       mIndex(-1),
       mRequestedIndex(-1),
-      mRootDocShellID(aRootDocShellID) {
+      mRootDocShellID(aRootBC->GetHistoryID()) {
   // Add this new SHistory object to the list
   gSHistoryList.insertBack(this);
 
   // Init mHistoryTracker on setting mRootBC so we can bind its event
   // target to the tabGroup.
   nsPIDOMWindowOuter* win;
   if (mRootBC && (win = mRootBC->GetDOMWindow())) {
     nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(win);
@@ -800,19 +800,16 @@ nsresult nsSHistory::AddEntry(nsISHEntry
   if (gHistoryMaxSize >= 0 && Length() > gHistoryMaxSize) {
     *aEntriesPurged = Length() - gHistoryMaxSize;
     PurgeHistory(*aEntriesPurged);
   }
 
   return NS_OK;
 }
 
-NS_IMETHODIMP_(void)
-nsSHistory::ClearRootBrowsingContext() { mRootBC = nullptr; }
-
 /* Get size of the history list */
 NS_IMETHODIMP
 nsSHistory::GetCount(int32_t* aResult) {
   MOZ_ASSERT(aResult, "null out param?");
   *aResult = Length();
   return NS_OK;
 }
 
--- a/docshell/shistory/nsSHistory.h
+++ b/docshell/shistory/nsSHistory.h
@@ -65,17 +65,17 @@ class nsSHistory : public mozilla::Linke
     nsISHEntry* destTreeParent;  // constant; the node under destTreeRoot
                                  // whose children will correspond to aEntry
     uint64_t otherPid;  // constant; pid of the process which indirectly called
                         // SetChildHistoryEntry
     // see comment for WalkHistoryEntriesFunc
     nsTArray<EntriesAndBrowsingContextData>* entriesToUpdate;
   };
 
-  nsSHistory(mozilla::dom::BrowsingContext* aRootBC, const nsID& aDocShellID);
+  explicit nsSHistory(mozilla::dom::BrowsingContext* aRootBC);
   NS_DECL_ISUPPORTS
   NS_DECL_NSISHISTORY
 
   // One time initialization method called upon docshell module construction
   static nsresult Startup();
   static void Shutdown();
   static void UpdatePrefs();
 
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -2127,18 +2127,17 @@ nsresult nsFrameLoader::MaybeCreateDocSh
     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.
   if (mIsTopLevelContent && mOwnerContent->IsXULElement(nsGkAtoms::browser) &&
       !mOwnerContent->HasAttr(kNameSpaceID_None, nsGkAtoms::disablehistory)) {
     // XXX(nika): Set this up more explicitly?
-    nsresult rv = docShell->InitSessionHistory();
-    NS_ENSURE_SUCCESS(rv, rv);
+    mPendingBrowsingContext->InitSessionHistory();
   }
 
   // Apply sandbox flags even if our owner is not an iframe, as this copies
   // flags from our owning content's owning document.
   // Note: ApplySandboxFlags should be called after docShell->SetIsFrame
   // because we need to get the correct presentation URL in ApplySandboxFlags.
   uint32_t sandboxFlags = 0;
   HTMLIFrameElement* iframe = HTMLIFrameElement::FromNode(mOwnerContent);
--- a/toolkit/components/browser/nsWebBrowser.cpp
+++ b/toolkit/components/browser/nsWebBrowser.cpp
@@ -149,17 +149,19 @@ already_AddRefed<nsWebBrowser> nsWebBrow
 
   docShell->SetTreeOwner(docShellTreeOwner);
 
   // If the webbrowser is a content docshell item then we won't hear any
   // events from subframes. To solve that we install our own chrome event
   // handler that always gets called (even for subframes) for any bubbling
   // event.
 
-  docShell->InitSessionHistory();
+  if (aBrowsingContext->IsTop()) {
+    aBrowsingContext->InitSessionHistory();
+  }
 
   NS_ENSURE_SUCCESS(docShellAsWin->Create(), nullptr);
 
   // Hook into the OnSecurityChange() notification for lock/unlock icon
   // updates
   // this works because the implementation of nsISecureBrowserUI
   // (nsSecureBrowserUIImpl) calls docShell->SetSecurityUI(this);
   nsCOMPtr<nsISecureBrowserUI> securityUI =