Bug 1596958 - Synchronize layouthistorystate to parent process, r=peterv
☠☠ backed out by 8d0875c13729 ☠ ☠
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Thu, 05 Dec 2019 17:33:48 +0000
changeset 505668 69ac0bf82505f7981e2ecff1824bbabb890669a5
parent 505667 68fff16609f01878bd216ab07ba7ac5d5954ee59
child 505669 fdeec504ed0467d2b573af091ed92becbb45b9d9
push id36886
push usernbeleuzu@mozilla.com
push dateFri, 06 Dec 2019 04:43:57 +0000
treeherdermozilla-central@10160518ddc8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspeterv
bugs1596958
milestone73.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 1596958 - Synchronize layouthistorystate to parent process, r=peterv Differential Revision: https://phabricator.services.mozilla.com/D53288
docshell/base/nsDocShell.cpp
docshell/base/nsIDocShell.idl
docshell/shistory/PSHEntry.ipdl
docshell/shistory/SHEntryChild.cpp
docshell/shistory/SHEntryParent.cpp
docshell/shistory/SHEntryParent.h
docshell/shistory/nsISHEntry.idl
docshell/shistory/nsSHEntry.cpp
docshell/shistory/nsSHEntry.h
docshell/shistory/nsSHEntryShared.cpp
docshell/shistory/nsSHEntryShared.h
dom/base/Document.cpp
layout/base/nsDocumentViewer.cpp
layout/base/nsILayoutHistoryState.idl
layout/base/nsLayoutHistoryState.cpp
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -3437,16 +3437,23 @@ nsDocShell::GetCurrentSHEntry(nsISHEntry
     NS_ADDREF(*aEntry = mLSHE);
   } else if (mOSHE) {
     NS_ADDREF(*aEntry = mOSHE);
     *aOSHE = true;
   }
   return NS_OK;
 }
 
+NS_IMETHODIMP nsDocShell::SynchronizeLayoutHistoryState() {
+  if (mOSHE) {
+    mOSHE->SynchronizeLayoutHistoryState();
+  }
+  return NS_OK;
+}
+
 nsIScriptGlobalObject* nsDocShell::GetScriptGlobalObject() {
   NS_ENSURE_SUCCESS(EnsureScriptEnvironment(), nullptr);
   return mScriptGlobal;
 }
 
 Document* nsDocShell::GetDocument() {
   NS_ENSURE_SUCCESS(EnsureContentViewer(), nullptr);
   return mContentViewer->GetDocument();
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -1148,9 +1148,14 @@ interface nsIDocShell : nsIDocShellTreeI
    * This will be true when the following methods are executing:
    *   nsIWebNavigation.binaryLoadURI
    *   nsIWebNavigation.goBack
    *   nsIWebNavigation.goForward
    *   nsIWebNavigation.gotoIndex
    *   nsIWebNavigation.loadURI
    */
   [infallible] readonly attribute boolean isNavigating;
+
+  /**
+   * @see nsISHEntry synchronizeLayoutHistoryState().
+   */
+  void synchronizeLayoutHistoryState();
 };
