Bug 1315105 - Part 1: Add support for prerendering PartialSHistories to GroupedSHistory, r=smaug
☠☠ backed out by 636a1e09a0e4 ☠ ☠
authorMichael Layzell <michael@thelayzells.com>
Mon, 19 Dec 2016 15:03:17 +0800
changeset 371414 ab6c012704b9552a14d44d5ab1ec95e81ba3f7d2
parent 371413 84bf30ed272922c0ff916c3906b4527a15014cc3
child 371415 059753ec9117db25295484ebdf3b0b863df655f1
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1315105
milestone53.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 1315105 - Part 1: Add support for prerendering PartialSHistories to GroupedSHistory, r=smaug MozReview-Commit-ID: A5bwSy8NkH3
docshell/shistory/nsIGroupedSHistory.idl
docshell/shistory/nsIPartialSHistory.idl
dom/base/GroupedSHistory.cpp
dom/base/GroupedSHistory.h
dom/base/PartialSHistory.cpp
dom/base/PartialSHistory.h
dom/base/nsFrameLoader.cpp
dom/base/nsFrameLoader.h
dom/base/nsIFrameLoader.idl
--- a/docshell/shistory/nsIGroupedSHistory.idl
+++ b/docshell/shistory/nsIGroupedSHistory.idl
@@ -17,16 +17,21 @@ interface nsIPartialSHistory;
  */
 [scriptable, builtinclass, uuid(813e498d-73a8-449a-be09-6187e62c5352)]
 interface nsIGroupedSHistory : nsISupports
 {
   // The total number of entries of all its partial session histories.
   [infallible] readonly attribute unsigned long count;
 
   /**
+   * The currently active frameloader controlled by this nsIGroupedSHistory.
+   */
+  readonly attribute nsIFrameLoader activeFrameLoader;
+
+  /**
    * Remove all partial histories after currently active one (if any) and then
    * append the given partial session history to the end of the list.
    */
   void appendPartialSessionHistory(in nsIPartialSHistory aPartialHistory);
 
   /**
    * Notify the grouped session history that the active partial session history
    * has been modified.
@@ -52,9 +57,27 @@ interface nsIGroupedSHistory : nsISuppor
    */
   nsIFrameLoader gotoIndex(in unsigned long aGlobalIndex);
 
   /**
    * Close the FrameLoaderOwners of the inactive PartialSHistories in this GlobalSHistory.
    * This does not remove the PartialSHistories from the GroupedSHistory.
    */
   void closeInactiveFrameLoaderOwners();
+
+  /**
+   * Add a partialSHistory as a "prerendering" partialSHistory. This
+   * partialSHistory's tab will have its lifetime managed by the
+   * GroupedSHistory, and will be closed when closeInactiveFrameLoaderOwners is
+   * called, or whenever a SHistory update is received.
+   */
+  void addPrerenderingPartialSHistory(in nsIPartialSHistory aPrerendering, in long aId);
+
+  /**
+   * Switch to the prerendering partialSHistory identified by aId, appending it after the current partialSHistory.
+   */
+  [implicit_jscontext] nsISupports activatePrerendering(in long aId);
+
+  /**
+   * Cancel the prerendering with the given ID.
+   */
+  void cancelPrerendering(in long aId);
 };
--- a/docshell/shistory/nsIPartialSHistory.idl
+++ b/docshell/shistory/nsIPartialSHistory.idl
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
+interface nsIGroupedSHistory;
 interface nsIFrameLoader;
 
 /**
  * nsIPartialSHistory represents a part of nsIGroupedSHistory. It associates to
  * a "partial" nsISHistory in either local or remote process.
  */
 [scriptable, builtinclass, uuid(5cd75e28-838c-4a0a-972e-6005f736ef7a)]
 interface nsIPartialSHistory : nsISupports
@@ -22,24 +23,39 @@ interface nsIPartialSHistory : nsISuppor
 
   // If it's part of a grouped session history, globalIndexOffset denotes the
   // number of entries ahead.
   [infallible] readonly attribute unsigned long globalIndexOffset;
 
   // The frameloader which owns this partial session history.
   readonly attribute nsIFrameLoader ownerFrameLoader;
 
