Bug 1406161 - Part 6: Move shistory related static functions to nsSHistory. r?smaug draft
authorSamael Wang <freesamael@gmail.com>
Tue, 19 Dec 2017 18:26:36 +0800
changeset 716076 d5994a0007dc8f0d4432fde5ba96cd6adbf692c2
parent 716075 fba96e869602a66bcd0ae1d86f0ce9a6590a5a21
child 744940 b433123c25568bb7b509e62f23685aaf8ef215a3
push id94319
push userbmo:sawang@mozilla.com
push dateFri, 05 Jan 2018 03:38:14 +0000
reviewerssmaug
bugs1406161
milestone59.0a1
Bug 1406161 - Part 6: Move shistory related static functions to nsSHistory. r?smaug MozReview-Commit-ID: 7rqo2rISCB5
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
docshell/shistory/nsSHistory.cpp
docshell/shistory/nsSHistory.h
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -277,18 +277,16 @@ FavorPerformanceHint(bool aPerfOverStarv
   if (appShell) {
     appShell->FavorPerformanceHint(
       aPerfOverStarvation,
       Preferences::GetUint("docshell.event_starvation_delay_hint",
                            NS_EVENT_STARVATION_DELAY_HINT));
   }
 }
 
-static nsISHEntry* GetRootSHEntry(nsISHEntry* aEntry);
-
 static void
 IncreasePrivateDocShellCount()
 {
   gNumberOfPrivateDocShells++;
   if (gNumberOfPrivateDocShells > 1 ||
       !XRE_IsContentProcess()) {
     return;
   }
@@ -3972,18 +3970,18 @@ nsDocShell::AddChildSHEntryInternal(nsIS
                                           getter_AddRefs(currentHE));
     NS_ENSURE_TRUE(currentHE, NS_ERROR_FAILURE);
 
     nsCOMPtr<nsISHEntry> currentEntry(do_QueryInterface(currentHE));
     if (currentEntry) {
       uint32_t cloneID = 0;
       nsCOMPtr<nsISHEntry> nextEntry;
       aCloneRef->GetID(&cloneID);
-      rv = CloneAndReplace(currentEntry, this, cloneID, aNewEntry,
-                           aCloneChildren, getter_AddRefs(nextEntry));
+      rv = nsSHistory::CloneAndReplace(currentEntry, this, cloneID,
+        aNewEntry, aCloneChildren, getter_AddRefs(nextEntry));
 
       if (NS_SUCCEEDED(rv)) {
         nsCOMPtr<nsISHistoryInternal> shPrivate =
           do_QueryInterface(mSessionHistory);
         NS_ENSURE_TRUE(shPrivate, NS_ERROR_FAILURE);
         rv = shPrivate->AddEntry(nextEntry, true);
       }
     }
@@ -11961,17 +11959,17 @@ nsDocShell::AddState(JS::Handle<JS::Valu
 
   if (!aReplace) {
     int32_t curIndex = -1;
     rv = rootSH->GetIndex(&curIndex);
     if (NS_SUCCEEDED(rv) && curIndex > -1) {
       internalSH->EvictOutOfRangeContentViewers(curIndex);
     }
   } else {
-    nsCOMPtr<nsISHEntry> rootSHEntry = GetRootSHEntry(newSHEntry);
+    nsCOMPtr<nsISHEntry> rootSHEntry = nsSHistory::GetRootSHEntry(newSHEntry);
 
     int32_t index = -1;
     rv = rootSH->GetIndexOfEntry(rootSHEntry, &index);
     if (NS_SUCCEEDED(rv) && index > -1) {
       internalSH->ReplaceEntry(index, rootSHEntry);
     }
   }
 
@@ -12261,18 +12259,18 @@ nsDocShell::AddToSessionHistory(nsIURI* 
 
   if (root == static_cast<nsIDocShellTreeItem*>(this) && mSessionHistory) {
     // If we need to clone our children onto the new session
     // history entry, do so now.
     if (aCloneChildren && mOSHE) {
       uint32_t cloneID;
       mOSHE->GetID(&cloneID);
       nsCOMPtr<nsISHEntry> newEntry;
-      CloneAndReplace(mOSHE, this, cloneID, entry, true,
-                      getter_AddRefs(newEntry));
+      nsSHistory::CloneAndReplace(mOSHE, this, cloneID, entry, true,
+                                  getter_AddRefs(newEntry));
       NS_ASSERTION(entry == newEntry,
                    "The new session history should be in the new entry");
     }
 
     // This is the root docshell
     bool addToSHistory = !LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY);
     if (!addToSHistory) {
       // Replace current entry in session history; If the requested index is
@@ -12507,275 +12505,59 @@ nsDocShell::PersistLayoutHistoryState()
     if (scrollRestorationIsManual && layoutState) {
       layoutState->ResetScrollState();
     }
   }
 
   return rv;
 }
 
-/* static */ nsresult
-nsDocShell::WalkHistoryEntries(nsISHEntry* aRootEntry,
-                               nsDocShell* aRootShell,
-                               WalkHistoryEntriesFunc aCallback,
-                               void* aData)
-{
-  NS_ENSURE_TRUE(aRootEntry, NS_ERROR_FAILURE);
-
-  nsCOMPtr<nsISHContainer> container(do_QueryInterface(aRootEntry));
-  if (!container) {
-    return NS_ERROR_FAILURE;
-  }
-
-  int32_t childCount;
-  container->GetChildCount(&childCount);
-  for (int32_t i = 0; i < childCount; i++) {
-    nsCOMPtr<nsISHEntry> childEntry;
-    container->GetChildAt(i, getter_AddRefs(childEntry));
-    if (!childEntry) {
-      // childEntry can be null for valid reasons, for example if the
-      // docshell at index i never loaded anything useful.
-      // Remember to clone also nulls in the child array (bug 464064).
-      aCallback(nullptr, nullptr, i, aData);
-      continue;
-    }
-
-    nsDocShell* childShell = nullptr;
-    if (aRootShell) {
-      // Walk the children of aRootShell and see if one of them
-      // has srcChild as a SHEntry.
-      nsTObserverArray<nsDocLoader*>::ForwardIterator iter(
-        aRootShell->mChildList);
-      while (iter.HasMore()) {
-        nsDocShell* child = static_cast<nsDocShell*>(iter.GetNext());
-
-        if (child->HasHistoryEntry(childEntry)) {
-          childShell = child;
-          break;
-        }
-      }
-    }
-    nsresult rv = aCallback(childEntry, childShell, i, aData);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  return NS_OK;
-}
-
-// callback data for WalkHistoryEntries
-struct MOZ_STACK_CLASS CloneAndReplaceData
-{
-  CloneAndReplaceData(uint32_t aCloneID, nsISHEntry* aReplaceEntry,
-                      bool aCloneChildren, nsISHEntry* aDestTreeParent)
-    : cloneID(aCloneID)
-    , cloneChildren(aCloneChildren)
-    , replaceEntry(aReplaceEntry)
-    , destTreeParent(aDestTreeParent)
-  {
-  }
-
-  uint32_t cloneID;
-  bool cloneChildren;
-  nsISHEntry* replaceEntry;
-  nsISHEntry* destTreeParent;
-  nsCOMPtr<nsISHEntry> resultEntry;
-};
-
-/* static */ nsresult
-nsDocShell::CloneAndReplaceChild(nsISHEntry* aEntry, nsDocShell* aShell,
-                                 int32_t aEntryIndex, void* aData)
-{
-  nsCOMPtr<nsISHEntry> dest;
-
-  CloneAndReplaceData* data = static_cast<CloneAndReplaceData*>(aData);
-  uint32_t cloneID = data->cloneID;
-  nsISHEntry* replaceEntry = data->replaceEntry;
-
-  nsCOMPtr<nsISHContainer> container = do_QueryInterface(data->destTreeParent);
-  if (!aEntry) {
-    if (container) {
-      container->AddChild(nullptr, aEntryIndex);
-    }
-    return NS_OK;
-  }
-
-  uint32_t srcID;
-  aEntry->GetID(&srcID);
-
-  nsresult rv = NS_OK;
-  if (srcID == cloneID) {
-    // Replace the entry
-    dest = replaceEntry;
-  } else {
-    // Clone the SHEntry...
-    rv = aEntry->Clone(getter_AddRefs(dest));
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-  dest->SetIsSubFrame(true);
-
-  if (srcID != cloneID || data->cloneChildren) {
-    // Walk the children
-    CloneAndReplaceData childData(cloneID, replaceEntry,
-                                  data->cloneChildren, dest);
-    rv = WalkHistoryEntries(aEntry, aShell,
-                            CloneAndReplaceChild, &childData);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  if (srcID != cloneID && aShell) {
-    aShell->SwapHistoryEntries(aEntry, dest);
-  }
-
-  if (container) {
-    container->AddChild(dest, aEntryIndex);
-  }
-
-  data->resultEntry = dest;
-  return rv;
-}
-
-/* static */ nsresult
-nsDocShell::CloneAndReplace(nsISHEntry* aSrcEntry,
-                            nsDocShell* aSrcShell,
-                            uint32_t aCloneID,
-                            nsISHEntry* aReplaceEntry,
-                            bool aCloneChildren,
-                            nsISHEntry** aResultEntry)
-{
-  NS_ENSURE_ARG_POINTER(aResultEntry);
-  NS_ENSURE_TRUE(aReplaceEntry, NS_ERROR_FAILURE);
-
-  CloneAndReplaceData data(aCloneID, aReplaceEntry, aCloneChildren, nullptr);
-  nsresult rv = CloneAndReplaceChild(aSrcEntry, aSrcShell, 0, &data);
-
-  data.resultEntry.swap(*aResultEntry);
-  return rv;
-}
-
 void
 nsDocShell::SwapHistoryEntries(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry)
 {
   if (aOldEntry == mOSHE) {
     mOSHE = aNewEntry;
   }
 
   if (aOldEntry == mLSHE) {
     mLSHE = aNewEntry;
   }
 }
 
-struct SwapEntriesData
-{
-  nsDocShell* ignoreShell;     // constant; the shell to ignore
-  nsISHEntry* destTreeRoot;    // constant; the root of the dest tree
-  nsISHEntry* destTreeParent;  // constant; the node under destTreeRoot
-                               // whose children will correspond to aEntry
-};
-
-nsresult
-nsDocShell::SetChildHistoryEntry(nsISHEntry* aEntry, nsDocShell* aShell,
-                                 int32_t aEntryIndex, void* aData)
-{
-  SwapEntriesData* data = static_cast<SwapEntriesData*>(aData);
-  nsDocShell* ignoreShell = data->ignoreShell;
-
-  if (!aShell || aShell == ignoreShell) {
-    return NS_OK;
-  }
-
-  nsISHEntry* destTreeRoot = data->destTreeRoot;
-
-  nsCOMPtr<nsISHEntry> destEntry;
-  nsCOMPtr<nsISHContainer> container = do_QueryInterface(data->destTreeParent);
-
-  if (container) {
-    // aEntry is a clone of some child of destTreeParent, but since the
-    // trees aren't necessarily in sync, we'll have to locate it.
-    // Note that we could set aShell's entry to null if we don't find a
-    // corresponding entry under destTreeParent.
-
-    uint32_t targetID, id;
-    aEntry->GetID(&targetID);
-
-    // First look at the given index, since this is the common case.
-    nsCOMPtr<nsISHEntry> entry;
-    container->GetChildAt(aEntryIndex, getter_AddRefs(entry));
-    if (entry && NS_SUCCEEDED(entry->GetID(&id)) && id == targetID) {
-      destEntry.swap(entry);
-    } else {
-      int32_t childCount;
-      container->GetChildCount(&childCount);
-      for (int32_t i = 0; i < childCount; ++i) {
-        container->GetChildAt(i, getter_AddRefs(entry));
-        if (!entry) {
-          continue;
-        }
-
-        entry->GetID(&id);
-        if (id == targetID) {
-          destEntry.swap(entry);
-          break;
-        }
-      }
-    }
-  } else {
-    destEntry = destTreeRoot;
-  }
-
-  aShell->SwapHistoryEntries(aEntry, destEntry);
-
-  // Now handle the children of aEntry.
-  SwapEntriesData childData = { ignoreShell, destTreeRoot, destEntry };
-  return WalkHistoryEntries(aEntry, aShell, SetChildHistoryEntry, &childData);
-}
-
-static nsISHEntry*
-GetRootSHEntry(nsISHEntry* aEntry)
-{
-  nsCOMPtr<nsISHEntry> rootEntry = aEntry;
-  nsISHEntry* result = nullptr;
-  while (rootEntry) {
-    result = rootEntry;
-    result->GetParent(getter_AddRefs(rootEntry));
-  }
-
-  return result;
-}
-
 void
 nsDocShell::SetHistoryEntry(nsCOMPtr<nsISHEntry>* aPtr, nsISHEntry* aEntry)
 {
   // We need to sync up the docshell and session history trees for
   // subframe navigation.  If the load was in a subframe, we forward up to
   // the root docshell, which will then recursively sync up all docshells
   // to their corresponding entries in the new session history tree.
   // If we don't do this, then we can cache a content viewer on the wrong
   // cloned entry, and subsequently restore it at the wrong time.
 
-  nsISHEntry* newRootEntry = GetRootSHEntry(aEntry);
+  nsISHEntry* newRootEntry = nsSHistory::GetRootSHEntry(aEntry);
   if (newRootEntry) {
     // newRootEntry is now the new root entry.
     // Find the old root entry as well.
 
     // Need a strong ref. on |oldRootEntry| so it isn't destroyed when
     // SetChildHistoryEntry() does SwapHistoryEntries() (bug 304639).
-    nsCOMPtr<nsISHEntry> oldRootEntry = GetRootSHEntry(*aPtr);
+    nsCOMPtr<nsISHEntry> oldRootEntry = nsSHistory::GetRootSHEntry(*aPtr);
     if (oldRootEntry) {
       nsCOMPtr<nsIDocShellTreeItem> rootAsItem;
       GetSameTypeRootTreeItem(getter_AddRefs(rootAsItem));
       nsCOMPtr<nsIDocShell> rootShell = do_QueryInterface(rootAsItem);
       if (rootShell) { // if we're the root just set it, nothing to swap
-        SwapEntriesData data = { this, newRootEntry };
+        nsSHistory::SwapEntriesData data = { this, newRootEntry };
         nsIDocShell* rootIDocShell = static_cast<nsIDocShell*>(rootShell);
         nsDocShell* rootDocShell = static_cast<nsDocShell*>(rootIDocShell);
 
 #ifdef DEBUG
         nsresult rv =
 #endif
-        SetChildHistoryEntry(oldRootEntry, rootDocShell, 0, &data);
+        nsSHistory::SetChildHistoryEntry(oldRootEntry, rootDocShell, 0, &data);
         NS_ASSERTION(NS_SUCCEEDED(rv), "SetChildHistoryEntry failed");
       }
     }
   }
 
   *aPtr = aEntry;
 }
 
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -352,16 +352,26 @@ public:
     mAncestorOuterWindowIDs = mozilla::Move(aAncestorOuterWindowIDs);
   }
 
   const mozilla::OriginAttributes& GetOriginAttributes()
   {
     return mOriginAttributes;
   }
 
+  // Determine whether this docshell corresponds to the given history entry,
+  // via having a pointer to it in mOSHE or mLSHE.
+  bool HasHistoryEntry(nsISHEntry* aEntry) const
+  {
+    return aEntry && (aEntry == mOSHE || aEntry == mLSHE);
+  }
+
+  // Update any pointers (mOSHE or mLSHE) to aOldEntry to point to aNewEntry
+  void SwapHistoryEntries(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry);
+
   static bool SandboxFlagsImplyCookies(const uint32_t &aSandboxFlags);
 
   // Tell the favicon service that aNewURI has the same favicon as aOldURI.
   static void CopyFavicon(nsIURI* aOldURI,
                           nsIURI* aNewURI,
                           nsIPrincipal* aLoadingPrincipal,
                           bool aInPrivateBrowsing);
 
@@ -387,57 +397,16 @@ private: // member functions
   friend void mozilla::TimelineConsumers::AddMarkerForDocShell(
     nsDocShell*, const char*, const TimeStamp&, MarkerTracingType,
     MarkerStackRequest);
   friend void mozilla::TimelineConsumers::AddMarkerForDocShell(
     nsDocShell*, UniquePtr<AbstractTimelineMarker>&&);
   friend void mozilla::TimelineConsumers::PopMarkers(nsDocShell*,
     JSContext*, nsTArray<dom::ProfileTimelineMarker>&);
 
-  // Callback prototype for WalkHistoryEntries.
-  // aEntry is the child history entry, aShell is its corresponding docshell,
-  // aChildIndex is the child's index in its parent entry, and aData is
-  // the opaque pointer passed to WalkHistoryEntries.
-  typedef nsresult(*WalkHistoryEntriesFunc)(nsISHEntry* aEntry,
-                                            nsDocShell* aShell,
-                                            int32_t aChildIndex,
-                                            void* aData);
-
-  // Clone a session history tree for subframe navigation.
-  // The tree rooted at |aSrcEntry| will be cloned into |aDestEntry|, except
-  // for the entry with id |aCloneID|, which will be replaced with
-  // |aReplaceEntry|. |aSrcShell| is a (possibly null) docshell which
-  // corresponds to |aSrcEntry| via its mLSHE or mOHE pointers, and will
-  // have that pointer updated to point to the cloned history entry.
-  // If aCloneChildren is true then the children of the entry with id
-  // |aCloneID| will be cloned into |aReplaceEntry|.
-  static nsresult CloneAndReplace(nsISHEntry* aSrcEntry,
-                                  nsDocShell* aSrcShell,
-                                  uint32_t aCloneID,
-                                  nsISHEntry* aReplaceEntry,
-                                  bool aCloneChildren,
-                                  nsISHEntry** aDestEntry);
-
-  // Child-walking callback for CloneAndReplace
-  static nsresult CloneAndReplaceChild(nsISHEntry* aEntry, nsDocShell* aShell,
-                                       int32_t aChildIndex, void* aData);
-
-
-  // Child-walking callback for SetHistoryEntry
-  static nsresult SetChildHistoryEntry(nsISHEntry* aEntry, nsDocShell* aShell,
-                                       int32_t aEntryIndex, void* aData);
-
-  // For each child of aRootEntry, find the corresponding docshell which is
-  // a child of aRootShell, and call aCallback. The opaque pointer aData
-  // is passed to the callback.
-  static nsresult WalkHistoryEntries(nsISHEntry* aRootEntry,
-                                     nsDocShell* aRootShell,
-                                     WalkHistoryEntriesFunc aCallback,
-                                     void* aData);
-
   // Security checks to prevent frameset spoofing. See comments at
   // implementation sites.
   static bool CanAccessItem(nsIDocShellTreeItem* aTargetItem,
                             nsIDocShellTreeItem* aAccessingItem,
                             bool aConsiderOpener = true);
   static bool ValidateOrigin(nsIDocShellTreeItem* aOriginTreeItem,
                              nsIDocShellTreeItem* aTargetTreeItem);
 
@@ -523,26 +492,16 @@ private: // member functions
 
   nsresult AddChildSHEntryToParent(nsISHEntry* aNewEntry, int32_t aChildOffset,
                                    bool aCloneChildren);
 
   nsresult AddChildSHEntryInternal(nsISHEntry* aCloneRef, nsISHEntry* aNewEntry,
                                    int32_t aChildOffset, uint32_t aLoadType,
                                    bool aCloneChildren);
 
-  // Determine whether this docshell corresponds to the given history entry,
-  // via having a pointer to it in mOSHE or mLSHE.
-  bool HasHistoryEntry(nsISHEntry* aEntry) const
-  {
-    return aEntry && (aEntry == mOSHE || aEntry == mLSHE);
-  }
-
-  // Update any pointers (mOSHE or mLSHE) to aOldEntry to point to aNewEntry
-  void SwapHistoryEntries(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry);
-
   // Call this method to swap in a new history entry to m[OL]SHE, rather than
   // setting it directly. This completes the navigation in all docshells
   // in the case of a subframe navigation.
   void SetHistoryEntry(nsCOMPtr<nsISHEntry>* aPtr, nsISHEntry* aEntry);
 
   //
   // URI Load
   //
--- a/docshell/shistory/nsSHistory.cpp
+++ b/docshell/shistory/nsSHistory.cpp
@@ -372,16 +372,233 @@ nsSHistory::Shutdown()
     if (obsSvc) {
       obsSvc->RemoveObserver(gObserver, "cacheservice:empty-cache");
       obsSvc->RemoveObserver(gObserver, "memory-pressure");
     }
     gObserver = nullptr;
   }
 }
 
+// static
+nsISHEntry*
+nsSHistory::GetRootSHEntry(nsISHEntry* aEntry)
+{
+  nsCOMPtr<nsISHEntry> rootEntry = aEntry;
+  nsISHEntry* result = nullptr;
+  while (rootEntry) {
+    result = rootEntry;
+    result->GetParent(getter_AddRefs(rootEntry));
+  }
+
+  return result;
+}
+
+// static
+nsresult
+nsSHistory::WalkHistoryEntries(nsISHEntry* aRootEntry,
+                               nsDocShell* aRootShell,
+                               WalkHistoryEntriesFunc aCallback,
+                               void* aData)
+{
+  NS_ENSURE_TRUE(aRootEntry, NS_ERROR_FAILURE);
+
+  nsCOMPtr<nsISHContainer> container(do_QueryInterface(aRootEntry));
+  if (!container) {
+    return NS_ERROR_FAILURE;
+  }
+
+  int32_t childCount;
+  container->GetChildCount(&childCount);
+  for (int32_t i = 0; i < childCount; i++) {
+    nsCOMPtr<nsISHEntry> childEntry;
+    container->GetChildAt(i, getter_AddRefs(childEntry));
+    if (!childEntry) {
+      // childEntry can be null for valid reasons, for example if the
+      // docshell at index i never loaded anything useful.
+      // Remember to clone also nulls in the child array (bug 464064).
+      aCallback(nullptr, nullptr, i, aData);
+      continue;
+    }
+
+    nsDocShell* childShell = nullptr;
+    if (aRootShell) {
+      // Walk the children of aRootShell and see if one of them
+      // has srcChild as a SHEntry.
+      int32_t length;
+      aRootShell->GetChildCount(&length);
+      for (int32_t i = 0; i < length; i++) {
+        nsCOMPtr<nsIDocShellTreeItem> item;
+        nsresult rv = aRootShell->GetChildAt(i, getter_AddRefs(item));
+        NS_ENSURE_SUCCESS(rv, rv);
+        nsDocShell* child = static_cast<nsDocShell*>(item.get());
+        if (child->HasHistoryEntry(childEntry)) {
+          childShell = child;
+          break;
+        }
+      }
+    }
+    nsresult rv = aCallback(childEntry, childShell, i, aData);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return NS_OK;
+}
+
+// callback data for WalkHistoryEntries
+struct MOZ_STACK_CLASS CloneAndReplaceData
+{
+  CloneAndReplaceData(uint32_t aCloneID, nsISHEntry* aReplaceEntry,
+                      bool aCloneChildren, nsISHEntry* aDestTreeParent)
+    : cloneID(aCloneID)
+    , cloneChildren(aCloneChildren)
+    , replaceEntry(aReplaceEntry)
+    , destTreeParent(aDestTreeParent)
+  {
+  }
+
+  uint32_t cloneID;
+  bool cloneChildren;
+  nsISHEntry* replaceEntry;
+  nsISHEntry* destTreeParent;
+  nsCOMPtr<nsISHEntry> resultEntry;
+};
+
+// static
+nsresult
+nsSHistory::CloneAndReplaceChild(nsISHEntry* aEntry,
+                                          nsDocShell* aShell,
+                                          int32_t aEntryIndex,
+                                          void* aData)
+{
+  nsCOMPtr<nsISHEntry> dest;
+
+  CloneAndReplaceData* data = static_cast<CloneAndReplaceData*>(aData);
+  uint32_t cloneID = data->cloneID;
+  nsISHEntry* replaceEntry = data->replaceEntry;
+
+  nsCOMPtr<nsISHContainer> container = do_QueryInterface(data->destTreeParent);
+  if (!aEntry) {
+    if (container) {
+      container->AddChild(nullptr, aEntryIndex);
+    }
+    return NS_OK;
+  }
+
+  uint32_t srcID;
+  aEntry->GetID(&srcID);
+
+  nsresult rv = NS_OK;
+  if (srcID == cloneID) {
+    // Replace the entry
+    dest = replaceEntry;
+  } else {
+    // Clone the SHEntry...
+    rv = aEntry->Clone(getter_AddRefs(dest));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  dest->SetIsSubFrame(true);
+
+  if (srcID != cloneID || data->cloneChildren) {
+    // Walk the children
+    CloneAndReplaceData childData(cloneID, replaceEntry,
+                                  data->cloneChildren, dest);
+    rv = WalkHistoryEntries(aEntry, aShell,
+                            CloneAndReplaceChild, &childData);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  if (srcID != cloneID && aShell) {
+    aShell->SwapHistoryEntries(aEntry, dest);
+  }
+
+  if (container) {
+    container->AddChild(dest, aEntryIndex);
+  }
+
+  data->resultEntry = dest;
+  return rv;
+}
+
+// static
+nsresult
+nsSHistory::CloneAndReplace(nsISHEntry* aSrcEntry,
+                                     nsDocShell* aSrcShell,
+                                     uint32_t aCloneID,
+                                     nsISHEntry* aReplaceEntry,
+                                     bool aCloneChildren,
+                                     nsISHEntry** aResultEntry)
+{
+  NS_ENSURE_ARG_POINTER(aResultEntry);
+  NS_ENSURE_TRUE(aReplaceEntry, NS_ERROR_FAILURE);
+
+  CloneAndReplaceData data(aCloneID, aReplaceEntry, aCloneChildren, nullptr);
+  nsresult rv = CloneAndReplaceChild(aSrcEntry, aSrcShell, 0, &data);
+
+  data.resultEntry.swap(*aResultEntry);
+  return rv;
+}
+
+// static
+nsresult
+nsSHistory::SetChildHistoryEntry(nsISHEntry* aEntry, nsDocShell* aShell,
+                                 int32_t aEntryIndex, void* aData)
+{
+  SwapEntriesData* data = static_cast<SwapEntriesData*>(aData);
+  nsDocShell* ignoreShell = data->ignoreShell;
+
+  if (!aShell || aShell == ignoreShell) {
+    return NS_OK;
+  }
+
+  nsISHEntry* destTreeRoot = data->destTreeRoot;
+
+  nsCOMPtr<nsISHEntry> destEntry;
+  nsCOMPtr<nsISHContainer> container = do_QueryInterface(data->destTreeParent);
+
+  if (container) {
+    // aEntry is a clone of some child of destTreeParent, but since the
+    // trees aren't necessarily in sync, we'll have to locate it.
+    // Note that we could set aShell's entry to null if we don't find a
+    // corresponding entry under destTreeParent.
+
+    uint32_t targetID, id;
+    aEntry->GetID(&targetID);
+
+    // First look at the given index, since this is the common case.
+    nsCOMPtr<nsISHEntry> entry;
+    container->GetChildAt(aEntryIndex, getter_AddRefs(entry));
+    if (entry && NS_SUCCEEDED(entry->GetID(&id)) && id == targetID) {
+      destEntry.swap(entry);
+    } else {
+      int32_t childCount;
+      container->GetChildCount(&childCount);
+      for (int32_t i = 0; i < childCount; ++i) {
+        container->GetChildAt(i, getter_AddRefs(entry));
+        if (!entry) {
+          continue;
+        }
+
+        entry->GetID(&id);
+        if (id == targetID) {
+          destEntry.swap(entry);
+          break;
+        }
+      }
+    }
+  } else {
+    destEntry = destTreeRoot;
+  }
+
+  aShell->SwapHistoryEntries(aEntry, destEntry);
+
+  // Now handle the children of aEntry.
+  SwapEntriesData childData = { ignoreShell, destTreeRoot, destEntry };
+  return WalkHistoryEntries(aEntry, aShell, SetChildHistoryEntry, &childData);
+}
+
 /* Add an entry to the History list at mIndex and
  * increment the index to point to the new entry
  */
 NS_IMETHODIMP
 nsSHistory::AddEntry(nsISHEntry* aSHEntry, bool aPersist)
 {
   NS_ENSURE_ARG(aSHEntry);
 
--- a/docshell/shistory/nsSHistory.h
+++ b/docshell/shistory/nsSHistory.h
@@ -56,16 +56,25 @@ public:
     }
 
   private:
     // HistoryTracker is owned by nsSHistory; it always outlives HistoryTracker
     // so it's safe to use raw pointer here.
     nsSHistory* mSHistory;
   };
 
+  // Structure used in SetChildHistoryEntry
+  struct SwapEntriesData
+  {
+    nsDocShell* ignoreShell;     // constant; the shell to ignore
+    nsISHEntry* destTreeRoot;    // constant; the root of the dest tree
+    nsISHEntry* destTreeParent;  // constant; the node under destTreeRoot
+                                 // whose children will correspond to aEntry
+  };
+
   nsSHistory();
   NS_DECL_ISUPPORTS
   NS_DECL_NSISHISTORY
   NS_DECL_NSISHISTORYINTERNAL
   NS_DECL_NSIWEBNAVIGATION
 
   // One time initialization method called upon docshell module construction
   static nsresult Startup();
@@ -73,16 +82,60 @@ public:
   static void UpdatePrefs();
 
   // Max number of total cached content viewers.  If the pref
   // browser.sessionhistory.max_total_viewers is negative, then
   // this value is calculated based on the total amount of memory.
   // Otherwise, it comes straight from the pref.
   static uint32_t GetMaxTotalViewers() { return sHistoryMaxTotalViewers; }
 
+  // Get the root SHEntry from a given entry.
+  static nsISHEntry* GetRootSHEntry(nsISHEntry* aEntry);
+
+  // Callback prototype for WalkHistoryEntries.
+  // aEntry is the child history entry, aShell is its corresponding docshell,
+  // aChildIndex is the child's index in its parent entry, and aData is
+  // the opaque pointer passed to WalkHistoryEntries.
+  typedef nsresult(*WalkHistoryEntriesFunc)(nsISHEntry* aEntry,
+                                            nsDocShell* aShell,
+                                            int32_t aChildIndex,
+                                            void* aData);
+
+  // Clone a session history tree for subframe navigation.
+  // The tree rooted at |aSrcEntry| will be cloned into |aDestEntry|, except
+  // for the entry with id |aCloneID|, which will be replaced with
+  // |aReplaceEntry|. |aSrcShell| is a (possibly null) docshell which
+  // corresponds to |aSrcEntry| via its mLSHE or mOHE pointers, and will
+  // have that pointer updated to point to the cloned history entry.
+  // If aCloneChildren is true then the children of the entry with id
+  // |aCloneID| will be cloned into |aReplaceEntry|.
+  static nsresult CloneAndReplace(nsISHEntry* aSrcEntry,
+                                  nsDocShell* aSrcShell,
+                                  uint32_t aCloneID,
+                                  nsISHEntry* aReplaceEntry,
+                                  bool aCloneChildren,
+                                  nsISHEntry** aDestEntry);
+
+  // Child-walking callback for CloneAndReplace
+  static nsresult CloneAndReplaceChild(nsISHEntry* aEntry, nsDocShell* aShell,
+                                       int32_t aChildIndex, void* aData);
+
+
+  // Child-walking callback for SetHistoryEntry
+  static nsresult SetChildHistoryEntry(nsISHEntry* aEntry, nsDocShell* aShell,
+                                       int32_t aEntryIndex, void* aData);
+
+  // For each child of aRootEntry, find the corresponding docshell which is
+  // a child of aRootShell, and call aCallback. The opaque pointer aData
+  // is passed to the callback.
+  static nsresult WalkHistoryEntries(nsISHEntry* aRootEntry,
+                                     nsDocShell* aRootShell,
+                                     WalkHistoryEntriesFunc aCallback,
+                                     void* aData);
+
 private:
   virtual ~nsSHistory();
   friend class nsSHEnumerator;
   friend class nsSHistoryObserver;
 
   nsresult GetTransactionAtIndex(int32_t aIndex, nsISHTransaction** aResult);
   nsresult LoadDifferingEntries(nsISHEntry* aPrevEntry, nsISHEntry* aNextEntry,
                                 nsIDocShell* aRootDocShell, long aLoadType,