--- a/docshell/shistory/PSHEntry.ipdl
+++ b/docshell/shistory/PSHEntry.ipdl
@@ -1,16 +1,17 @@
 /* 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 protocol PContent;
 include protocol PSHistory;
 
 include DOMTypes;
+include PresState;
 
 using refcounted class mozilla::dom::CrossProcessSHEntry from "mozilla/dom/MaybeNewPSHEntry.h";
 using refcounted class nsDocShellLoadState from "mozilla/dom/DocShellMessageUtils.h";
 using struct nsID from "nsID.h";
 using nsIntRect from "nsRect.h";
 
 namespace mozilla {
 namespace dom {
@@ -103,13 +104,16 @@ parent:
   sync AddChild(nullable PSHEntry childEntry, int32_t offset, bool useRemoteSubframes) returns (nsresult result);
   sync RemoveChild(PSHEntry childEntry) returns (nsresult result);
   sync GetChildAt(int32_t index) returns (CrossProcessSHEntry childEntry);
   sync GetChildSHEntryIfHasNoDynamicallyAddedChild(int32_t childOffset) returns (CrossProcessSHEntry childEntry);
   sync ReplaceChild(PSHEntry newChildEntry) returns (nsresult result);
   async ClearEntry(uint64_t aNewSharedID);
   sync CreateLoadInfo() returns (nsDocShellLoadState loadState);
 
+  async UpdateLayoutHistoryState(bool scrollPositionOnly,
+                                 nsCString[] keys, PresState[] states);
+
   sync __delete__();
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/docshell/shistory/SHEntryChild.cpp
+++ b/docshell/shistory/SHEntryChild.cpp
@@ -449,49 +449,60 @@ SHEntryChild::GetPostData(nsIInputStream
 
 NS_IMETHODIMP
 SHEntryChild::SetPostData(nsIInputStream* aPostData) {
   return SendSetPostData(aPostData) ? NS_OK : NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 SHEntryChild::GetLayoutHistoryState(nsILayoutHistoryState** aResult) {
-  // FIXME Bug 1547734 Move to parent.
   NS_IF_ADDREF(*aResult = mShared->mLayoutHistoryState);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 SHEntryChild::SetLayoutHistoryState(nsILayoutHistoryState* aState) {
-  // FIXME Bug 1547734 Move to parent.
   mShared->mLayoutHistoryState = aState;
   if (mShared->mLayoutHistoryState) {
     mShared->mLayoutHistoryState->SetScrollPositionOnly(
         !mShared->mSaveLayoutState);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 SHEntryChild::InitLayoutHistoryState(nsILayoutHistoryState** aState) {
-  // FIXME Bug 1547734  Move to parent.
   nsCOMPtr<nsILayoutHistoryState> historyState;
   if (mShared->mLayoutHistoryState) {
     historyState = mShared->mLayoutHistoryState;
   } else {
     historyState = NS_NewLayoutHistoryState();
     SetLayoutHistoryState(historyState);
   }
 
   historyState.forget(aState);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+SHEntryChild::SynchronizeLayoutHistoryState() {
+  if (!mShared->mLayoutHistoryState) {
+    return NS_OK;
+  }
+
+  bool scrollPositionOnly = false;
+  nsTArray<nsCString> keys;
+  nsTArray<mozilla::PresState> states;
+  mShared->mLayoutHistoryState->GetContents(&scrollPositionOnly, keys, states);
+  Unused << SendUpdateLayoutHistoryState(scrollPositionOnly, keys, states);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 SHEntryChild::GetLoadType(uint32_t* aResult) {
   return SendGetLoadType(aResult) ? NS_OK : NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 SHEntryChild::SetLoadType(uint32_t aLoadType) {
   return SendSetLoadType(aLoadType) ? NS_OK : NS_ERROR_FAILURE;
 }
--- a/docshell/shistory/SHEntryParent.cpp
+++ b/docshell/shistory/SHEntryParent.cpp
@@ -553,10 +553,34 @@ bool SHEntryParent::RecvClearEntry(const
 }
 
 bool SHEntryParent::RecvCreateLoadInfo(
     RefPtr<nsDocShellLoadState>* aLoadState) {
   mEntry->CreateLoadInfo(getter_AddRefs(*aLoadState));
   return true;
 }
 
+bool SHEntryParent::RecvUpdateLayoutHistoryState(
+    const bool& aScrollPositionOnly, nsTArray<nsCString>&& aKeys,
+    nsTArray<PresState>&& aStates) {
+  nsCOMPtr<nsILayoutHistoryState> layoutHistoryState;
+  // InitLayoutHistoryState creates a new object only if there isn't one
+  // already.
+  mEntry->InitLayoutHistoryState(getter_AddRefs(layoutHistoryState));
+  layoutHistoryState->Reset();
+
+  if (aKeys.Length() != aStates.Length()) {
+    NS_WARNING("Bogus data sent from the child process?");
+    return true;
+  }
+
+  layoutHistoryState->SetScrollPositionOnly(aScrollPositionOnly);
+
+  for (uint32_t i = 0; i < aKeys.Length(); ++i) {
+    PresState& state = aStates[i];
+    UniquePtr<PresState> newState = MakeUnique<PresState>(state);
+    layoutHistoryState->AddState(aKeys[i], std::move(newState));
+  }
+  return true;
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/docshell/shistory/SHEntryParent.h
+++ b/docshell/shistory/SHEntryParent.h
@@ -199,15 +199,19 @@ class SHEntryParent final : public PSHEn
                       RefPtr<CrossProcessSHEntry>* aChild);
   bool RecvGetChildSHEntryIfHasNoDynamicallyAddedChild(
       const int32_t& aChildOffset, RefPtr<CrossProcessSHEntry>* aChild);
   bool RecvReplaceChild(PSHEntryParent* aNewChild, nsresult* aResult);
   bool RecvClearEntry(const uint64_t& aNewSharedID);
 
   bool RecvCreateLoadInfo(RefPtr<nsDocShellLoadState>* aLoadState);
 
+  bool RecvUpdateLayoutHistoryState(const bool& aScrollPositionOnly,
+                                    nsTArray<nsCString>&& aKeys,
+                                    nsTArray<PresState>&& aStates);
+
   RefPtr<LegacySHEntry> mEntry;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif /* mozilla_dom_SHEntryParent_h */