+  // The groupedSHistory which this partialSHistory is a part of, or null.
+  readonly attribute nsIGroupedSHistory groupedSHistory;
+
+  // The current state of the nsIPartialSHistory, whether it is active,
+  // inactive, or currently prerendering.
+  const long STATE_INACTIVE = 0;
+  const long STATE_ACTIVE = 1;
+  const long STATE_PRERENDER = 2;
+
+  [infallible] attribute long activeState;
+
   /**
    * Notify that it's been added to a grouped session history. It also implies
    * it's becoming the active partial history of the group.
    *
+   * @param aGroup                 The GroupedSHistory which this partialSHistory
+   *                               is joining.
+   *
    * @param aOffset                The number of entries in preceding partial
    *                               session histories.
    */
-  void onAttachGroupedSessionHistory(in unsigned long aOffset);
+  void onAttachGroupedSessionHistory(in nsIGroupedSHistory aGroup,
+                                     in unsigned long aOffset);
 
   /**
    * This method is used by the TabParent to notify the PartialSHistory
    * that the state of its corresponding nsISHistory in the content process
    * has been updated. It is unused in the in-process case.
    *
    * @param aCount      The number of entries in the associated session history.
    * @param aLocalIndex The local index of the currently active entry in the
--- a/dom/base/GroupedSHistory.cpp
+++ b/dom/base/GroupedSHistory.cpp
@@ -6,17 +6,30 @@
 
 #include "GroupedSHistory.h"
 #include "TabParent.h"
 #include "PartialSHistory.h"
 
 namespace mozilla {
 namespace dom {
 
-NS_IMPL_CYCLE_COLLECTION(GroupedSHistory, mPartialHistories)
+NS_IMPL_CYCLE_COLLECTION_CLASS(GroupedSHistory)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(GroupedSHistory)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPartialHistories)
+  tmp->mPrerenderingHistories.Clear();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(GroupedSHistory)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPartialHistories)
+  for (GroupedSHistory::PrerenderingHistory& h : tmp->mPrerenderingHistories) {
+    ImplCycleCollectionTraverse(cb, h.mPartialHistory, "mPrerenderingHistories[i]->mPartialHistory", 0);
+  }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
 NS_IMPL_CYCLE_COLLECTING_ADDREF(GroupedSHistory)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(GroupedSHistory)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(GroupedSHistory)
   NS_INTERFACE_MAP_ENTRY(nsIGroupedSHistory)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIGroupedSHistory)
 NS_INTERFACE_MAP_END
 
@@ -58,19 +71,22 @@ GroupedSHistory::AppendPartialSessionHis
     }
     prevPartialHistory->OnDeactive();
   }
 
   // Attach the partial history.
   uint32_t offset = mCount;
   mCount += partialHistory->GetCount();
   mPartialHistories.AppendElement(partialHistory);
-  partialHistory->OnAttachGroupedSessionHistory(offset);
+  partialHistory->OnAttachGroupedSessionHistory(this, offset);
   mIndexOfActivePartialHistory = mPartialHistories.Count() - 1;
 
+  // Remove the prerendered documents, as there was a history navigation
+  PurgePrerendering();
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 GroupedSHistory::HandleSHistoryUpdate(nsIPartialSHistory* aPartial, bool aTruncate)
 {
   if (!aPartial) {
     return NS_ERROR_INVALID_POINTER;
@@ -96,16 +112,19 @@ GroupedSHistory::HandleSHistoryUpdate(ns
       nsCOMPtr<nsIFrameLoader> loader;
       pHistory->GetOwnerFrameLoader(getter_AddRefs(loader));
       if (loader && !loader->GetIsDead()) {
         loader->RequestFrameLoaderClose();
       }
     }
   }
 
+  // Remove the prerendered documents, as there was a history navigation
+  PurgePrerendering();
+
   // If we should be truncating, make sure to purge any partialSHistories which
   // follow the one being updated.
   if (aTruncate) {
     int32_t index = mPartialHistories.IndexOf(partialHistory);
     if (NS_WARN_IF(index != mIndexOfActivePartialHistory) ||
         NS_WARN_IF(index < 0)) {
       // Non-active or not attached partialHistory
       return NS_ERROR_UNEXPECTED;
@@ -204,23 +223,121 @@ GroupedSHistory::PurgePartialHistories(u
                                      lastIndex - aLastPartialIndexToKeep);
 }
 
 /* static */ bool
 GroupedSHistory::GroupedHistoryEnabled() {
   return Preferences::GetBool("browser.groupedhistory.enabled", false);
 }
 