--- a/docshell/shistory/nsISHEntry.idl
+++ b/docshell/shistory/nsISHEntry.idl
@@ -417,10 +417,17 @@ interface nsISHEntry : nsISupports
     
     /**
      * Create nsDocShellLoadState and fill it with information. 
      * Don't set nsSHEntry here to avoid serializing it.
      */
     [noscript] nsDocShellLoadStatePtr CreateLoadInfo();
 
     [infallible] readonly attribute unsigned long long bfcacheID;
+
+    /**
+     * synchronizeLayoutHistoryState() can be used to synchronize
+     * layoutHistoryState object to the parent process in case session history
+     * lives there. With in-process session history this method is no-op.
+     */
+    void synchronizeLayoutHistoryState();
 };
 
--- a/docshell/shistory/nsSHEntry.cpp
+++ b/docshell/shistory/nsSHEntry.cpp
@@ -229,35 +229,43 @@ nsSHEntry::GetPostData(nsIInputStream** 
 NS_IMETHODIMP
 nsSHEntry::SetPostData(nsIInputStream* aPostData) {
   mPostData = aPostData;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetLayoutHistoryState(nsILayoutHistoryState** aResult) {
-  MOZ_CRASH(
-      "Classes inheriting from nsSHEntry should implement this. "
-      "Bug 1546344 will clean this up.");
+  *aResult = mShared->mLayoutHistoryState;
+  NS_IF_ADDREF(*aResult);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetLayoutHistoryState(nsILayoutHistoryState* aState) {
-  MOZ_CRASH(
-      "Classes inheriting from nsSHEntry should implement this. "
-      "Bug 1546344 will clean this up.");
+  MOZ_ASSERT(!mShared->mLayoutHistoryState);
+  mShared->mLayoutHistoryState = aState;
+  if (mShared->mLayoutHistoryState) {
+    mShared->mLayoutHistoryState->SetScrollPositionOnly(
+        !mShared->mSaveLayoutState);
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::InitLayoutHistoryState(nsILayoutHistoryState** aState) {
-  MOZ_CRASH(
-      "Classes inheriting from nsSHEntry should implement this. "
-      "Bug 1546344 will clean this up.");
+  if (!mShared->mLayoutHistoryState) {
+    nsCOMPtr<nsILayoutHistoryState> historyState;
+    historyState = NS_NewLayoutHistoryState();
+    SetLayoutHistoryState(historyState);
+  }
+
+  nsCOMPtr<nsILayoutHistoryState> state = GetLayoutHistoryState();
+  state.forget(aState);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetLoadType(uint32_t* aResult) {
   *aResult = mLoadType;
   return NS_OK;
 }
@@ -1020,75 +1028,50 @@ void nsSHEntry::EvictContentViewer() {
     // Drop the presentation state before destroying the viewer, so that
     // document teardown is able to correctly persist the state.
     SetContentViewer(nullptr);
     SyncPresentationState();
     viewer->Destroy();
   }
 }
 
+NS_IMETHODIMP
+nsSHEntry::SynchronizeLayoutHistoryState() {
+  // No-op on purpose. See nsISHEntry.idl
+  return NS_OK;
+}
+
 nsLegacySHEntry::nsLegacySHEntry(nsISHistory* aHistory, uint64_t aID)
     : nsSHEntry(new nsSHEntryShared(aHistory, aID)) {}
 
 NS_IMETHODIMP
 nsLegacySHEntry::SetContentViewer(nsIContentViewer* aViewer) {
   return GetState()->SetContentViewer(aViewer);
 }
 
 NS_IMETHODIMP
 nsLegacySHEntry::GetContentViewer(nsIContentViewer** aResult) {
   *aResult = GetState()->mContentViewer;
   NS_IF_ADDREF(*aResult);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsLegacySHEntry::GetLayoutHistoryState(nsILayoutHistoryState** aResult) {
-  *aResult = GetState()->mLayoutHistoryState;
-  NS_IF_ADDREF(*aResult);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsLegacySHEntry::SetLayoutHistoryState(nsILayoutHistoryState* aState) {
-  GetState()->mLayoutHistoryState = aState;
-  if (GetState()->mLayoutHistoryState) {
-    GetState()->mLayoutHistoryState->SetScrollPositionOnly(
-        !GetState()->mSaveLayoutState);
-  }
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsLegacySHEntry::InitLayoutHistoryState(nsILayoutHistoryState** aState) {
-  if (!GetState()->mLayoutHistoryState) {
-    nsCOMPtr<nsILayoutHistoryState> historyState;
-    historyState = NS_NewLayoutHistoryState();
-    SetLayoutHistoryState(historyState);
-  }
-
-  nsCOMPtr<nsILayoutHistoryState> state = GetLayoutHistoryState();
-  state.forget(aState);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 nsLegacySHEntry::Create(
     nsIURI* aURI, const nsAString& aTitle, nsIInputStream* aInputStream,
     uint32_t aCacheKey, const nsACString& aContentType,
     nsIPrincipal* aTriggeringPrincipal, nsIPrincipal* aPrincipalToInherit,
     nsIPrincipal* aStoragePrincipalToInherit, nsIContentSecurityPolicy* aCsp,
     const nsID& aDocShellID, bool aDynamicCreation, nsIURI* aOriginalURI,
     nsIURI* aResultPrincipalURI, bool aLoadReplace,
     nsIReferrerInfo* aReferrerInfo, const nsAString& aSrcdocData,
     bool aSrcdocEntry, nsIURI* aBaseURI, bool aSaveLayoutState, bool aExpired) {
-  GetState()->mLayoutHistoryState = nullptr;
+  mShared->mLayoutHistoryState = nullptr;
 
-  GetState()->mSaveLayoutState = aSaveLayoutState;
+  mShared->mSaveLayoutState = aSaveLayoutState;
 
   return nsSHEntry::Create(aURI, aTitle, aInputStream, aCacheKey, aContentType,
                            aTriggeringPrincipal, aPrincipalToInherit,
                            aStoragePrincipalToInherit, aCsp, aDocShellID,
                            aDynamicCreation, aOriginalURI, aResultPrincipalURI,
                            aLoadReplace, aReferrerInfo, aSrcdocData,
                            aSrcdocEntry, aBaseURI, aSaveLayoutState, aExpired);
 }
@@ -1097,25 +1080,25 @@ NS_IMETHODIMP
 nsLegacySHEntry::Clone(nsISHEntry** aResult) {
   nsCOMPtr<nsISHEntry> entry = new nsLegacySHEntry(*this);
   entry.forget(aResult);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsLegacySHEntry::GetSaveLayoutStateFlag(bool* aFlag) {
-  *aFlag = GetState()->mSaveLayoutState;
+  *aFlag = mShared->mSaveLayoutState;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsLegacySHEntry::SetSaveLayoutStateFlag(bool aFlag) {
-  GetState()->mSaveLayoutState = aFlag;
-  if (GetState()->mLayoutHistoryState) {
-    GetState()->mLayoutHistoryState->SetScrollPositionOnly(!aFlag);
+  mShared->mSaveLayoutState = aFlag;
+  if (mShared->mLayoutHistoryState) {
+    mShared->mLayoutHistoryState->SetScrollPositionOnly(!aFlag);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsLegacySHEntry::SetWindowState(nsISupports* aState) {
   GetState()->mWindowState = aState;
--- a/docshell/shistory/nsSHEntry.h
+++ b/docshell/shistory/nsSHEntry.h
@@ -84,28 +84,24 @@ class nsLegacySHEntry final : public nsS
 
   NS_IMETHOD GetContentViewer(nsIContentViewer** aResult) override;
   NS_IMETHOD SetContentViewer(nsIContentViewer* aViewer) override;
   NS_IMETHOD GetWindowState(nsISupports** aState) override;
   NS_IMETHOD SetWindowState(nsISupports* aState) override;
   using nsISHEntry::GetRefreshURIList;
   NS_IMETHOD GetRefreshURIList(nsIMutableArray** aRefreshURIList) override;
   NS_IMETHOD SetRefreshURIList(nsIMutableArray* aRefreshURIList) override;
-  using nsSHEntry::GetLayoutHistoryState;
-  NS_IMETHOD GetLayoutHistoryState(nsILayoutHistoryState** aResult) override;
-  NS_IMETHOD SetLayoutHistoryState(nsILayoutHistoryState* aState) override;
   using nsISHEntry::GetSaveLayoutStateFlag;
   NS_IMETHOD GetSaveLayoutStateFlag(bool* aSaveLayoutStateFlag) override;
   NS_IMETHOD SetSaveLayoutStateFlag(bool aSaveLayoutStateFlag) override;
   NS_IMETHOD_(void) AddChildShell(nsIDocShellTreeItem* aShell) override;
   NS_IMETHOD ChildShellAt(int32_t aIndex,
                           nsIDocShellTreeItem** aShell) override;
   NS_IMETHOD_(void) ClearChildShells() override;
   NS_IMETHOD_(void) SyncPresentationState() override;
-  NS_IMETHOD InitLayoutHistoryState(nsILayoutHistoryState** aState) override;
   NS_IMETHOD Create(
       nsIURI* aURI, const nsAString& aTitle, nsIInputStream* aInputStream,
       uint32_t aCacheKey, const nsACString& aContentType,
       nsIPrincipal* aTriggeringPrincipal, nsIPrincipal* aPrincipalToInherit,
       nsIPrincipal* aStoragePrincipalToInherit, nsIContentSecurityPolicy* aCsp,
       const nsID& aDocshellID, bool aDynamicCreation, nsIURI* aOriginalURI,
       nsIURI* aResultPrincipalURI, bool aLoadReplace,
       nsIReferrerInfo* aReferrerInfo, const nsAString& aSrcdocData,
--- a/docshell/shistory/nsSHEntryShared.cpp
+++ b/docshell/shistory/nsSHEntryShared.cpp
@@ -36,26 +36,28 @@ SHEntrySharedParentState::SHEntrySharedP
       mViewerBounds(0, 0, 0, 0),
       mCacheKey(0),
       mLastTouched(0),
       mID(aID),
       mSHistory(aSHistory),
       mIsFrameNavigation(false),
       mSticky(true),
       mDynamicallyCreated(false),
-      mExpired(false) {}
+      mExpired(false),
+      mSaveLayoutState(true) {}
 
 SHEntrySharedParentState::~SHEntrySharedParentState() {}
 
 void SHEntrySharedParentState::CopyFrom(SHEntrySharedParentState* aEntry) {
   mDocShellID = aEntry->mDocShellID;
   mTriggeringPrincipal = aEntry->mTriggeringPrincipal;
   mPrincipalToInherit = aEntry->mPrincipalToInherit;
   mStoragePrincipalToInherit = aEntry->mStoragePrincipalToInherit;
   mCsp = aEntry->mCsp;
+  mSaveLayoutState = aEntry->mSaveLayoutState;
   mContentType.Assign(aEntry->mContentType);
   mIsFrameNavigation = aEntry->mIsFrameNavigation;
   mSticky = aEntry->mSticky;
   mDynamicallyCreated = aEntry->mDynamicallyCreated;
   mCacheKey = aEntry->mCacheKey;
   mLastTouched = aEntry->mLastTouched;
 }
 
--- a/docshell/shistory/nsSHEntryShared.h
+++ b/docshell/shistory/nsSHEntryShared.h
@@ -67,16 +67,19 @@ class SHEntrySharedParentState {
 
   // These members are copied by SHEntrySharedParentState::CopyFrom(). If you
   // add a member here, be sure to update the CopyFrom() implementation.
   nsID mDocShellID;
   nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
   nsCOMPtr<nsIPrincipal> mPrincipalToInherit;
   nsCOMPtr<nsIPrincipal> mStoragePrincipalToInherit;
   nsCOMPtr<nsIContentSecurityPolicy> mCsp;
+  // Child side updates layout history state when page is being unloaded or
+  // moved to bfcache.
+  nsCOMPtr<nsILayoutHistoryState> mLayoutHistoryState;
   nsCString mContentType;
 
   nsIntRect mViewerBounds;
 
   uint32_t mCacheKey;
   uint32_t mLastTouched;
 
   // These members aren't copied by SHEntrySharedParentState::CopyFrom() because
@@ -85,16 +88,18 @@ class SHEntrySharedParentState {
   nsWeakPtr mSHistory;
 
   bool mIsFrameNavigation;
   bool mSticky;
   bool mDynamicallyCreated;
 
   // This flag is about necko cache, not bfcache.
   bool mExpired;
+
+  bool mSaveLayoutState;
 };
 
 /**
  * SHEntrySharedChildState holds the shared state that needs to live in the
  * process where the document was loaded.
  */
 class SHEntrySharedChildState {
  protected:
@@ -124,16 +129,19 @@ class SHEntrySharedChildState {
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 /**
  * nsSHEntryShared holds the shared state if the session history is not stored
  * in the parent process, or if the load itself happens in the parent process.
+ * Note, since nsSHEntryShared inherits both SHEntrySharedParentState and
+ * SHEntrySharedChildState and those have some same member variables,
+ * the ones from SHEntrySharedParentState should be used.
  */
 class nsSHEntryShared final : public nsIBFCacheEntry,
                               public nsStubMutationObserver,
                               public mozilla::dom::SHEntrySharedParentState,
                               public mozilla::dom::SHEntrySharedChildState {
  public:
   static void EnsureHistoryTracker();
   static void Shutdown();
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -10325,16 +10325,21 @@ void Document::RemovedFromDocShell() {
 
   mRemovedFromDocShell = true;
   EnumerateActivityObservers(NotifyActivityChanged, nullptr);
 
   for (nsIContent* child = GetFirstChild(); child;
        child = child->GetNextSibling()) {
     child->SaveSubtreeState();
   }
+
+  nsIDocShell* docShell = GetDocShell();
+  if (docShell) {
+    docShell->SynchronizeLayoutHistoryState();
+  }
 }
 
 already_AddRefed<nsILayoutHistoryState> Document::GetLayoutHistoryState()
     const {
   nsCOMPtr<nsILayoutHistoryState> state;
   if (!mScriptGlobalObject) {
     state = mLayoutHistoryState;
   } else {
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -1692,16 +1692,17 @@ nsDocumentViewer::Close(nsISHEntry* aSHE
     }
   }
 
   return NS_OK;
 }
 
 static void DetachContainerRecurse(nsIDocShell* aShell) {
   // Unhook this docshell's presentation
+  aShell->SynchronizeLayoutHistoryState();
   nsCOMPtr<nsIContentViewer> viewer;
   aShell->GetContentViewer(getter_AddRefs(viewer));
   if (viewer) {
     if (Document* doc = viewer->GetDocument()) {
       doc->SetContainer(nullptr);
     }
     if (PresShell* presShell = viewer->GetPresShell()) {
       auto weakShell = static_cast<nsDocShell*>(aShell);
@@ -1819,16 +1820,17 @@ nsDocumentViewer::Destroy() {
     nsCOMPtr<nsISHEntry> shEntry = mSHEntry.forget();  // we'll need this below
 
     shEntry->SetContentViewer(this);
 
     // Always sync the presentation state.  That way even if someone screws up
     // and shEntry has no window state at this point we'll be ok; we just won't
     // cache ourselves.
     shEntry->SyncPresentationState();
+    shEntry->SynchronizeLayoutHistoryState();
 
     // Shut down accessibility for the document before we start to tear it down.
 #ifdef ACCESSIBILITY
     if (mPresShell) {
       a11y::DocAccessible* docAcc = mPresShell->GetDocAccessible();
       if (docAcc) {
         docAcc->Shutdown();
       }
--- a/layout/base/nsILayoutHistoryState.idl
+++ b/layout/base/nsILayoutHistoryState.idl
@@ -8,16 +8,17 @@
  * the document is not
  */
 
 #include "nsISupports.idl"
 
 
 [ptr] native PresStatePtr(mozilla::PresState);
 native PresStateUnique(mozilla::UniquePtr<mozilla::PresState>);
+native PresState(mozilla::PresState);
 [ref] native nsCString(const nsCString);
 native constBool(const bool);
 
 %{C++
 #include "nsStringFwd.h"
 #include "mozilla/UniquePtr.h"
 
 namespace mozilla {
@@ -89,16 +90,28 @@ interface nsILayoutHistoryState : nsISup
    * or all possible history
    */
   [noscript, notxpcom, nostdcall] void SetScrollPositionOnly(in constBool aFlag);
 
   /**
    * Resets PresState::GetScrollState of all PresState objects to 0,0.
    */
   [noscript, notxpcom, nostdcall] void ResetScrollState();
+
+  /**
+   * Get the contents of the layout history.
+   */
+  [noscript, notxpcom, nostdcall] void GetContents(out boolean aScrollPositionOnly,
+                                                   out Array<ACString> aKeys,
+                                                   out Array<PresState> aStates);
+
+  /**
+   * Remove all the states and clear the scroll position only flag.
+   */
+  [noscript, notxpcom, nostdcall] void Reset();
 };
 
 %{C++
 /* Defined in nsLayoutHistoryState.cpp */
 already_AddRefed<nsILayoutHistoryState>
 NS_NewLayoutHistoryState();
 
 namespace mozilla {
--- a/layout/base/nsLayoutHistoryState.cpp
+++ b/layout/base/nsLayoutHistoryState.cpp
@@ -130,16 +130,33 @@ void nsLayoutHistoryState::ResetScrollSt
   for (auto iter = mStates.Iter(); !iter.Done(); iter.Next()) {
     PresState* state = iter.Data().get();
     if (state) {
       state->scrollState() = nsPoint(0, 0);
     }
   }
 }
 
+void nsLayoutHistoryState::GetContents(bool* aScrollPositionOnly,
+                                       nsTArray<nsCString>& aKeys,
+                                       nsTArray<mozilla::PresState>& aStates) {
+  *aScrollPositionOnly = mScrollPositionOnly;
+  aKeys.SetCapacity(mStates.Count());
+  aStates.SetCapacity(mStates.Count());
+  for (auto iter = mStates.Iter(); !iter.Done(); iter.Next()) {
+    aKeys.AppendElement(iter.Key());
+    aStates.AppendElement(*(iter.Data().get()));
+  }
+}
+
+void nsLayoutHistoryState::Reset() {
+  mScrollPositionOnly = false;
+  mStates.Clear();
+}
+
 namespace mozilla {
 UniquePtr<PresState> NewPresState() {
   return MakeUnique<PresState>(
       /* contentData */ mozilla::void_t(),
       /* scrollState */ nsPoint(0, 0),
       /* allowScrollOriginDowngrade */ true,
       /* resolution */ 1.0,
       /* disabledSet */ false,