+void
+GroupedSHistory::PurgePrerendering()
+{
+  nsTArray<PrerenderingHistory> histories = Move(mPrerenderingHistories);
+  // Remove the frameloaders which are owned by the prerendering history, and
+  // remove them from mPrerenderingHistories.
+  for (uint32_t i = 0; i < histories.Length(); ++i) {
+    nsCOMPtr<nsIFrameLoader> loader;
+    histories[i].mPartialHistory->GetOwnerFrameLoader(getter_AddRefs(loader));
+    if (loader) {
+      loader->RequestFrameLoaderClose();
+    }
+  }
+  MOZ_ASSERT(mPrerenderingHistories.IsEmpty());
+}
+
 NS_IMETHODIMP
 GroupedSHistory::CloseInactiveFrameLoaderOwners()
 {
-  for (int32_t i = 0; i < mPartialHistories.Count(); ++i) {
-    if (i != mIndexOfActivePartialHistory) {
+  MOZ_ASSERT(mIndexOfActivePartialHistory >= 0);
+  // Remove inactive frameloaders which are participating in the grouped shistory
+  for (uint32_t i = 0; i < mPartialHistories.Length(); ++i) {
+    if (i != static_cast<uint32_t>(mIndexOfActivePartialHistory)) {
       nsCOMPtr<nsIFrameLoader> loader;
       mPartialHistories[i]->GetOwnerFrameLoader(getter_AddRefs(loader));
       loader->RequestFrameLoaderClose();
     }
   }
+
+  PurgePrerendering();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+GroupedSHistory::AddPrerenderingPartialSHistory(nsIPartialSHistory* aPrerendering, int32_t aId)
+{
+  NS_ENSURE_TRUE(aPrerendering && aId, NS_ERROR_UNEXPECTED);
+  aPrerendering->SetActiveState(nsIPartialSHistory::STATE_PRERENDER);
+  PrerenderingHistory history = { aPrerendering, aId };
+  mPrerenderingHistories.AppendElement(history);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+GroupedSHistory::GetActiveFrameLoader(nsIFrameLoader** aFrameLoader)
+{
+  if (mIndexOfActivePartialHistory >= 0) {
+    return mPartialHistories[mIndexOfActivePartialHistory]->GetOwnerFrameLoader(aFrameLoader);
+  }
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+GroupedSHistory::ActivatePrerendering(int32_t aId, JSContext* aCx, nsISupports** aPromise)
+{
+  NS_ENSURE_TRUE(aId && aCx && aPromise, NS_ERROR_UNEXPECTED);
+
+  // Look for an entry with the given aId in mPrerenderingHistories.
+  for (uint32_t i = 0; i < mPrerenderingHistories.Length(); ++i) {
+    if (mPrerenderingHistories[i].mId == aId) {
+      nsCOMPtr<nsIPartialSHistory> partialHistory = mPrerenderingHistories[i].mPartialHistory;
+      mPrerenderingHistories.RemoveElementAt(i);
+
+      nsCOMPtr<nsIFrameLoader> fl;
+      partialHistory->GetOwnerFrameLoader(getter_AddRefs(fl));
+      NS_ENSURE_TRUE(fl, NS_ERROR_FAILURE);
+
+      nsCOMPtr<nsIFrameLoader> activeFl;
+      GetActiveFrameLoader(getter_AddRefs(activeFl));
+      NS_ENSURE_TRUE(activeFl, NS_ERROR_FAILURE);
+
+      nsresult rv = fl->MakePrerenderedLoaderActive();
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      return activeFl->AppendPartialSessionHistoryAndSwap(fl, aPromise);
+    }
+  }
+
+  // Generate a rejected promise as the entry was not found.
+  nsCOMPtr<nsIGlobalObject> go = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx));
+  if (NS_WARN_IF(!go)) {
+    return NS_ERROR_FAILURE;
+  }
+  ErrorResult rv;
+  RefPtr<Promise> promise = Promise::Reject(go, aCx, JS::UndefinedHandleValue, rv);
+  if (NS_WARN_IF(rv.Failed())) {
+    return NS_ERROR_FAILURE;
+  }
+  promise.forget(aPromise);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+GroupedSHistory::CancelPrerendering(int32_t aId)
+{
+  for (uint32_t i = 0; i < mPrerenderingHistories.Length(); ++i) {
+    if (mPrerenderingHistories[i].mId == aId) {
+      nsCOMPtr<nsIPartialSHistory> partialHistory = mPrerenderingHistories[i].mPartialHistory;
+      nsCOMPtr<nsIFrameLoader> fl;
+      partialHistory->GetOwnerFrameLoader(getter_AddRefs(fl));
+      if (fl) {
+        fl->RequestFrameLoaderClose();
+      }
+      mPrerenderingHistories.RemoveElementAt(i);
+    }
+  }
+
   return NS_OK;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/GroupedSHistory.h
+++ b/dom/base/GroupedSHistory.h
@@ -83,23 +83,37 @@ private:
   ~GroupedSHistory() {}
 
   /**
    * Remove all partial histories and close tabs after the given index (of
    * mPartialHistories, not the index of session history entries).
    */
   void PurgePartialHistories(uint32_t aLastPartialIndexToKeep);
 
+  /**
+   * Remove the frameloaders which are owned by the prerendering history, and
+   * remove them from mPrerenderingHistories.
+   */
+  void PurgePrerendering();
+
   // The total number of entries in all partial histories.
   uint32_t mCount;
 
   // The index of currently active partial history in mPartialHistories.
   // Use int32_t as we have invalid index and nsCOMArray also uses int32_t.
   int32_t mIndexOfActivePartialHistory;
 
   // All participating nsIPartialSHistory objects.
   nsCOMArray<nsIPartialSHistory> mPartialHistories;
+
+  // All nsIPartialSHistories which are being prerendered.
+  struct PrerenderingHistory
+  {
+    nsCOMPtr<nsIPartialSHistory> mPartialHistory;
+    int32_t mId;
+  };
+  nsTArray<PrerenderingHistory> mPrerenderingHistories;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* GroupedSHistory_h */
--- a/dom/base/PartialSHistory.cpp
+++ b/dom/base/PartialSHistory.cpp
@@ -6,31 +6,32 @@
 
 #include "PartialSHistory.h"
 
 #include "nsIWebNavigation.h"
 
 namespace mozilla {
 namespace dom {
 
-NS_IMPL_CYCLE_COLLECTION(PartialSHistory, mOwnerFrameLoader)
+NS_IMPL_CYCLE_COLLECTION(PartialSHistory, mOwnerFrameLoader, mGroupedSHistory)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(PartialSHistory)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(PartialSHistory)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PartialSHistory)
   NS_INTERFACE_MAP_ENTRY(nsIPartialSHistory)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPartialSHistory)
   NS_INTERFACE_MAP_ENTRY(nsISHistoryListener)
   NS_INTERFACE_MAP_ENTRY(nsIPartialSHistoryListener)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
 NS_INTERFACE_MAP_END
 
 PartialSHistory::PartialSHistory(nsIFrameLoader* aOwnerFrameLoader)
   : mCount(0),
     mGlobalIndexOffset(0),
+    mActive(nsIPartialSHistory::STATE_ACTIVE),
     mOwnerFrameLoader(aOwnerFrameLoader)
 {
   MOZ_ASSERT(aOwnerFrameLoader);
 }
 
 already_AddRefed<nsISHistory>
 PartialSHistory::GetSessionHistory()
 {
@@ -139,19 +140,23 @@ NS_IMETHODIMP
 PartialSHistory::GetOwnerFrameLoader(nsIFrameLoader** aResult)
 {
   nsCOMPtr<nsIFrameLoader> loader(mOwnerFrameLoader);
   loader.forget(aResult);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-PartialSHistory::OnAttachGroupedSessionHistory(uint32_t aOffset)
+PartialSHistory::OnAttachGroupedSessionHistory(nsIGroupedSHistory* aGroup, uint32_t aOffset)
 {
+  MOZ_ASSERT(!mGroupedSHistory, "Only may join a single GroupedSHistory");
+
+  mActive = nsIPartialSHistory::STATE_ACTIVE;
   mGlobalIndexOffset = aOffset;
+  mGroupedSHistory = aGroup;
 
   // If we have direct reference to nsISHistory, simply pass through.
   nsCOMPtr<nsISHistory> shistory(GetSessionHistory());
   if (shistory) {
     // nsISHistory uses int32_t
     if (aOffset > INT32_MAX) {
       return NS_ERROR_FAILURE;
     }
@@ -182,31 +187,33 @@ PartialSHistory::HandleSHistoryUpdate(ui
 nsresult
 PartialSHistory::SHistoryDidUpdate(bool aTruncate /* = false */)
 {
   if (!mOwnerFrameLoader) {
     // Cycle collected?
     return NS_ERROR_UNEXPECTED;
   }
 
-  nsCOMPtr<nsIGroupedSHistory> groupedHistory;
-  mOwnerFrameLoader->GetGroupedSessionHistory(getter_AddRefs(groupedHistory));
-  if (NS_WARN_IF(!groupedHistory)) {
-    // Maybe we're not the active partial history, but in this case we shouldn't
-    // receive any update from session history object either.
-    return NS_ERROR_FAILURE;
+  if (!mGroupedSHistory) {
+    // It's OK if we don't have a grouped history, that just means that we
+    // aren't in a grouped shistory, so we don't need to do anything.
+    return NS_OK;
   }
 
-  groupedHistory->HandleSHistoryUpdate(this, aTruncate);
+  mGroupedSHistory->HandleSHistoryUpdate(this, aTruncate);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PartialSHistory::OnActive(uint32_t aGlobalLength, uint32_t aTargetLocalIndex)
 {
+  MOZ_ASSERT(mGroupedSHistory);
+
+  mActive = nsIPartialSHistory::STATE_ACTIVE;
+
   // In-process case.
   nsCOMPtr<nsISHistory> shistory(GetSessionHistory());
   if (shistory) {
     // nsISHistory uses int32_t
     if (aGlobalLength > INT32_MAX || aTargetLocalIndex > INT32_MAX) {
       return NS_ERROR_FAILURE;
     }
     return shistory->OnPartialSessionHistoryActive(aGlobalLength,
@@ -224,16 +231,20 @@ PartialSHistory::OnActive(uint32_t aGlob
                                                              aTargetLocalIndex);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PartialSHistory::OnDeactive()
 {
+  MOZ_ASSERT(mGroupedSHistory);
+
+  mActive = nsIPartialSHistory::STATE_INACTIVE;
+
   // In-process case.
   nsCOMPtr<nsISHistory> shistory(GetSessionHistory());
   if (shistory) {
     if (NS_FAILED(shistory->OnPartialSessionHistoryDeactive())) {
       return NS_ERROR_FAILURE;
     }
     return NS_OK;
   }
@@ -244,16 +255,38 @@ PartialSHistory::OnDeactive()
     // We have neither shistory nor tabParent?
     NS_WARNING("Unable to get shitory nor tabParent!");
     return NS_ERROR_UNEXPECTED;
   }
   Unused << tabParent->SendNotifyPartialSessionHistoryDeactive();
   return NS_OK;
 }
 
+NS_IMETHODIMP
+PartialSHistory::GetActiveState(int32_t* aActive)
+{
+  *aActive = mActive;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PartialSHistory::SetActiveState(int32_t aActive)
+{
+  mActive = aActive;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PartialSHistory::GetGroupedSHistory(nsIGroupedSHistory** aGrouped)
+{
+  nsCOMPtr<nsIGroupedSHistory> shistory = mGroupedSHistory;
+  shistory.forget(aGrouped);
+  return NS_OK;
+}
+
 /*******************************************************************************
  * nsIPartialSHistoryListener
  ******************************************************************************/
 
 NS_IMETHODIMP
 PartialSHistory::OnRequestCrossBrowserNavigation(uint32_t aIndex)
 {
   if (!mOwnerFrameLoader) {
--- a/dom/base/PartialSHistory.h
+++ b/dom/base/PartialSHistory.h
@@ -51,16 +51,22 @@ private:
 
   // The current local index of the active document in this partial SHistory.
   uint32_t mIndex;
 
   // The cache of globalIndexOffset in corresponding nsISHistory. It's only
   // used for remote process case.
   uint32_t mGlobalIndexOffset;
 
+  // One of the possible active states from nsIPartialSHistory
+  int32_t mActive;
+
   // The frameloader which owns this PartialSHistory.
   nsCOMPtr<nsIFrameLoader> mOwnerFrameLoader;
+
+  // The GroupedSHistory which this PartialSHistory is part of, or null.
+  nsCOMPtr<nsIGroupedSHistory> mGroupedSHistory;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* PartialSHistory_h */
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -136,18 +136,17 @@ typedef FrameMetrics::ViewID ViewID;
 // we'd need to re-institute a fixed version of bug 98158.
 #define MAX_DEPTH_CONTENT_FRAMES 10
 
 NS_IMPL_CYCLE_COLLECTION(nsFrameLoader,
                          mDocShell,
                          mMessageManager,
                          mChildMessageManager,
                          mOpener,
-                         mPartialSessionHistory,
-                         mGroupedSessionHistory)
+                         mPartialSessionHistory)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFrameLoader)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFrameLoader)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFrameLoader)
   NS_INTERFACE_MAP_ENTRY(nsIFrameLoader)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIFrameLoader)
   NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPersistable)
 NS_INTERFACE_MAP_END
@@ -373,22 +372,48 @@ nsFrameLoader::GetPartialSessionHistory(
     mPartialSessionHistory = new PartialSHistory(this);
   }
 
   nsCOMPtr<nsIPartialSHistory> partialHistory(mPartialSessionHistory);
   partialHistory.forget(aResult);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsFrameLoader::EnsureGroupedSHistory(nsIGroupedSHistory** aResult)
+{
+  nsCOMPtr<nsIPartialSHistory> partialHistory;
+  GetPartialSessionHistory(getter_AddRefs(partialHistory));
+  MOZ_ASSERT(partialHistory);
+
+  nsCOMPtr<nsIGroupedSHistory> groupedHistory;
+  partialHistory->GetGroupedSHistory(getter_AddRefs(groupedHistory));
+  if (!groupedHistory) {
+    groupedHistory = new GroupedSHistory();
+    groupedHistory->AppendPartialSessionHistory(partialHistory);
+
+#ifdef DEBUG
+    nsCOMPtr<nsIGroupedSHistory> test;
+    GetGroupedSessionHistory(getter_AddRefs(test));
+    MOZ_ASSERT(test == groupedHistory, "GroupedHistory must match");
+#endif
+  }
+
+  groupedHistory.forget(aResult);
+  return NS_OK;
+}
 
 NS_IMETHODIMP
 nsFrameLoader::GetGroupedSessionHistory(nsIGroupedSHistory** aResult)
 {
-  nsCOMPtr<nsIGroupedSHistory> groupedHistory(mGroupedSessionHistory);
-  groupedHistory.forget(aResult);
+  nsCOMPtr<nsIGroupedSHistory> groupedSHistory;
+  if (mPartialSessionHistory) {
+    mPartialSessionHistory->GetGroupedSHistory(getter_AddRefs(groupedSHistory));
+  }
+  groupedSHistory.forget(aResult);
   return NS_OK;
 }
 
 bool
 nsFrameLoader::SwapBrowsersAndNotify(nsFrameLoader* aOther)
 {
   // Cache the owner content before calling SwapBrowsers, which will change
   // these member variables.
@@ -402,19 +427,16 @@ nsFrameLoader::SwapBrowsersAndNotify(nsF
   if (NS_WARN_IF(!ourBrowser || !otherBrowser)) {
     return false;
   }
   nsresult rv = ourBrowser->SwapBrowsers(otherBrowser, nsIBrowser::SWAP_KEEP_PERMANENT_KEY);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return false;
   }
 
-  // Swap the GroupedSessionHistory
-  mGroupedSessionHistory.swap(aOther->mGroupedSessionHistory);
-
   // Dispatch the BrowserChangedProcess event to tell JS that the process swap
   // has occurred.
   GroupedHistoryEventInit eventInit;
   eventInit.mBubbles = true;
   eventInit.mCancelable= false;
   eventInit.mOtherBrowser = secondaryContent;
   RefPtr<GroupedHistoryEvent> event =
     GroupedHistoryEvent::Constructor(primaryContent,
@@ -447,31 +469,27 @@ public:
                "Cannot append a GroupedSHistory owner to another.");
     if (otherGroupedHistory) {
       mPromise->MaybeRejectWithUndefined();
       return;
     }
 
     // Append ourselves.
     nsresult rv;
-    if (!mThis->mGroupedSessionHistory) {
-      mThis->mGroupedSessionHistory = new GroupedSHistory();
-      nsCOMPtr<nsIPartialSHistory> partialSHistory;
-      MOZ_ALWAYS_SUCCEEDS(mThis->GetPartialSessionHistory(getter_AddRefs(partialSHistory)));
-      rv = mThis->mGroupedSessionHistory->AppendPartialSessionHistory(partialSHistory);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        mPromise->MaybeRejectWithUndefined();
-        return;
-      }
+    nsCOMPtr<nsIGroupedSHistory> groupedSHistory;
+    rv = mThis->EnsureGroupedSHistory(getter_AddRefs(groupedSHistory));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      mPromise->MaybeRejectWithUndefined();
+      return;
     }
 
     // Append the other.
     nsCOMPtr<nsIPartialSHistory> otherPartialSHistory;
     MOZ_ALWAYS_SUCCEEDS(mOther->GetPartialSessionHistory(getter_AddRefs(otherPartialSHistory)));
-    rv = mThis->mGroupedSessionHistory->AppendPartialSessionHistory(otherPartialSHistory);
+    rv = groupedSHistory->AppendPartialSessionHistory(otherPartialSHistory);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       mPromise->MaybeRejectWithUndefined();
       return;
     }
 
     // Swap the browsers and fire the BrowserChangedProcess event.
     if (mThis->SwapBrowsersAndNotify(mOther)) {
       mPromise->MaybeResolveWithUndefined();
@@ -515,20 +533,26 @@ public:
   void
   ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
   {
     if (NS_WARN_IF(!mThis->mOwnerContent)) {
       mPromise->MaybeRejectWithUndefined();
       return;
     }
 
+    nsCOMPtr<nsIGroupedSHistory> groupedSHistory;
+    mThis->GetGroupedSessionHistory(getter_AddRefs(groupedSHistory));
+    if (NS_WARN_IF(!groupedSHistory)) {
+      mPromise->MaybeRejectWithUndefined();
+      return;
+    }
+
     // Navigate the loader to the new index
     nsCOMPtr<nsIFrameLoader> otherLoader;
-    nsresult rv = mThis->mGroupedSessionHistory->
-                    GotoIndex(mGlobalIndex, getter_AddRefs(otherLoader));
+    nsresult rv = groupedSHistory->GotoIndex(mGlobalIndex, getter_AddRefs(otherLoader));
 
     // Check if the gotoIndex failed because the target frameloader is dead. We
     // need to perform a navigateAndRestoreByIndex and then return to recover.
     if (rv == NS_ERROR_NOT_AVAILABLE) {
       // Get the nsIXULBrowserWindow so that we can call NavigateAndRestoreByIndex on it.
       nsCOMPtr<nsIDocShell> docShell = mThis->mOwnerContent->OwnerDoc()->GetDocShell();
       if (NS_WARN_IF(!docShell)) {
         mPromise->MaybeRejectWithUndefined();
@@ -681,19 +705,16 @@ nsFrameLoader::AppendPartialSessionHisto
   ready->AppendNativeHandler(helper);
   complete.forget(aPromise);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFrameLoader::RequestGroupedHistoryNavigation(uint32_t aGlobalIndex, nsISupports** aPromise)
 {
-  if (!mGroupedSessionHistory) {
-    return NS_ERROR_UNEXPECTED;
-  }
 
   RefPtr<Promise> ready = FireWillChangeProcessEvent();
   if (NS_WARN_IF(!ready)) {
     return NS_ERROR_FAILURE;
   }
 
   // This promise will be resolved when the swap has finished, we return it now
   // and pass it to our helper so our helper can resolve it.
@@ -1984,18 +2005,25 @@ nsFrameLoader::StartDestroy()
   if (mDocShell) {
     nsCOMPtr<nsPIDOMWindowOuter> win_private(mDocShell->GetWindow());
     if (win_private) {
       win_private->SetFrameElementInternal(nullptr);
     }
   }
 
   // Destroy the other frame loader owners now that we are being destroyed.
-  if (mGroupedSessionHistory) {
-    mGroupedSessionHistory->CloseInactiveFrameLoaderOwners();
+  if (mPartialSessionHistory &&
+      mPartialSessionHistory->GetActiveState() == nsIPartialSHistory::STATE_ACTIVE) {
+    nsCOMPtr<nsIGroupedSHistory> groupedSHistory;
+    GetGroupedSessionHistory(getter_AddRefs(groupedSHistory));
+    if (groupedSHistory) {
+      NS_DispatchToCurrentThread(NS_NewRunnableFunction([groupedSHistory] () {
+        groupedSHistory->CloseInactiveFrameLoaderOwners();
+      }));
+    }
   }
 
   nsCOMPtr<nsIRunnable> destroyRunnable = new nsFrameLoaderDestroyRunnable(this);
   if (mNeedsAsyncDestroy || !doc ||
       NS_FAILED(doc->FinalizeFrameLoader(this, destroyRunnable))) {
     NS_DispatchToCurrentThread(destroyRunnable);
   }
 }
--- a/dom/base/nsFrameLoader.h
+++ b/dom/base/nsFrameLoader.h
@@ -346,17 +346,16 @@ private:
   // See nsIFrameLoader.idl. EVENT_MODE_NORMAL_DISPATCH automatically
   // forwards some input events to out-of-process content.
   uint32_t mEventMode;
 
   // Holds the last known size of the frame.
   mozilla::ScreenIntSize mLazySize;
 
   nsCOMPtr<nsIPartialSHistory> mPartialSessionHistory;
-  nsCOMPtr<nsIGroupedSHistory> mGroupedSessionHistory;
 
   // A stack-maintained reference to an array of promises which are blocking
   // grouped history navigation
   nsTArray<RefPtr<mozilla::dom::Promise>>* mBrowserChangingProcessBlockers;
 
   bool mIsPrerendered : 1;
   bool mDepthTooGreat : 1;
   bool mIsTopLevelContent : 1;
--- a/dom/base/nsIFrameLoader.idl
+++ b/dom/base/nsIFrameLoader.idl
@@ -166,16 +166,21 @@ interface nsIFrameLoader : nsISupports
    *                       set to prevent prompting.
    * @param aProgressListener optional print progress listener.
    */
   void print(in unsigned long long aOuterWindowID,
              in nsIPrintSettings aPrintSettings,
              in nsIWebProgressListener aProgressListener);
 
   /**
+   * Ensure that the current nsIFrameLoader has a GroupedSHistory.
+   */
+  nsIGroupedSHistory ensureGroupedSHistory();
+
+  /**
    * The default event mode automatically forwards the events
    * handled in EventStateManager::HandleCrossProcessEvent to
    * the child content process when these events are targeted to
    * the remote browser element.
    *
    * Used primarly for input events (mouse, keyboard)
    */
   const unsigned long EVENT_MODE_NORMAL_DISPATCH = 0x00000000;