Bug 1438272 - Part 3: move session history to parent process. r=nika
authorPeter Van der Beken <peterv@propagandism.org>
Tue, 05 Mar 2019 15:46:10 +0100
changeset 500488 05e813c2d5ced2480d2827f652ad73142bef3b80
parent 500487 5481b4adc9a98cee979b16eba93f2dccf5ce3027
child 500489 273eba0c8b57b25c690a933c7c11be2aa5eb3019
push id114165
push userpvanderbeken@mozilla.com
push dateWed, 06 Nov 2019 09:18:40 +0000
treeherdermozilla-inbound@db1ddab2985d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnika
bugs1438272
milestone72.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 1438272 - Part 3: move session history to parent process. r=nika Differential Revision: https://phabricator.services.mozilla.com/D26904
docshell/base/nsDocShell.cpp
docshell/build/components.conf
docshell/build/nsDocShellModule.cpp
docshell/shistory/ChildSHistory.cpp
docshell/shistory/ChildSHistory.h
docshell/shistory/NewPSHEntry.ipdlh
docshell/shistory/PSHEntry.ipdl
docshell/shistory/PSHistory.ipdl
docshell/shistory/SHEntryChild.cpp
docshell/shistory/SHEntryChild.h
docshell/shistory/SHEntryParent.cpp
docshell/shistory/SHEntryParent.h
docshell/shistory/SHistoryChild.cpp
docshell/shistory/SHistoryChild.h
docshell/shistory/SHistoryParent.cpp
docshell/shistory/SHistoryParent.h
docshell/shistory/moz.build
docshell/shistory/nsIBFCacheEntry.idl
docshell/shistory/nsISHEntry.idl
docshell/shistory/nsISHistory.idl
docshell/shistory/nsSHEntry.cpp
docshell/shistory/nsSHEntry.h
docshell/shistory/nsSHEntryShared.cpp
docshell/shistory/nsSHEntryShared.h
docshell/shistory/nsSHistory.cpp
docshell/shistory/nsSHistory.h
dom/base/nsContentUtils.cpp
dom/base/nsContentUtils.h
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
ipc/glue/ProtocolUtils.h
ipc/ipdl/sync-messages.ini
modules/libpref/init/StaticPrefList.yaml
toolkit/modules/sessionstore/SessionHistory.jsm
xpcom/base/nsISupportsImpl.h
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -3588,17 +3588,17 @@ void nsDocShell::ClearFrameHistory(nsISH
   }
 
   int32_t count = aEntry->GetChildCount();
   AutoTArray<nsID, 16> ids;
   for (int32_t i = 0; i < count; ++i) {
     nsCOMPtr<nsISHEntry> child;
     aEntry->GetChildAt(i, getter_AddRefs(child));
     if (child) {
-      ids.AppendElement(child->DocshellID());
+      child->GetDocshellID(*ids.AppendElement());
     }
   }
   int32_t index = rootSH->Index();
   rootSH->LegacySHistory()->RemoveEntries(ids, index);
 }
 
 //-------------------------------------
 //-- Helper Method for Print discovery
@@ -8888,17 +8888,19 @@ nsresult nsDocShell::HandleSameDocumentN
 
   nsCOMPtr<nsIInputStream> postData;
   uint32_t cacheKey = 0;
 
   bool scrollRestorationIsManual = false;
   if (mOSHE) {
     /* save current position of scroller(s) (bug 59774) */
     mOSHE->SetScrollPosition(scrollPos.x, scrollPos.y);
-    scrollRestorationIsManual = mOSHE->GetScrollRestorationIsManual();
+    DebugOnly<nsresult> rv =
+        mOSHE->GetScrollRestorationIsManual(&scrollRestorationIsManual);
+    MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
     // Get the postdata and page ident from the current page, if
     // the new load is being done via normal means.  Note that
     // "normal means" can be checked for just by checking for
     // LOAD_CMD_NORMAL, given the loadType and allowScroll check
     // above -- it filters out some LOAD_CMD_NORMAL cases that we
     // wouldn't want here.
     if (aLoadState->LoadType() & LOAD_CMD_NORMAL) {
       postData = mOSHE->GetPostData();
@@ -8915,18 +8917,20 @@ nsresult nsDocShell::HandleSameDocumentN
         }
         mLSHE->AdoptBFCacheEntry(mOSHE);
       }
     }
   }
 
   // If we're doing a history load, use its scroll restoration state.
   if (aLoadState->SHEntry()) {
-    scrollRestorationIsManual =
-        aLoadState->SHEntry()->GetScrollRestorationIsManual();
+    DebugOnly<nsresult> rv =
+        aLoadState->SHEntry()->GetScrollRestorationIsManual(
+            &scrollRestorationIsManual);
+    MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
   }
 
   /* Assign mLSHE to mOSHE. This will either be a new entry created
    * by OnNewURI() for normal loads or aLoadState->SHEntry() for history
    * loads.
    */
   if (mLSHE) {
     SetHistoryEntry(&mOSHE, mLSHE);
@@ -9392,18 +9396,18 @@ nsresult nsDocShell::InternalLoad(nsDocS
   // aLoadState->SHEntry() should be assigned to mLSHE, only after Stop() has
   // been called. But when loading an error page, do not clear the
   // mLSHE for the real page.
   if (mLoadType != LOAD_ERROR_PAGE) {
     SetHistoryEntry(&mLSHE, aLoadState->SHEntry());
     if (aLoadState->SHEntry()) {
       // We're making history navigation or a reload. Make sure our history ID
       // points to the same ID as SHEntry's docshell ID.
-      mHistoryID = aLoadState->SHEntry()->DocshellID();
-      mBrowsingContext->SetHistoryID(aLoadState->SHEntry()->DocshellID());
+      aLoadState->SHEntry()->GetDocshellID(mHistoryID);
+      mBrowsingContext->SetHistoryID(mHistoryID);
     }
   }
 
   mSavingOldViewer = savePresentation;
 
   // If we have a saved content viewer in history, restore and show it now.
   if (aLoadState->SHEntry() && (mLoadType & LOAD_CMD_HISTORY)) {
     // https://html.spec.whatwg.org/#history-traversal:
@@ -11159,25 +11163,28 @@ nsresult nsDocShell::UpdateURLAndHistory
     if (shistory) {
       shistory->RemovePendingHistoryNavigations();
     }
 
     // Save the current scroll position (bug 590573).  Step 2.3.
     nsPoint scrollPos = GetCurScrollPos();
     mOSHE->SetScrollPosition(scrollPos.x, scrollPos.y);
 
-    bool scrollRestorationIsManual = mOSHE->GetScrollRestorationIsManual();
+    bool scrollRestorationIsManual;
+    nsresult rv =
+        mOSHE->GetScrollRestorationIsManual(&scrollRestorationIsManual);
+    MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+
     nsCOMPtr<nsIContentSecurityPolicy> csp = aDocument->GetCsp();
 
     // Since we're not changing which page we have loaded, pass
     // true for aCloneChildren.
-    nsresult rv = AddToSessionHistory(
-        aNewURI, nullptr,
-        aDocument->NodePrincipal(),  // triggeringPrincipal
-        nullptr, nullptr, csp, true, getter_AddRefs(newSHEntry));
+    rv = AddToSessionHistory(aNewURI, nullptr,
+                             aDocument->NodePrincipal(),  // triggeringPrincipal
+                             nullptr, nullptr, csp, true, getter_AddRefs(newSHEntry));
     NS_ENSURE_SUCCESS(rv, rv);
 
     NS_ENSURE_TRUE(newSHEntry, NS_ERROR_FAILURE);
 
     // Session history entries created by pushState inherit scroll restoration
     // mode from the current entry.
     newSHEntry->SetScrollRestorationIsManual(scrollRestorationIsManual);
 
@@ -11296,17 +11303,17 @@ nsresult nsDocShell::UpdateURLAndHistory
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::GetCurrentScrollRestorationIsManual(bool* aIsManual) {
   *aIsManual = false;
   if (mOSHE) {
-    *aIsManual = mOSHE->GetScrollRestorationIsManual();
+    return mOSHE->GetScrollRestorationIsManual(aIsManual);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::SetCurrentScrollRestorationIsManual(bool aIsManual) {
   if (mOSHE) {
@@ -11490,17 +11497,16 @@ nsresult nsDocShell::AddToSessionHistory
       storagePrincipalToInherit = principalToInherit;
     }
   }
 
   // Title is set in nsDocShell::SetTitle()
   entry->Create(aURI,                 // uri
                 EmptyString(),        // Title
                 inputStream,          // Post data stream
-                nullptr,              // LayoutHistory state
                 cacheKey,             // CacheKey
                 mContentTypeHint,     // Content-type
                 triggeringPrincipal,  // Channel or provided principal
                 principalToInherit, storagePrincipalToInherit, csp, HistoryID(),
                 mDynamicallyCreated);
 
   entry->SetOriginalURI(originalURI);
   entry->SetResultPrincipalURI(resultPrincipalURI);
@@ -11721,17 +11727,18 @@ nsresult nsDocShell::LoadHistoryEntry(ns
                     nullptr);  // No nsIRequest
   return rv;
 }
 
 nsresult nsDocShell::PersistLayoutHistoryState() {
   nsresult rv = NS_OK;
 
   if (mOSHE) {
-    bool scrollRestorationIsManual = mOSHE->GetScrollRestorationIsManual();
+    bool scrollRestorationIsManual;
+    Unused << mOSHE->GetScrollRestorationIsManual(&scrollRestorationIsManual);
     nsCOMPtr<nsILayoutHistoryState> layoutState;
     if (RefPtr<PresShell> presShell = GetPresShell()) {
       rv = presShell->CaptureHistoryState(getter_AddRefs(layoutState));
     } else if (scrollRestorationIsManual) {
       // Even if we don't have layout anymore, we may want to reset the
       // current scroll state in layout history.
       GetLayoutHistoryState(getter_AddRefs(layoutState));
     }
@@ -11759,17 +11766,17 @@ void nsDocShell::SetHistoryEntry(nsCOMPt
                                  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 = nsSHistory::GetRootSHEntry(aEntry);
+  nsCOMPtr<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 = nsSHistory::GetRootSHEntry(*aPtr);
     if (oldRootEntry) {
--- a/docshell/build/components.conf
+++ b/docshell/build/components.conf
@@ -42,18 +42,22 @@ Headers = ['/docshell/build/nsDocShellMo
 InitFunc = 'mozilla::InitDocShellModule'
 UnloadFunc = 'mozilla::UnloadDocShellModule'
 
 Classes = [
     {
         'name': 'SHEntry',
         'cid': '{bfd1a791-ad9f-11d3-bdc7-0050040a9b44}',
         'contract_ids': ['@mozilla.org/browser/session-history-entry;1'],
-        'type': 'nsSHEntry',
-        'headers': ['/docshell/shistory/nsSHEntry.h'],
+        'type': 'nsISHEntry',
+        'constructor': 'mozilla::dom::CreateSHEntry',
+        'headers': [
+            'nsISHEntry.h',
+            'mozilla/dom/ChildSHistory.h',
+        ],
     },
     {
         'name': 'DocLoader',
         'cid': '{057b04d0-0ccf-11d2-beba-00805f8a66dc}',
         'contract_ids': ['@mozilla.org/docloaderservice;1'],
         'type': 'nsDocLoader',
         'headers': ['nsDocLoader.h'],
         'init_method': 'Init',
--- a/docshell/build/nsDocShellModule.cpp
+++ b/docshell/build/nsDocShellModule.cpp
@@ -1,28 +1,30 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 "mozilla/dom/BrowsingContext.h"
 #include "mozilla/dom/BrowsingContextGroup.h"
+#include "mozilla/dom/SHEntryChild.h"
 
 // session history
 #include "nsSHEntryShared.h"
 #include "nsSHistory.h"
 
 namespace mozilla {
 
 // The one time initialization for this module
 nsresult InitDocShellModule() {
   mozilla::dom::BrowsingContext::Init();
   nsresult rv = nsSHistory::Startup();
   NS_ENSURE_SUCCESS(rv, rv);
+  mozilla::dom::SHEntryChildShared::Init();
 
   return NS_OK;
 }
 
 void UnloadDocShellModule() {
   nsSHistory::Shutdown();
   nsSHEntryShared::Shutdown();
 }
--- a/docshell/shistory/ChildSHistory.cpp
+++ b/docshell/shistory/ChildSHistory.cpp
@@ -1,30 +1,46 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 "mozilla/dom/ChildSHistory.h"
 #include "mozilla/dom/ChildSHistoryBinding.h"
+#include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/ContentFrameMessageManager.h"
+#include "mozilla/dom/SHEntryChild.h"
+#include "mozilla/dom/SHistoryChild.h"
+#include "mozilla/StaticPrefs_fission.h"
 #include "nsIMessageManager.h"
 #include "nsComponentManagerUtils.h"
+#include "nsSHEntry.h"
 #include "nsSHistory.h"
 #include "nsDocShell.h"
 #include "nsISHEntry.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())));
+  }
+
+  nsCOMPtr<nsISHistory> history =
+      new nsSHistory(aDocShell->GetBrowsingContext(), aDocShell->HistoryID());
+  return history.forget();
+}
+
 ChildSHistory::ChildSHistory(nsDocShell* aDocShell)
-    : mDocShell(aDocShell), mHistory(new nsSHistory(aDocShell->GetBrowsingContext(),
-                                                    aDocShell->HistoryID())) {}
+    : mDocShell(aDocShell), mHistory(CreateSHistory(aDocShell)) {}
 
 ChildSHistory::~ChildSHistory() {}
 
 int32_t ChildSHistory::Count() { return mHistory->GetCount(); }
 
 int32_t ChildSHistory::Index() {
   int32_t index;
   mHistory->GetIndex(&index);
@@ -104,10 +120,21 @@ nsISupports* ChildSHistory::GetParentObj
   RefPtr<ContentFrameMessageManager> mm;
   if (mDocShell) {
     mm = mDocShell->GetMessageManager();
   }
   // else we must be unlinked... can that happen here?
   return ToSupports(mm);
 }
 
+already_AddRefed<nsISHEntry> CreateSHEntry() {
+  uint64_t sharedID = SHEntryChildShared::CreateSharedID();
+  if (XRE_IsContentProcess() && StaticPrefs::fission_sessionHistoryInParent()) {
+    return do_AddRef(static_cast<SHEntryChild*>(
+        ContentChild::GetSingleton()->SendPSHEntryConstructor(sharedID)));
+  }
+
+  nsCOMPtr<nsISHEntry> entry = new nsLegacySHEntry(sharedID);
+  return entry.forget();
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/docshell/shistory/ChildSHistory.h
+++ b/docshell/shistory/ChildSHistory.h
@@ -23,16 +23,17 @@
 #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 ParentSHistory;
@@ -98,16 +99,18 @@ class ChildSHistory : public nsISupports
     }
 
    private:
     RefPtr<ChildSHistory> mHistory;
     int32_t mOffset;
   };
 
   RefPtr<nsDocShell> mDocShell;
-  RefPtr<nsSHistory> mHistory;
+  nsCOMPtr<nsISHistory> mHistory;
   mozilla::LinkedList<PendingAsyncHistoryNavigation> mPendingNavigations;
 };
 
+already_AddRefed<nsISHEntry> CreateSHEntry();
+
 }  // namespace dom
 }  // namespace mozilla
 
 #endif /* mozilla_dom_ChildSHistory_h */
new file mode 100644
--- /dev/null
+++ b/docshell/shistory/NewPSHEntry.ipdlh
@@ -0,0 +1,27 @@
+/* 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 PSHEntry;
+
+namespace mozilla {
+namespace dom {
+
+union PSHEntryOrSharedID
+{
+  PSHEntry;
+  uint64_t;
+};
+
+struct NewPSHEntry {
+  ManagedEndpoint<PSHEntryChild> endpoint;
+  uint64_t sharedID;
+};
+
+union MaybeNewPSHEntry {
+  nullable PSHEntry;
+  NewPSHEntry;
+};
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/docshell/shistory/PSHEntry.ipdl
@@ -0,0 +1,107 @@
+/* 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 NewPSHEntry;
+
+using refcounted class nsIInputStream from "mozilla/ipc/IPCStreamUtils.h";
+
+using struct nsID from "nsID.h";
+using nsIntRect from "nsRect.h";
+
+namespace mozilla {
+namespace dom {
+
+sync protocol PSHEntry {
+  manager PContent;
+
+parent:
+  sync GetURI() returns (nsIURI uri);
+  sync SetURI(nsIURI uri);
+  sync GetOriginalURI() returns (nsIURI originalUri);
+  sync SetOriginalURI(nsIURI originalUri);
+  sync GetResultPrincipalURI() returns (nsIURI resultPrincipalUri);
+  sync SetResultPrincipalURI(nsIURI resultPrincipalUri);
+  sync GetLoadReplace() returns (bool loadReplace);
+  sync SetLoadReplace(bool loadReplace);
+  sync GetTitle() returns (nsString title);
+  sync SetTitle(nsString title);
+  sync GetIsSubFrame() returns (bool isSubFrame);
+  sync SetIsSubFrame(bool isSubFrame);
+  sync GetReferrerInfo() returns (nsIReferrerInfo referrerInfo);
+  sync SetReferrerInfo(nsIReferrerInfo referrerInfo);
+  sync GetSticky() returns (bool sticky);
+  sync SetSticky(bool sticky);
+  sync GetPostData() returns (nsIInputStream postData);
+  sync SetPostData(nsIInputStream postData);
+  sync GetParent() returns (MaybeNewPSHEntry parentEntry);
+  sync SetParent(nullable PSHEntry parentEntry);
+  sync GetLoadType() returns (uint32_t loadType);
+  sync SetLoadType(uint32_t loadType);
+  sync GetID() returns (uint32_t id);
+  sync SetID(uint32_t id);
+  sync GetCacheKey() returns (uint32_t cacheKey);
+  sync SetCacheKey(uint32_t cacheKey);
+  sync GetExpirationStatus() returns (bool expirationStatus);
+  sync SetExpirationStatus(bool expirationStatus);
+  sync GetContentType() returns (nsCString contentType);
+  sync SetContentType(nsCString contentType);
+  sync GetURIWasModified() returns (bool uriWasModified);
+  sync SetURIWasModified(bool uriWasModified);
+  sync GetTriggeringPrincipal() returns (nsIPrincipal triggeringPrincipal);
+  sync SetTriggeringPrincipal(nsIPrincipal triggeringPrincipal);
+  sync GetPrincipalToInherit() returns (nsIPrincipal principalToInherit);
+  sync SetPrincipalToInherit(nsIPrincipal principalToInherit);
+  sync GetStoragePrincipalToInherit()
+    returns (nsIPrincipal storagePrincipalToInherit);
+  sync SetStoragePrincipalToInherit(nsIPrincipal storagePrincipalToInherit);
+  sync GetCsp() returns (nsIContentSecurityPolicy csp);
+  sync SetCsp(nsIContentSecurityPolicy csp);
+  sync GetStateData() returns (ClonedMessageData stateData);
+  sync SetStateData(ClonedMessageData stateData);
+  sync GetDocshellID() returns (nsID docshellId);
+  sync SetDocshellID(nsID docshellId);
+  sync GetIsSrcdocEntry() returns (bool isSrcdocEntry);
+  sync GetSrcdocData() returns (nsString srcdocData);
+  sync SetSrcdocData(nsString srcdocData);
+  sync GetBaseURI() returns (nsIURI baseUri);
+  sync SetBaseURI(nsIURI baseUri); 
+  sync GetScrollRestorationIsManual() returns (bool scrollRestorationIsManual);
+  sync SetScrollRestorationIsManual(bool scrollRestorationIsManual);
+  sync GetLoadedInThisProcess() returns (bool loadedInThisProcess);
+  sync GetLastTouched() returns (uint32_t lastTouched);
+  sync SetLastTouched(uint32_t lastTouched);
+  sync GetChildCount() returns (int32_t childCount);
+  sync GetPersist() returns (bool persist);
+  sync SetPersist(bool persist);
+  sync SetScrollPosition(int32_t x, int32_t y);
+  sync GetScrollPosition() returns (int32_t x, int32_t y);
+  sync GetViewerBounds() returns (nsIntRect bounds);
+  sync SetViewerBounds(nsIntRect bounds);
+  sync Create(nsIURI URI, nsString title, nsIInputStream inputStream,
+              uint32_t cacheKey, nsCString contentType,
+              nsIPrincipal triggeringPrincipal, nsIPrincipal principalToInherit,
+              nsIContentSecurityPolicy csp, nsID docshellID,
+              bool dynamicCreation);
+  sync HasDetachedEditor() returns (bool hasDetachedEditor);
+  sync IsDynamicallyAdded() returns (bool isDynamicallyAdded);
+  sync HasDynamicallyAddedChild() returns (bool hasDynamicallyAddedChild);
+  sync AdoptBFCacheEntry(PSHEntry entry) returns (nsresult result);
+  sync AbandonBFCacheEntry(uint64_t aNewSharedID);
+  sync SharesDocumentWith(PSHEntry entry) returns (bool sharesDocumentWith,
+                                                   nsresult result);
+  sync SetLoadTypeAsHistory();
+  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 (MaybeNewPSHEntry childEntry);
+  sync ReplaceChild(PSHEntry newChildEntry) returns (nsresult result);
+
+  sync __delete__();
+};
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/docshell/shistory/PSHistory.ipdl
@@ -0,0 +1,63 @@
+/* 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 PSHEntry;
+
+include DOMTypes;
+include NewPSHEntry;
+
+using refcounted class mozilla::dom::BrowsingContext from "mozilla/dom/BrowsingContext.h";
+using refcounted class nsDocShellLoadState from "mozilla/dom/DocShellMessageUtils.h";
+
+using struct nsID from "nsID.h";
+using struct mozilla::null_t from "ipc/IPCMessageUtils.h";
+
+namespace mozilla {
+namespace dom {
+
+struct LoadSHEntryData
+{
+  MaybeNewPSHEntry shEntry;
+  BrowsingContext browsingContext;
+  nsDocShellLoadState loadState;
+};
+
+union LoadSHEntryResult {
+  nsresult;
+  LoadSHEntryData;
+};
+
+sync protocol PSHistory {
+  manager PContent;
+
+parent:
+  sync GetCount() returns (int32_t count);
+  sync GetIndex() returns (int32_t index);
+  sync SetIndex(int32_t index) returns (nsresult result);
+  sync GetRequestedIndex() returns (int32_t index);
+  sync InternalSetRequestedIndex(int32_t index);
+  sync GetEntryAtIndex(int32_t index) returns (nsresult result, MaybeNewPSHEntry entry);
+  sync PurgeHistory(int32_t numEntries) returns (nsresult result);
+  sync ReloadCurrentEntry() returns (LoadSHEntryResult load);
+  sync GotoIndex(int32_t index) returns (LoadSHEntryResult load);
+  sync GetIndexOfEntry(PSHEntry entry) returns (int32_t index);
+  sync AddEntry(PSHEntry entry, bool persist) returns (nsresult result, int32_t entriesPurged);
+  sync UpdateIndex();
+  sync ReplaceEntry(int32_t index, PSHEntry entry) returns (nsresult result);
+  sync NotifyOnHistoryReload() returns (bool ok);
+  sync EvictOutOfRangeContentViewers(int32_t index);
+  sync EvictAllContentViewers();
+  sync RemoveDynEntries(int32_t index, PSHEntry entry);
+  sync RemoveEntries(nsID[] ids, int32_t index) returns (bool didRemove);
+  sync Reload(uint32_t reloadFlags) returns (LoadSHEntryResult load);
+  sync GetAllEntries() returns (MaybeNewPSHEntry[] entries);
+  sync FindEntryForBFCache(uint64_t sharedID, bool includeCurrentEntry) returns (MaybeNewPSHEntry entries, int32_t startIndex);
+  sync Evict(PSHEntry[] entry);
+
+  async __delete__();
+};
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/docshell/shistory/SHEntryChild.cpp
@@ -0,0 +1,967 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "SHEntryChild.h"
+#include "SHistoryChild.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "nsDocShellEditorData.h"
+#include "nsDocShellLoadState.h"
+#include "nsIContentViewer.h"
+#include "nsILayoutHistoryState.h"
+#include "nsIMutableArray.h"
+#include "nsStructuredCloneContainer.h"
+
+namespace mozilla {
+namespace dom {
+
+StaticAutoPtr<nsRefPtrHashtable<nsUint64HashKey, SHEntryChildShared>>
+    sSHEntryChildSharedTable;
+
+uint64_t SHEntryChildShared::sNextSharedID = 0;
+
+/* static */
+void SHEntryChildShared::Init() {
+  sSHEntryChildSharedTable =
+      new nsRefPtrHashtable<nsUint64HashKey, SHEntryChildShared>();
+  ClearOnShutdown(&sSHEntryChildSharedTable);
+}
+
+/* static */
+SHEntryChildShared* SHEntryChildShared::GetOrCreate(uint64_t aSharedID) {
+  MOZ_DIAGNOSTIC_ASSERT(
+      sSHEntryChildSharedTable,
+      "SHEntryChildShared::Init should have created the table.");
+  RefPtr<SHEntryChildShared>& shared =
+      sSHEntryChildSharedTable->GetOrInsert(aSharedID);
+  if (!shared) {
+    shared = new SHEntryChildShared(aSharedID);
+  }
+  return shared;
+}
+
+void SHEntryChildShared::Remove(uint64_t aSharedID) {
+  if (sSHEntryChildSharedTable) {
+    sSHEntryChildSharedTable->Remove(aSharedID);
+  }
+}
+
+SHEntryChildShared::SHEntryChildShared(uint64_t aID) : mID(aID) {}
+
+SHEntryChildShared::~SHEntryChildShared() {
+  // The destruction can be caused by either the entry is removed from session
+  // history and no one holds the reference, or the whole session history is on
+  // destruction. We want to ensure that we invoke
+  // shistory->RemoveFromExpirationTracker for the former case.
+  RemoveFromExpirationTracker();
+
+  // Calling RemoveDynEntriesForBFCacheEntry on destruction is unnecessary since
+  // there couldn't be any SHEntry holding this shared entry, and we noticed
+  // that calling RemoveDynEntriesForBFCacheEntry in the middle of
+  // nsSHistory::Release can cause a crash, so set mSHistory to null explicitly
+  // before RemoveFromBFCacheSync.
+  mSHistory = nullptr;
+  if (mContentViewer) {
+    RemoveFromBFCacheSync();
+  }
+}
+
+NS_IMPL_ISUPPORTS(SHEntryChildShared, nsIBFCacheEntry, nsIMutationObserver)
+
+already_AddRefed<SHEntryChildShared> SHEntryChildShared::Duplicate() {
+  RefPtr<SHEntryChildShared> newEntry = new SHEntryChildShared(
+      mozilla::dom::SHEntryChildShared::CreateSharedID());
+  newEntry->CopyFrom(this);
+  return newEntry.forget();
+}
+
+void SHEntryChildShared::RemoveFromExpirationTracker() {
+  if (mSHistory && GetExpirationState()->IsTracked()) {
+    mSHistory->RemoveFromExpirationTracker(this);
+  }
+}
+
+void SHEntryChildShared::SyncPresentationState() {
+  if (mContentViewer && mWindowState) {
+    // If we have a content viewer and a window state, we should be ok.
+    return;
+  }
+
+  DropPresentationState();
+}
+
+void SHEntryChildShared::DropPresentationState() {
+  RefPtr<SHEntryChildShared> kungFuDeathGrip = this;
+
+  if (mDocument) {
+    mDocument->SetBFCacheEntry(nullptr);
+    mDocument->RemoveMutationObserver(this);
+    mDocument = nullptr;
+  }
+  if (mContentViewer) {
+    mContentViewer->ClearHistoryEntry();
+  }
+
+  RemoveFromExpirationTracker();
+  mContentViewer = nullptr;
+  // FIXME Bug 1547735
+  // mSticky = true;
+  mWindowState = nullptr;
+  // FIXME Bug 1547735
+  // mViewerBounds.SetRect(0, 0, 0, 0);
+  mChildShells.Clear();
+  mRefreshURIList = nullptr;
+  mEditorData = nullptr;
+}
+
+nsresult SHEntryChildShared::SetContentViewer(nsIContentViewer* aViewer) {
+  MOZ_ASSERT(!aViewer || !mContentViewer,
+             "SHEntryShared already contains viewer");
+
+  if (mContentViewer || !aViewer) {
+    DropPresentationState();
+  }
+
+  // If we're setting mContentViewer to null, state should already be cleared
+  // in the DropPresentationState() call above; If we're setting it to a
+  // non-null content viewer, the entry shouldn't have been tracked either.
+  MOZ_ASSERT(!GetExpirationState()->IsTracked());
+  mContentViewer = aViewer;
+
+  if (mContentViewer) {
+    // mSHistory is only set for root entries, but in general bfcache only
+    // applies to root entries as well. BFCache for subframe navigation has been
+    // disabled since 2005 in bug 304860.
+    if (mSHistory) {
+      mSHistory->AddToExpirationTracker(this);
+    }
+
+    // Store observed document in strong pointer in case it is removed from
+    // the contentviewer
+    mDocument = mContentViewer->GetDocument();
+    if (mDocument) {
+      mDocument->SetBFCacheEntry(this);
+      mDocument->AddMutationObserver(this);
+    }
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP SHEntryChildShared::RemoveFromBFCacheSync() {
+  MOZ_ASSERT(mContentViewer && mDocument, "we're not in the bfcache!");
+
+  // The call to DropPresentationState could drop the last reference, so hold
+  // |this| until RemoveDynEntriesForBFCacheEntry finishes.
+  RefPtr<SHEntryChildShared> kungFuDeathGrip = this;
+
+  // DropPresentationState would clear mContentViewer.
+  nsCOMPtr<nsIContentViewer> viewer = mContentViewer;
+  DropPresentationState();
+
+  if (viewer) {
+    viewer->Destroy();
+  }
+
+  // Now that we've dropped the viewer, we have to clear associated dynamic
+  // subframe entries.
+  if (mSHistory) {
+    mSHistory->RemoveDynEntriesForBFCacheEntry(this);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP SHEntryChildShared::RemoveFromBFCacheAsync() {
+  MOZ_ASSERT(mContentViewer && mDocument, "we're not in the bfcache!");
+
+  // Check it again to play safe in release builds.
+  if (!mDocument) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  // DropPresentationState would clear mContentViewer & mDocument. Capture and
+  // release the references asynchronously so that the document doesn't get
+  // nuked mid-mutation.
+  nsCOMPtr<nsIContentViewer> viewer = mContentViewer;
+  RefPtr<dom::Document> _document = mDocument;
+  RefPtr<SHEntryChildShared> self = this;
+  nsresult rv = mDocument->Dispatch(
+      mozilla::TaskCategory::Other,
+      NS_NewRunnableFunction(
+          "SHEntryChildShared::RemoveFromBFCacheAsync",
+          // _document isn't used in the closure, but just keeps mDocument
+          // alive until the closure runs.
+          [self, viewer, _document]() {
+            if (viewer) {
+              viewer->Destroy();
+            }
+
+            if (self->mSHistory) {
+              self->mSHistory->RemoveDynEntriesForBFCacheEntry(self);
+            }
+          }));
+
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Failed to dispatch RemoveFromBFCacheAsync runnable.");
+  } else {
+    // Drop presentation. Only do this if we succeeded in posting the event
+    // since otherwise the document could be torn down mid-mutation, causing
+    // crashes.
+    DropPresentationState();
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP SHEntryChildShared::GetID(uint64_t* aID) {
+  *aID = mID;
+  return NS_OK;
+}
+
+void SHEntryChildShared::CharacterDataChanged(nsIContent* aContent,
+                                              const CharacterDataChangeInfo&) {
+  RemoveFromBFCacheAsync();
+}
+
+void SHEntryChildShared::AttributeChanged(dom::Element* aElement,
+                                          int32_t aNameSpaceID,
+                                          nsAtom* aAttribute, int32_t aModType,
+                                          const nsAttrValue* aOldValue) {
+  RemoveFromBFCacheAsync();
+}
+
+void SHEntryChildShared::ContentAppended(nsIContent* aFirstNewContent) {
+  RemoveFromBFCacheAsync();
+}
+
+void SHEntryChildShared::ContentInserted(nsIContent* aChild) {
+  RemoveFromBFCacheAsync();
+}
+
+void SHEntryChildShared::ContentRemoved(nsIContent* aChild,
+                                        nsIContent* aPreviousSibling) {
+  RemoveFromBFCacheAsync();
+}
+
+NS_IMPL_ADDREF(SHEntryChild)
+NS_IMETHODIMP_(MozExternalRefCountType) SHEntryChild::Release() {
+  MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
+  NS_ASSERT_OWNINGTHREAD(SHEntryChild);
+  nsrefcnt count = --mRefCnt;
+  NS_LOG_RELEASE(this, count, "SHEntryChild");
+  if (count == 0) {
+    mRefCnt = 1; /* stabilize */
+    delete this;
+    return 0;
+  }
+  if (count == 1 && !mIPCActorDeleted) {
+    Send__delete__(this);
+  }
+  return count;
+}
+NS_IMPL_QUERY_INTERFACE(SHEntryChild, nsISHEntry)
+
+NS_IMETHODIMP
+SHEntryChild::SetScrollPosition(int32_t aX, int32_t aY) {
+  return SendSetScrollPosition(aX, aY) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetScrollPosition(int32_t* aX, int32_t* aY) {
+  return SendGetScrollPosition(aX, aY) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetURIWasModified(bool* aURIWasModified) {
+  return SendGetURIWasModified(aURIWasModified) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetURIWasModified(bool aURIWasModified) {
+  return SendSetURIWasModified(aURIWasModified) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetURI(nsIURI** aURI) {
+  RefPtr<nsIURI> uri;
+  if (!SendGetURI(&uri)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  uri.forget(aURI);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetURI(nsIURI* aURI) {
+  return SendSetURI(aURI) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetOriginalURI(nsIURI** aOriginalURI) {
+  RefPtr<nsIURI> originalURI;
+  if (!SendGetOriginalURI(&originalURI)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  originalURI.forget(aOriginalURI);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetOriginalURI(nsIURI* aOriginalURI) {
+  return SendSetOriginalURI(aOriginalURI) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetResultPrincipalURI(nsIURI** aResultPrincipalURI) {
+  RefPtr<nsIURI> resultPrincipalURI;
+  if (!SendGetResultPrincipalURI(&resultPrincipalURI)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  resultPrincipalURI.forget(aResultPrincipalURI);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetResultPrincipalURI(nsIURI* aResultPrincipalURI) {
+  return SendSetResultPrincipalURI(aResultPrincipalURI) ? NS_OK
+                                                        : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetLoadReplace(bool* aLoadReplace) {
+  return SendGetLoadReplace(aLoadReplace) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetLoadReplace(bool aLoadReplace) {
+  return SendSetLoadReplace(aLoadReplace) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetReferrerInfo(nsIReferrerInfo** aReferrerInfo) {
+  RefPtr<nsIReferrerInfo> referrerInfo;
+  if (SendGetReferrerInfo(&referrerInfo)) {
+    referrerInfo.forget(aReferrerInfo);
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
+  return SendSetReferrerInfo(aReferrerInfo) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetContentViewer(nsIContentViewer* aViewer) {
+  return mShared->SetContentViewer(aViewer);
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetContentViewer(nsIContentViewer** aResult) {
+  NS_IF_ADDREF(*aResult = mShared->mContentViewer);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetSticky(bool aSticky) {
+  return SendSetSticky(aSticky) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetSticky(bool* aSticky) {
+  return SendGetSticky(aSticky) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetTitle(nsAString& aTitle) {
+  nsString title;
+  if (!SendGetTitle(&title)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  aTitle = std::move(title);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetTitle(const nsAString& aTitle) {
+  return SendSetTitle(nsString(aTitle)) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetPostData(nsIInputStream** aPostData) {
+  RefPtr<nsIInputStream> postData;
+  if (!SendGetPostData(&postData)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  postData.forget(aPostData);
+
+  return NS_OK;
+}
+
+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::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;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetID(uint32_t* aResult) {
+  return SendGetID(aResult) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetID(uint32_t aID) {
+  return SendSetID(aID) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetIsSubFrame(bool* aFlag) {
+  return SendGetIsSubFrame(aFlag) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetIsSubFrame(bool aFlag) {
+  return SendSetIsSubFrame(aFlag) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetCacheKey(uint32_t* aResult) {
+  return SendGetCacheKey(aResult) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetCacheKey(uint32_t aCacheKey) {
+  return SendSetCacheKey(aCacheKey) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetSaveLayoutStateFlag(bool* aFlag) {
+  *aFlag = mShared->mSaveLayoutState;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetSaveLayoutStateFlag(bool aFlag) {
+  mShared->mSaveLayoutState = aFlag;
+  if (mShared->mLayoutHistoryState) {
+    mShared->mLayoutHistoryState->SetScrollPositionOnly(!aFlag);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetExpirationStatus(bool* aFlag) {
+  return SendGetExpirationStatus(aFlag) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetExpirationStatus(bool aFlag) {
+  return SendSetExpirationStatus(aFlag) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetContentType(nsACString& aContentType) {
+  nsCString contentType;
+  if (!SendGetContentType(&contentType)) {
+    return NS_ERROR_FAILURE;
+  }
+  aContentType = contentType;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetContentType(const nsACString& aContentType) {
+  return SendSetContentType(nsCString(aContentType)) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::Create(nsIURI* aURI, const nsAString& aTitle,
+                     nsIInputStream* aInputStream, uint32_t aCacheKey,
+                     const nsACString& aContentType,
+                     nsIPrincipal* aTriggeringPrincipal,
+                     nsIPrincipal* aPrincipalToInherit,
+                     nsIContentSecurityPolicy* aCsp, const nsID& aDocShellID,
+                     bool aDynamicCreation) {
+  mShared->mLayoutHistoryState = nullptr;
+
+  // By default we save LayoutHistoryState
+  mShared->mSaveLayoutState = true;
+
+  return SendCreate(aURI, nsString(aTitle), aInputStream, aCacheKey,
+                    nsCString(aContentType), aTriggeringPrincipal,
+                    aPrincipalToInherit, aCsp, aDocShellID, aDynamicCreation)
+             ? NS_OK
+             : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::Clone(nsISHEntry** aResult) {
+  NS_IF_ADDREF(*aResult = static_cast<SHEntryChild*>(
+                   ContentChild::GetSingleton()->SendPSHEntryConstructor(
+                       this)));
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetParent(nsISHEntry** aResult) {
+  MaybeNewPSHEntry parent;
+  if (!SendGetParent(&parent)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  *aResult = SHEntryChild::GetOrCreate(parent).take();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetParent(nsISHEntry* aParent) {
+  return SendSetParent(static_cast<SHEntryChild*>(aParent)) ? NS_OK
+                                                            : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetWindowState(nsISupports* aState) {
+  mShared->mWindowState = aState;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetWindowState(nsISupports** aState) {
+  NS_IF_ADDREF(*aState = mShared->mWindowState);
+  return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+SHEntryChild::SetViewerBounds(const nsIntRect& aBounds) {
+  Unused << SendSetViewerBounds(aBounds);
+}
+
+NS_IMETHODIMP_(void)
+SHEntryChild::GetViewerBounds(nsIntRect& aBounds) {
+  Unused << SendGetViewerBounds(&aBounds);
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetTriggeringPrincipal(nsIPrincipal** aTriggeringPrincipal) {
+  RefPtr<nsIPrincipal> triggeringPrincipal;
+  if (!SendGetTriggeringPrincipal(&triggeringPrincipal)) {
+    return NS_ERROR_FAILURE;
+  }
+  triggeringPrincipal.forget(aTriggeringPrincipal);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetTriggeringPrincipal(nsIPrincipal* aTriggeringPrincipal) {
+  return SendSetTriggeringPrincipal(aTriggeringPrincipal) ? NS_OK
+                                                          : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetPrincipalToInherit(nsIPrincipal** aPrincipalToInherit) {
+  RefPtr<nsIPrincipal> principalToInherit;
+  if (!SendGetPrincipalToInherit(&principalToInherit)) {
+    return NS_ERROR_FAILURE;
+  }
+  principalToInherit.forget(aPrincipalToInherit);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit) {
+  return SendSetPrincipalToInherit(aPrincipalToInherit) ? NS_OK
+                                                        : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetStoragePrincipalToInherit(
+    nsIPrincipal** aStoragePrincipalToInherit) {
+  RefPtr<nsIPrincipal> storagePrincipalToInherit;
+  if (!SendGetStoragePrincipalToInherit(&storagePrincipalToInherit)) {
+    return NS_ERROR_FAILURE;
+  }
+  storagePrincipalToInherit.forget(aStoragePrincipalToInherit);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetStoragePrincipalToInherit(
+    nsIPrincipal* aStoragePrincipalToInherit) {
+  return SendSetStoragePrincipalToInherit(aStoragePrincipalToInherit)
+             ? NS_OK
+             : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetCsp(nsIContentSecurityPolicy** aCsp) {
+  RefPtr<nsIContentSecurityPolicy> csp;
+  if (!SendGetCsp(&csp)) {
+    return NS_ERROR_FAILURE;
+  }
+  csp.forget(aCsp);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetCsp(nsIContentSecurityPolicy* aCsp) {
+  return SendSetCsp(aCsp) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetBFCacheEntry(nsIBFCacheEntry** aEntry) {
+  NS_IF_ADDREF(*aEntry = mShared);
+  return NS_OK;
+}
+
+bool SHEntryChild::HasBFCacheEntry(nsIBFCacheEntry* aEntry) {
+  return mShared == aEntry;
+}
+
+NS_IMETHODIMP
+SHEntryChild::AdoptBFCacheEntry(nsISHEntry* aEntry) {
+  RefPtr<SHEntryChild> entry = static_cast<SHEntryChild*>(aEntry);
+  nsresult rv;
+  if (!SendAdoptBFCacheEntry(entry, &rv)) {
+    return NS_ERROR_FAILURE;
+  }
+  NS_ENSURE_SUCCESS(rv, rv);
+  mShared = entry->mShared;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SharesDocumentWith(nsISHEntry* aEntry, bool* aOut) {
+  nsresult rv;
+  // FIXME Maybe check local shared state instead?
+  return SendSharesDocumentWith(static_cast<SHEntryChild*>(aEntry), aOut, &rv)
+             ? rv
+             : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::AbandonBFCacheEntry() {
+  RefPtr<SHEntryChildShared> shared = mShared->Duplicate();
+  if (!SendAbandonBFCacheEntry(shared->GetID())) {
+    return NS_ERROR_FAILURE;
+  }
+
+  shared.swap(mShared);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetIsSrcdocEntry(bool* aIsSrcdocEntry) {
+  return SendGetIsSrcdocEntry(aIsSrcdocEntry) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetSrcdocData(nsAString& aSrcdocData) {
+  nsString srcdocData;
+  if (!SendGetSrcdocData(&srcdocData)) {
+    return NS_ERROR_FAILURE;
+  }
+  aSrcdocData = srcdocData;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetSrcdocData(const nsAString& aSrcdocData) {
+  return SendSetSrcdocData(nsString(aSrcdocData)) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetBaseURI(nsIURI** aBaseURI) {
+  RefPtr<nsIURI> baseURI;
+  if (!SendGetBaseURI(&baseURI)) {
+    return NS_ERROR_FAILURE;
+  }
+  baseURI.forget(aBaseURI);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetBaseURI(nsIURI* aBaseURI) {
+  return SendSetBaseURI(aBaseURI) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetScrollRestorationIsManual(bool* aIsManual) {
+  return SendGetScrollRestorationIsManual(aIsManual) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetScrollRestorationIsManual(bool aIsManual) {
+  return SendSetScrollRestorationIsManual(aIsManual) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetLoadedInThisProcess(bool* aLoadedInThisProcess) {
+  return SendGetLoadedInThisProcess(aLoadedInThisProcess) ? NS_OK
+                                                          : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetChildCount(int32_t* aCount) {
+  return SendGetChildCount(aCount) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::AddChild(nsISHEntry* aChild, int32_t aOffset,
+                       bool aUseRemoteSubframes) {
+  nsresult rv;
+  return SendAddChild(static_cast<SHEntryChild*>(aChild), aOffset,
+                      aUseRemoteSubframes, &rv)
+             ? rv
+             : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::RemoveChild(nsISHEntry* aChild) {
+  nsresult rv;
+  return aChild && SendRemoveChild(static_cast<SHEntryChild*>(aChild), &rv)
+             ? rv
+             : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetChildAt(int32_t aIndex, nsISHEntry** aResult) {
+  MaybeNewPSHEntry child;
+  if (!SendGetChildAt(aIndex, &child)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  *aResult = SHEntryChild::GetOrCreate(child).take();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHEntryChild::ReplaceChild(nsISHEntry* aNewEntry) {
+  nsresult rv;
+  return SendReplaceChild(static_cast<SHEntryChild*>(aNewEntry), &rv)
+             ? rv
+             : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP_(void)
+SHEntryChild::AddChildShell(nsIDocShellTreeItem* aShell) {
+  mShared->mChildShells.AppendObject(aShell);
+}
+
+NS_IMETHODIMP
+SHEntryChild::ChildShellAt(int32_t aIndex, nsIDocShellTreeItem** aShell) {
+  NS_IF_ADDREF(*aShell = mShared->mChildShells.SafeObjectAt(aIndex));
+  return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+SHEntryChild::ClearChildShells() { mShared->mChildShells.Clear(); }
+
+NS_IMETHODIMP
+SHEntryChild::GetRefreshURIList(nsIMutableArray** aList) {
+  // FIXME Move to parent.
+  NS_IF_ADDREF(*aList = mShared->mRefreshURIList);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetRefreshURIList(nsIMutableArray* aList) {
+  // FIXME Move to parent.
+  mShared->mRefreshURIList = aList;
+  return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+SHEntryChild::SyncPresentationState() { mShared->SyncPresentationState(); }
+
+nsDocShellEditorData* SHEntryChild::ForgetEditorData() {
+  return mShared->mEditorData.forget();
+}
+
+void SHEntryChild::SetEditorData(nsDocShellEditorData* aData) {
+  NS_ASSERTION(!(aData && mShared->mEditorData),
+               "We're going to overwrite an owning ref!");
+  if (mShared->mEditorData != aData) {
+    mShared->mEditorData = aData;
+  }
+}
+
+bool SHEntryChild::HasDetachedEditor() {
+  return mShared->mEditorData != nullptr;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetStateData(nsIStructuredCloneContainer** aContainer) {
+  ClonedMessageData data;
+  if (!SendGetStateData(&data)) {
+    return NS_ERROR_FAILURE;
+  }
+  // FIXME Should we signal null separately from the ClonedMessageData?
+  if (data.data().data.Size() == 0) {
+    *aContainer = nullptr;
+  } else {
+    RefPtr<nsStructuredCloneContainer> container =
+        new nsStructuredCloneContainer();
+    container->StealFromClonedMessageDataForParent(data);
+    container.forget(aContainer);
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetStateData(nsIStructuredCloneContainer* aContainer) {
+  // FIXME nsIStructuredCloneContainer is not builtin_class
+  ClonedMessageData data;
+  if (aContainer) {
+    static_cast<nsStructuredCloneContainer*>(aContainer)
+        ->BuildClonedMessageDataForChild(ContentChild::GetSingleton(), data);
+  }
+  return SendSetStateData(data) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP_(bool)
+SHEntryChild::IsDynamicallyAdded() {
+  bool isDynamicallyAdded;
+  return SendIsDynamicallyAdded(&isDynamicallyAdded) && isDynamicallyAdded;
+}
+
+NS_IMETHODIMP
+SHEntryChild::HasDynamicallyAddedChild(bool* aAdded) {
+  return SendHasDynamicallyAddedChild(aAdded) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetDocshellID(nsID& aID) {
+  if (!SendGetDocshellID(&aID)) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetDocshellID(const nsID& aID) {
+  return SendSetDocshellID(aID) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetLastTouched(uint32_t* aLastTouched) {
+  return SendGetLastTouched(aLastTouched) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetLastTouched(uint32_t aLastTouched) {
+  return SendSetLastTouched(aLastTouched) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetSHistory(nsISHistory** aSHistory) {
+  *aSHistory = do_AddRef(mShared->mSHistory).take();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetSHistory(nsISHistory* aSHistory) {
+  // mSHistory can not be changed once it's set
+  MOZ_ASSERT(!mShared->mSHistory || (mShared->mSHistory == aSHistory));
+  mShared->mSHistory = static_cast<SHistoryChild*>(aSHistory);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetLoadTypeAsHistory() {
+  return SendSetLoadTypeAsHistory() ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::GetPersist(bool* aPersist) {
+  return SendGetPersist(aPersist) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHEntryChild::SetPersist(bool aPersist) {
+  return SendSetPersist(aPersist) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+void SHEntryChild::EvictContentViewer() {
+  nsCOMPtr<nsIContentViewer> viewer = GetContentViewer();
+  if (viewer) {
+    // Drop the presentation state before destroying the viewer, so that
+    // document teardown is able to correctly persist the state.
+    SetContentViewer(nullptr);
+    SyncPresentationState();
+    viewer->Destroy();
+  }
+}
+
+/* static */
+already_AddRefed<SHEntryChild> SHEntryChild::GetOrCreate(
+    MaybeNewPSHEntry& aEntry) {
+  RefPtr<SHEntryChild> entry;
+  if (aEntry.type() == MaybeNewPSHEntry::TNewPSHEntry) {
+    NewPSHEntry& newEntry = aEntry.get_NewPSHEntry();
+    entry = new SHEntryChild(newEntry.sharedID());
+    ContentChild::GetSingleton()->BindPSHEntryEndpoint(
+        std::move(newEntry.endpoint()), do_AddRef(entry).take());
+  } else {
+    entry = static_cast<SHEntryChild*>(aEntry.get_PSHEntryChild());
+  }
+  return entry.forget();
+}
+
+}  // namespace dom
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/docshell/shistory/SHEntryChild.h
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_dom_SHEntryChild_h
+#define mozilla_dom_SHEntryChild_h
+
+#include "mozilla/dom/PSHEntryChild.h"
+#include "nsContentUtils.h"
+#include "nsExpirationTracker.h"
+#include "nsIBFCacheEntry.h"
+#include "nsISHEntry.h"
+#include "nsRect.h"
+#include "nsSHEntryShared.h"
+#include "nsStubMutationObserver.h"
+
+class nsDocShellEditorData;
+class nsIContentViewer;
+class nsILayoutHistoryState;
+class nsIMutableArray;
+
+namespace mozilla {
+namespace dom {
+
+class SHistoryChild;
+
+/**
+ * Implementation of the shared state for session history entries in the child
+ * process.
+ */
+class SHEntryChildShared final : public nsIBFCacheEntry,
+                                 public nsStubMutationObserver,
+                                 public SHEntrySharedChildState {
+ public:
+  static void Init();
+
+  static SHEntryChildShared* GetOrCreate(uint64_t aSharedID);
+  static void Remove(uint64_t aSharedID);
+
+  static uint64_t CreateSharedID() {
+    return nsContentUtils::GenerateProcessSpecificId(++sNextSharedID);
+  }
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIBFCACHEENTRY
+
+  // The nsIMutationObserver bits we actually care about.
+  NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
+  NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+  NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+  NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+  NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+
+  nsExpirationState* GetExpirationState() { return &mExpirationState; }
+
+  uint64_t GetID() { return mID; }
+
+ private:
+  static uint64_t sNextSharedID;
+
+  explicit SHEntryChildShared(uint64_t aID);
+  ~SHEntryChildShared();
+
+  friend class SHEntryChild;
+
+  already_AddRefed<SHEntryChildShared> Duplicate();
+
+  void RemoveFromExpirationTracker();
+  void SyncPresentationState();
+  void DropPresentationState();
+
+  nsresult SetContentViewer(nsIContentViewer* aViewer);
+
+  uint64_t mID;
+  RefPtr<SHistoryChild> mSHistory;
+};
+
+/**
+ * Session history entry actor for the child process.
+ */
+class SHEntryChild final : public PSHEntryChild, public nsISHEntry {
+  friend class PSHEntryChild;
+
+ public:
+  explicit SHEntryChild(const SHEntryChild* aClone)
+      : mShared(aClone->mShared.get()), mIPCActorDeleted(false) {}
+  explicit SHEntryChild(uint64_t aSharedID)
+      : mShared(SHEntryChildShared::GetOrCreate(aSharedID)),
+        mIPCActorDeleted(false) {}
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSISHENTRY
+
+  void EvictContentViewer();
+
+  static already_AddRefed<SHEntryChild> GetOrCreate(MaybeNewPSHEntry& aEntry);
+
+ protected:
+  void ActorDestroy(ActorDestroyReason aWhy) override {
+    mIPCActorDeleted = true;
+  }
+
+ private:
+  ~SHEntryChild() = default;
+
+  RefPtr<SHEntryChildShared> mShared;
+  bool mIPCActorDeleted;
+};
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif /* mozilla_dom_SHEntryChild_h */
new file mode 100644
--- /dev/null
+++ b/docshell/shistory/SHEntryParent.cpp
@@ -0,0 +1,530 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "SHEntryParent.h"
+#include "SHistoryParent.h"
+#include "mozilla/dom/ContentParent.h"
+#include "nsStructuredCloneContainer.h"
+
+namespace mozilla {
+namespace dom {
+
+SHEntrySharedParent::SHEntrySharedParent(PContentParent* aContentParent,
+                                         uint64_t aSharedID)
+    : SHEntrySharedParentState(aSharedID), mContentParent(aContentParent) {}
+
+void SHEntrySharedParent::Destroy() {
+  if (mContentParent &&
+      !static_cast<ContentParent*>(mContentParent.get())->IsDestroyed()) {
+    Unused << mContentParent->SendDestroySHEntrySharedState(mID);
+  }
+  SHEntrySharedParentState::Destroy();
+}
+
+SHEntryParent* LegacySHEntry::CreateActor() {
+  MOZ_ASSERT(!mActor);
+  mActor = new SHEntryParent(this);
+  return mActor;
+}
+
+void LegacySHEntry::GetOrCreateActor(PContentParent* aContentParent,
+                                     MaybeNewPSHEntry& aEntry) {
+  if (!mActor) {
+    mActor = new SHEntryParent(this);
+    aEntry =
+        NewPSHEntry(aContentParent->OpenPSHEntryEndpoint(mActor), mShared->mID);
+  } else {
+    aEntry = mActor;
+  }
+}
+
+void LegacySHEntry::AbandonBFCacheEntry(uint64_t aNewSharedID) {
+  PContentParent* contentParent =
+      static_cast<SHEntrySharedParent*>(mShared.get())->GetContentParent();
+  RefPtr<SHEntrySharedParent> shared =
+      new SHEntrySharedParent(contentParent, aNewSharedID);
+  shared->CopyFrom(mShared);
+  mShared = shared.forget();
+}
+
+void SHEntryParent::ActorDestroy(ActorDestroyReason aWhy) {
+  mEntry->mActor = nullptr;
+}
+
+bool SHEntryParent::RecvGetURI(RefPtr<nsIURI>* aURI) {
+  *aURI = mEntry->GetURI();
+  return true;
+}
+
+bool SHEntryParent::RecvSetURI(nsIURI* aURI) {
+  DebugOnly<nsresult> rv = mEntry->SetURI(aURI);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvGetOriginalURI(RefPtr<nsIURI>* aOriginalURI) {
+  *aOriginalURI = mEntry->GetOriginalURI();
+  return true;
+}
+
+bool SHEntryParent::RecvSetOriginalURI(nsIURI* aOriginalURI) {
+  DebugOnly<nsresult> rv = mEntry->SetOriginalURI(aOriginalURI);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvGetResultPrincipalURI(
+    RefPtr<nsIURI>* aResultPrincipalURI) {
+  *aResultPrincipalURI = mEntry->GetResultPrincipalURI();
+  return true;
+}
+
+bool SHEntryParent::RecvSetResultPrincipalURI(nsIURI* aResultPrincipalURI) {
+  DebugOnly<nsresult> rv = mEntry->SetResultPrincipalURI(aResultPrincipalURI);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvGetLoadReplace(bool* aLoadReplace) {
+  *aLoadReplace = mEntry->GetLoadReplace();
+  return true;
+}
+
+bool SHEntryParent::RecvSetLoadReplace(const bool& aLoadReplace) {
+  DebugOnly<nsresult> rv = mEntry->SetLoadReplace(aLoadReplace);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvGetTitle(nsString* aTitle) {
+  DebugOnly<nsresult> rv = mEntry->GetTitle(*aTitle);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvSetTitle(const nsString& aTitle) {
+  DebugOnly<nsresult> rv = mEntry->SetTitle(aTitle);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvGetIsSubFrame(bool* aIsSubFrame) {
+  *aIsSubFrame = mEntry->GetIsSubFrame();
+  return true;
+}
+
+bool SHEntryParent::RecvSetIsSubFrame(const bool& aIsSubFrame) {
+  DebugOnly<nsresult> rv = mEntry->SetIsSubFrame(aIsSubFrame);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvGetReferrerInfo(
+    RefPtr<nsIReferrerInfo>* aReferrerInfo) {
+  *aReferrerInfo = mEntry->GetReferrerInfo();
+  return true;
+}
+
+bool SHEntryParent::RecvSetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
+  DebugOnly<nsresult> rv = mEntry->SetReferrerInfo(aReferrerInfo);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvGetSticky(bool* aSticky) {
+  *aSticky = mEntry->GetSticky();
+  return true;
+}
+
+bool SHEntryParent::RecvSetSticky(const bool& aSticky) {
+  DebugOnly<nsresult> rv = mEntry->SetSticky(aSticky);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvGetPostData(RefPtr<nsIInputStream>* aPostData) {
+  *aPostData = mEntry->GetPostData();
+  return true;
+}
+
+bool SHEntryParent::RecvSetPostData(nsIInputStream* aPostData) {
+  DebugOnly<nsresult> rv = mEntry->SetPostData(aPostData);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvGetParent(MaybeNewPSHEntry* aParentEntry) {
+  nsCOMPtr<nsISHEntry> parent = mEntry->GetParent();
+  GetOrCreate(parent, aParentEntry);
+  return true;
+}
+
+bool SHEntryParent::RecvSetParent(PSHEntryParent* aParentEntry) {
+  DebugOnly<nsresult> rv = mEntry->SetParent(
+      aParentEntry ? static_cast<SHEntryParent*>(aParentEntry)->mEntry.get()
+                   : nullptr);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvGetLoadType(uint32_t* aLoadType) {
+  *aLoadType = mEntry->GetLoadType();
+  return true;
+}
+
+bool SHEntryParent::RecvSetLoadType(const uint32_t& aLoadType) {
+  DebugOnly<nsresult> rv = mEntry->SetLoadType(aLoadType);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvGetID(uint32_t* aID) {
+  *aID = mEntry->GetID();
+  return true;
+}
+
+bool SHEntryParent::RecvSetID(const uint32_t& aID) {
+  DebugOnly<nsresult> rv = mEntry->SetID(aID);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvGetCacheKey(uint32_t* aCacheKey) {
+  *aCacheKey = mEntry->GetCacheKey();
+  return true;
+}
+
+bool SHEntryParent::RecvSetCacheKey(const uint32_t& aCacheKey) {
+  DebugOnly<nsresult> rv = mEntry->SetCacheKey(aCacheKey);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvGetExpirationStatus(bool* aExpirationStatus) {
+  *aExpirationStatus = mEntry->GetExpirationStatus();
+  return true;
+}
+
+bool SHEntryParent::RecvSetExpirationStatus(const bool& aExpirationStatus) {
+  DebugOnly<nsresult> rv = mEntry->SetExpirationStatus(aExpirationStatus);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvGetContentType(nsCString* aContentType) {
+  DebugOnly<nsresult> rv = mEntry->GetContentType(*aContentType);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvSetContentType(const nsCString& aContentType) {
+  DebugOnly<nsresult> rv = mEntry->SetContentType(aContentType);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvGetURIWasModified(bool* aURIWasModified) {
+  *aURIWasModified = mEntry->GetURIWasModified();
+  return true;
+}
+
+bool SHEntryParent::RecvSetURIWasModified(const bool& aURIWasModified) {
+  DebugOnly<nsresult> rv = mEntry->SetURIWasModified(aURIWasModified);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvGetTriggeringPrincipal(
+    RefPtr<nsIPrincipal>* aTriggeringPrincipal) {
+  *aTriggeringPrincipal = mEntry->GetTriggeringPrincipal();
+  return true;
+}
+
+bool SHEntryParent::RecvSetTriggeringPrincipal(
+    nsIPrincipal* aTriggeringPrincipal) {
+  DebugOnly<nsresult> rv = mEntry->SetTriggeringPrincipal(aTriggeringPrincipal);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvGetPrincipalToInherit(
+    RefPtr<nsIPrincipal>* aPrincipalToInherit) {
+  *aPrincipalToInherit = mEntry->GetPrincipalToInherit();
+  return true;
+}
+
+bool SHEntryParent::RecvSetPrincipalToInherit(
+    nsIPrincipal* aPrincipalToInherit) {
+  DebugOnly<nsresult> rv = mEntry->SetPrincipalToInherit(aPrincipalToInherit);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvGetStoragePrincipalToInherit(
+    RefPtr<nsIPrincipal>* aStoragePrincipalToInherit) {
+  *aStoragePrincipalToInherit = mEntry->GetStoragePrincipalToInherit();
+  return true;
+}
+
+bool SHEntryParent::RecvSetStoragePrincipalToInherit(
+    nsIPrincipal* aStoragePrincipalToInherit) {
+  DebugOnly<nsresult> rv =
+      mEntry->SetStoragePrincipalToInherit(aStoragePrincipalToInherit);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvGetCsp(RefPtr<nsIContentSecurityPolicy>* aCsp) {
+  *aCsp = mEntry->GetCsp();
+  return true;
+}
+
+bool SHEntryParent::RecvSetCsp(nsIContentSecurityPolicy* aCsp) {
+  DebugOnly<nsresult> rv = mEntry->SetCsp(aCsp);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvGetStateData(ClonedMessageData* aData) {
+  nsCOMPtr<nsIStructuredCloneContainer> container = mEntry->GetStateData();
+  if (container) {
+    static_cast<nsStructuredCloneContainer*>(container.get())
+        ->BuildClonedMessageDataForParent(
+            static_cast<ContentParent*>(Manager()), *aData);
+  }
+  return true;
+}
+
+bool SHEntryParent::RecvSetStateData(ClonedMessageData&& aData) {
+  // FIXME Need more data! Should we signal null separately from the
+  //       ClonedMessageData?
+  if (aData.data().data.Size() == 0) {
+    mEntry->SetStateData(nullptr);
+  } else {
+    RefPtr<nsStructuredCloneContainer> container =
+        new nsStructuredCloneContainer();
+    container->StealFromClonedMessageDataForParent(aData);
+    mEntry->SetStateData(container);
+  }
+  return true;
+}
+
+bool SHEntryParent::RecvGetDocshellID(nsID* aDocshellID) {
+  DebugOnly<nsresult> rv = mEntry->GetDocshellID(*aDocshellID);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+
+  return true;
+}
+
+bool SHEntryParent::RecvSetDocshellID(const nsID& aDocshellID) {
+  DebugOnly<nsresult> rv = mEntry->SetDocshellID(aDocshellID);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvGetIsSrcdocEntry(bool* aIsSrcdocEntry) {
+  *aIsSrcdocEntry = mEntry->GetIsSrcdocEntry();
+  return true;
+}
+
+bool SHEntryParent::RecvGetSrcdocData(nsString* aSrcdocData) {
+  DebugOnly<nsresult> rv = mEntry->GetSrcdocData(*aSrcdocData);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvSetSrcdocData(const nsString& aSrcdocData) {
+  DebugOnly<nsresult> rv = mEntry->SetSrcdocData(aSrcdocData);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvGetBaseURI(RefPtr<nsIURI>* aBaseURI) {
+  *aBaseURI = mEntry->GetBaseURI();
+  return true;
+}
+
+bool SHEntryParent::RecvSetBaseURI(nsIURI* aBaseURI) {
+  DebugOnly<nsresult> rv = mEntry->SetBaseURI(aBaseURI);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvGetScrollRestorationIsManual(
+    bool* aScrollRestorationIsManual) {
+  DebugOnly<nsresult> rv =
+      mEntry->GetScrollRestorationIsManual(aScrollRestorationIsManual);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvSetScrollRestorationIsManual(
+    const bool& aScrollRestorationIsManual) {
+  DebugOnly<nsresult> rv =
+      mEntry->SetScrollRestorationIsManual(aScrollRestorationIsManual);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvGetLoadedInThisProcess(bool* aLoadedInThisProcess) {
+  *aLoadedInThisProcess = mEntry->GetLoadedInThisProcess();
+  return true;
+}
+
+bool SHEntryParent::RecvGetLastTouched(uint32_t* aLastTouched) {
+  *aLastTouched = mEntry->GetLastTouched();
+  return true;
+}
+
+bool SHEntryParent::RecvSetLastTouched(const uint32_t& aLastTouched) {
+  DebugOnly<nsresult> rv = mEntry->SetLastTouched(aLastTouched);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvGetChildCount(int32_t* aChildCount) {
+  *aChildCount = mEntry->GetChildCount();
+  return true;
+}
+
+bool SHEntryParent::RecvGetPersist(bool* aPersist) {
+  *aPersist = mEntry->GetPersist();
+  return true;
+}
+
+bool SHEntryParent::RecvSetPersist(const bool& aPersist) {
+  DebugOnly<nsresult> rv = mEntry->SetPersist(aPersist);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvGetScrollPosition(int32_t* aX, int32_t* aY) {
+  DebugOnly<nsresult> rv = mEntry->GetScrollPosition(aX, aY);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvSetScrollPosition(const int32_t& aX,
+                                          const int32_t& aY) {
+  DebugOnly<nsresult> rv = mEntry->SetScrollPosition(aX, aY);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvGetViewerBounds(nsIntRect* aBounds) {
+  mEntry->GetViewerBounds(*aBounds);
+  return true;
+}
+
+bool SHEntryParent::RecvSetViewerBounds(const nsIntRect& aBounds) {
+  mEntry->SetViewerBounds(aBounds);
+  return true;
+}
+
+bool SHEntryParent::RecvCreate(
+    nsIURI* aURI, const nsString& aTitle, nsIInputStream* aInputStream,
+    const uint32_t& aCacheKey, const nsCString& aContentType,
+    nsIPrincipal* aTriggeringPrincipal, nsIPrincipal* aPrincipalToInherit,
+    nsIContentSecurityPolicy* aCsp, const nsID& aDocshellID,
+    const bool& aDynamicCreation) {
+  DebugOnly<nsresult> rv = mEntry->Create(
+      aURI, aTitle, aInputStream, aCacheKey, aContentType, aTriggeringPrincipal,
+      aPrincipalToInherit, aCsp, aDocshellID, aDynamicCreation);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvHasDetachedEditor(bool* aHasDetachedEditor) {
+  *aHasDetachedEditor = mEntry->HasDetachedEditor();
+  return true;
+}
+
+bool SHEntryParent::RecvIsDynamicallyAdded(bool* aIsDynamicallyAdded) {
+  *aIsDynamicallyAdded = mEntry->IsDynamicallyAdded();
+  return true;
+}
+
+bool SHEntryParent::RecvHasDynamicallyAddedChild(
+    bool* aHasDynamicallyAddedChild) {
+  DebugOnly<nsresult> rv =
+      mEntry->HasDynamicallyAddedChild(aHasDynamicallyAddedChild);
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvDocshellID(nsID* aDocshellID) {
+  mEntry->GetDocshellID(*aDocshellID);
+  return true;
+}
+
+bool SHEntryParent::RecvAdoptBFCacheEntry(PSHEntryParent* aEntry,
+                                          nsresult* aResult) {
+  *aResult =
+      mEntry->AdoptBFCacheEntry(static_cast<SHEntryParent*>(aEntry)->mEntry);
+  return true;
+}
+
+bool SHEntryParent::RecvAbandonBFCacheEntry(const uint64_t& aNewSharedID) {
+  mEntry->AbandonBFCacheEntry(aNewSharedID);
+  return true;
+}
+
+bool SHEntryParent::RecvSharesDocumentWith(PSHEntryParent* aEntry,
+                                           bool* aSharesDocumentWith,
+                                           nsresult* aResult) {
+  *aResult = mEntry->SharesDocumentWith(
+      static_cast<SHEntryParent*>(aEntry)->mEntry, aSharesDocumentWith);
+  return true;
+}
+
+bool SHEntryParent::RecvSetLoadTypeAsHistory() {
+  DebugOnly<nsresult> rv = mEntry->SetLoadTypeAsHistory();
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+  return true;
+}
+
+bool SHEntryParent::RecvAddChild(PSHEntryParent* aChild, const int32_t& aOffset,
+                                 const bool& aUseRemoteSubframes,
+                                 nsresult* aResult) {
+  *aResult = mEntry->AddChild(
+      aChild ? static_cast<SHEntryParent*>(aChild)->mEntry.get() : nullptr,
+      aOffset, aUseRemoteSubframes);
+  return true;
+}
+
+bool SHEntryParent::RecvRemoveChild(PSHEntryParent* aChild, nsresult* aResult) {
+  *aResult = mEntry->RemoveChild(static_cast<SHEntryParent*>(aChild)->mEntry);
+  return true;
+}
+
+bool SHEntryParent::RecvGetChildAt(const int32_t& aIndex,
+                                   MaybeNewPSHEntry* aChild) {
+  nsCOMPtr<nsISHEntry> child;
+  DebugOnly<nsresult> rv = mEntry->GetChildAt(aIndex, getter_AddRefs(child));
+  MOZ_ASSERT(NS_SUCCEEDED(rv), "Didn't expect this to fail.");
+
+  GetOrCreate(child, aChild);
+  return true;
+}
+
+bool SHEntryParent::RecvReplaceChild(PSHEntryParent* aNewChild,
+                                     nsresult* aResult) {
+  *aResult =
+      mEntry->ReplaceChild(static_cast<SHEntryParent*>(aNewChild)->mEntry);
+  return true;
+}
+
+void SHEntryParent::GetOrCreate(PContentParent* aManager, nsISHEntry* aSHEntry,
+                                MaybeNewPSHEntry& aResult) {
+  if (aSHEntry) {
+    static_cast<LegacySHEntry*>(aSHEntry)->GetOrCreateActor(aManager, aResult);
+  } else {
+    aResult = (PSHEntryParent*)nullptr;
+  }
+}
+
+}  // namespace dom
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/docshell/shistory/SHEntryParent.h
@@ -0,0 +1,184 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_dom_SHistoryEntry_h
+#define mozilla_dom_SHistoryEntry_h
+
+#include "mozilla/dom/PSHEntryParent.h"
+#include "mozilla/WeakPtr.h"
+#include "nsSHEntry.h"
+#include "nsSHEntryShared.h"
+
+namespace mozilla {
+namespace dom {
+
+class PContentParent;
+class SHEntryParent;
+
+/**
+ * Implementation of the shared state for session history entries in the parent
+ * process.
+ */
+class SHEntrySharedParent : public SHEntrySharedParentState {
+ public:
+  SHEntrySharedParent(PContentParent* aContentParent, uint64_t aSharedID);
+
+  void Destroy() override;
+
+  PContentParent* GetContentParent() { return mContentParent.get(); }
+
+ private:
+  mozilla::WeakPtr<PContentParent> mContentParent;
+};
+
+/**
+ * Session history entry implementation based on the legacy implementation that
+ * used to live in the child process. Ideally this wouldn't implement nsISHEntry
+ * (it should only ever be accessed by SHEntryParent and LegacySHistory).
+ * The actor is (re)created as needed, whenever we need to return an entry to
+ * the child process. The lifetime is determined by the child side.
+ */
+class LegacySHEntry final : public nsSHEntry {
+ public:
+  LegacySHEntry(PContentParent* aParent, uint64_t aSharedID)
+      : nsSHEntry(new SHEntrySharedParent(aParent, aSharedID)),
+        mActor(nullptr) {}
+  explicit LegacySHEntry(const LegacySHEntry& aEntry)
+      : nsSHEntry(aEntry), mActor(nullptr) {}
+
+  void GetOrCreateActor(PContentParent* aContentParent,
+                        MaybeNewPSHEntry& aEntry);
+
+  using nsSHEntry::AbandonBFCacheEntry;
+  void AbandonBFCacheEntry(uint64_t aNewSharedID);
+
+  uint64_t GetSharedStateID() const { return mShared->GetID(); }
+
+ private:
+  friend class SHEntryParent;
+  friend class ContentParent;
+
+  SHEntryParent* CreateActor();
+
+  SHEntryParent* mActor;
+};
+
+/**
+ * Session history entry actor for the parent process. Forwards to the legacy
+ * implementation that used to live in the child process (see LegacySHEntry).
+ */
+class SHEntryParent final : public PSHEntryParent {
+  friend class PSHEntryParent;
+  friend class SHistoryParent;
+  friend class ContentParent;
+
+ public:
+  explicit SHEntryParent(LegacySHEntry* aEntry)
+      : PSHEntryParent(), mEntry(aEntry) {}
+
+  static void GetOrCreate(PContentParent* aManager, nsISHEntry* aSHEntry,
+                          MaybeNewPSHEntry& aResult);
+
+ protected:
+  void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ private:
+  bool RecvGetURI(RefPtr<nsIURI>* aURI);
+  bool RecvSetURI(nsIURI* aURI);
+  bool RecvGetOriginalURI(RefPtr<nsIURI>* aOriginalURI);
+  bool RecvSetOriginalURI(nsIURI* aOriginalURI);
+  bool RecvGetResultPrincipalURI(RefPtr<nsIURI>* aResultPrincipalURI);
+  bool RecvSetResultPrincipalURI(nsIURI* aResultPrincipalURI);
+  bool RecvGetLoadReplace(bool* aLoadReplace);
+  bool RecvSetLoadReplace(const bool& aLoadReplace);
+  bool RecvGetTitle(nsString* aTitle);
+  bool RecvSetTitle(const nsString& aTitle);
+  bool RecvGetIsSubFrame(bool* aIsSubFrame);
+  bool RecvSetIsSubFrame(const bool& aIsSubFrame);
+  bool RecvGetReferrerInfo(RefPtr<nsIReferrerInfo>* aReferrerInfo);
+  bool RecvSetReferrerInfo(nsIReferrerInfo* aReferrerInfo);
+  bool RecvGetSticky(bool* aSticky);
+  bool RecvSetSticky(const bool& aSticky);
+  bool RecvGetPostData(RefPtr<nsIInputStream>* aPostData);
+  bool RecvSetPostData(nsIInputStream* aPostData);
+  bool RecvGetParent(MaybeNewPSHEntry* aParentEntry);
+  bool RecvSetParent(PSHEntryParent* aParentEntry);
+  bool RecvGetLoadType(uint32_t* aLoadType);
+  bool RecvSetLoadType(const uint32_t& aLoadType);
+  bool RecvGetID(uint32_t* aID);
+  bool RecvSetID(const uint32_t& aID);
+  bool RecvGetCacheKey(uint32_t* aCacheKey);
+  bool RecvSetCacheKey(const uint32_t& aCacheKey);
+  bool RecvGetExpirationStatus(bool* aExpirationStatus);
+  bool RecvSetExpirationStatus(const bool& aExpirationStatus);
+  bool RecvGetContentType(nsCString* aContentType);
+  bool RecvSetContentType(const nsCString& aContentType);
+  bool RecvGetURIWasModified(bool* aURIWasModified);
+  bool RecvSetURIWasModified(const bool& aURIWasModified);
+  bool RecvGetTriggeringPrincipal(RefPtr<nsIPrincipal>* aTriggeringPrincipal);
+  bool RecvSetTriggeringPrincipal(nsIPrincipal* aTriggeringPrincipal);
+  bool RecvGetPrincipalToInherit(RefPtr<nsIPrincipal>* aPrincipalToInherit);
+  bool RecvSetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit);
+  bool RecvGetStoragePrincipalToInherit(
+      RefPtr<nsIPrincipal>* aStoragePrincipalToInherit);
+  bool RecvSetStoragePrincipalToInherit(
+      nsIPrincipal* aStoragePrincipalToInherit);
+  bool RecvGetCsp(RefPtr<nsIContentSecurityPolicy>* aCsp);
+  bool RecvSetCsp(nsIContentSecurityPolicy* aCsp);
+  bool RecvGetStateData(ClonedMessageData* aData);
+  bool RecvSetStateData(ClonedMessageData&& aData);
+  bool RecvGetDocshellID(nsID* aDocshellID);
+  bool RecvSetDocshellID(const nsID& aDocshellID);
+  bool RecvGetIsSrcdocEntry(bool* aIsSrcdocEntry);
+  bool RecvGetSrcdocData(nsString* aSrcdocData);
+  bool RecvSetSrcdocData(const nsString& aSrcdocData);
+  bool RecvGetBaseURI(RefPtr<nsIURI>* aBaseURI);
+  bool RecvSetBaseURI(nsIURI* aBaseURI);
+  bool RecvGetScrollRestorationIsManual(bool* aScrollRestorationIsManual);
+  bool RecvSetScrollRestorationIsManual(const bool& aScrollRestorationIsManual);
+  bool RecvGetLoadedInThisProcess(bool* aLoadedInThisProcess);
+  bool RecvGetLastTouched(uint32_t* aLastTouched);
+  bool RecvSetLastTouched(const uint32_t& aLastTouched);
+  bool RecvGetChildCount(int32_t* aChildCount);
+  bool RecvGetPersist(bool* aPersist);
+  bool RecvSetPersist(const bool& aPersist);
+  bool RecvGetScrollPosition(int32_t* aX, int32_t* aY);
+  bool RecvSetScrollPosition(const int32_t& aX, const int32_t& aY);
+  bool RecvGetViewerBounds(nsIntRect* aBounds);
+  bool RecvSetViewerBounds(const nsIntRect& aBounds);
+  bool RecvCreate(nsIURI* aURI, const nsString& aTitle,
+                  nsIInputStream* aInputStream, const uint32_t& aCacheKey,
+                  const nsCString& aContentType,
+                  nsIPrincipal* aTriggeringPrincipal,
+                  nsIPrincipal* aPrincipalToInherit,
+                  nsIContentSecurityPolicy* aCsp, const nsID& aDocshellID,
+                  const bool& aDynamicCreation);
+  bool RecvHasDetachedEditor(bool* aHasDetachedEditor);
+  bool RecvIsDynamicallyAdded(bool* aIsDynamicallyAdded);
+  bool RecvHasDynamicallyAddedChild(bool* aHasDynamicallyAddedChild);
+  bool RecvDocshellID(nsID* aDocshellID);
+  bool RecvAdoptBFCacheEntry(PSHEntryParent* aEntry, nsresult* aResult);
+  bool RecvAbandonBFCacheEntry(const uint64_t& aNewSharedID);
+  bool RecvSharesDocumentWith(PSHEntryParent* aEntry, bool* aSharesDocumentWith,
+                              nsresult* aResult);
+  bool RecvSetLoadTypeAsHistory();
+  bool RecvAddChild(PSHEntryParent* aChild, const int32_t& aOffset,
+                    const bool& aUseRemoteSubframes, nsresult* aResult);
+  bool RecvRemoveChild(PSHEntryParent* aChild, nsresult* aResult);
+  bool RecvGetChildAt(const int32_t& aIndex, MaybeNewPSHEntry* aChild);
+  bool RecvReplaceChild(PSHEntryParent* aNewChild, nsresult* aResult);
+
+  void GetOrCreate(nsISHEntry* aSHEntry, MaybeNewPSHEntry* aResult) {
+    GetOrCreate(Manager(), aSHEntry, *aResult);
+  }
+
+  RefPtr<LegacySHEntry> mEntry;
+};
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif /* mozilla_dom_SHEntryParent_h */
new file mode 100644
--- /dev/null
+++ b/docshell/shistory/SHistoryChild.cpp
@@ -0,0 +1,374 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "SHistoryChild.h"
+#include "SHEntryChild.h"
+#include "nsISHistoryListener.h"
+
+#define CONTENT_VIEWER_TIMEOUT_SECONDS \
+  "browser.sessionhistory.contentViewerTimeout"
+
+// Default this to time out unused content viewers after 30 minutes
+#define CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT (30 * 60)
+
+namespace mozilla {
+namespace dom {
+
+void SHistoryChild::HistoryTracker::NotifyExpired(SHEntryChildShared* aObj) {
+  RemoveObject(aObj);
+  mSHistory->EvictExpiredContentViewerForEntry(aObj);
+}
+
+SHistoryChild::SHistoryChild(BrowsingContext* aRootBC)
+    : mRootDocShell(static_cast<nsDocShell*>(aRootBC->GetDocShell())),
+      mIPCActorDeleted(false) {
+  // Bind mHistoryTracker's event target to the tabGroup for aRootBC.
+  // Maybe move this to ChildSHistory?
+  nsCOMPtr<nsPIDOMWindowOuter> win = aRootBC->GetDOMWindow();
+  if (win) {
+    // Seamonkey moves shistory between <xul:browser>s when restoring a tab.
+    // Let's try not to break our friend too badly...
+    if (mHistoryTracker) {
+      NS_WARNING(
+          "Change the root docshell of a shistory is unsafe and "
+          "potentially problematic.");
+      mHistoryTracker->AgeAllGenerations();
+    }
+
+    nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(win);
+
+    mHistoryTracker = mozilla::MakeUnique<SHistoryChild::HistoryTracker>(
+        this,
+        mozilla::Preferences::GetUint(CONTENT_VIEWER_TIMEOUT_SECONDS,
+                                      CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT),
+        global->EventTargetFor(mozilla::TaskCategory::Other));
+  }
+}
+
+NS_IMPL_ADDREF(SHistoryChild)
+NS_IMETHODIMP_(MozExternalRefCountType) SHistoryChild::Release() {
+  MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
+  NS_ASSERT_OWNINGTHREAD(SHEntryChild);
+  nsrefcnt count = --mRefCnt;
+  NS_LOG_RELEASE(this, count, "SHistoryChild");
+  if (count == 0) {
+    mRefCnt = 1; /* stabilize */
+    delete this;
+    return 0;
+  }
+  if (count == 1 && !mIPCActorDeleted) {
+    Unused << Send__delete__(this);
+  }
+  return count;
+}
+NS_IMPL_QUERY_INTERFACE(SHistoryChild, nsISHistory, nsISupportsWeakReference)
+
+NS_IMETHODIMP
+SHistoryChild::GetCount(int32_t* aCount) {
+  return SendGetCount(aCount) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHistoryChild::GetIndex(int32_t* aIndex) {
+  return SendGetIndex(aIndex) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHistoryChild::SetIndex(int32_t aIndex) {
+  nsresult rv;
+  return SendSetIndex(aIndex, &rv) ? rv : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHistoryChild::GetRequestedIndex(int32_t* aRequestedIndex) {
+  return SendGetRequestedIndex(aRequestedIndex) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP_(void)
+SHistoryChild::InternalSetRequestedIndex(int32_t aRequestedIndex) {
+  SendInternalSetRequestedIndex(aRequestedIndex);
+}
+
+NS_IMETHODIMP
+SHistoryChild::GetEntryAtIndex(int32_t aIndex, nsISHEntry** aResult) {
+  nsresult rv;
+  MaybeNewPSHEntry entry;
+  if (!SendGetEntryAtIndex(aIndex, &rv, &entry)) {
+    return NS_ERROR_FAILURE;
+  }
+  RefPtr<SHEntryChild> child;
+  if (NS_SUCCEEDED(rv)) {
+    child = SHEntryChild::GetOrCreate(entry);
+  }
+  child.forget(aResult);
+  return rv;
+}
+
+NS_IMETHODIMP
+SHistoryChild::PurgeHistory(int32_t aNumEntries) {
+  nsresult rv;
+  if (!SendPurgeHistory(aNumEntries, &rv)) {
+    return NS_ERROR_FAILURE;
+  }
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (mRootDocShell) {
+    mRootDocShell->HistoryPurged(aNumEntries);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHistoryChild::AddSHistoryListener(nsISHistoryListener* aListener) {
+  NS_ENSURE_ARG_POINTER(aListener);
+
+  // Check if the listener supports Weak Reference. This is a must.
+  // This listener functionality is used by embedders and we want to
+  // have the right ownership with who ever listens to SHistory
+  nsWeakPtr listener = do_GetWeakReference(aListener);
+  if (!listener) {
+    return NS_ERROR_FAILURE;
+  }
+
+  mListeners.AppendElementUnlessExists(listener);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHistoryChild::RemoveSHistoryListener(nsISHistoryListener* aListener) {
+  // Make sure the listener that wants to be removed is the
+  // one we have in store.
+  nsWeakPtr listener = do_GetWeakReference(aListener);
+  mListeners.RemoveElement(listener);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHistoryChild::ReloadCurrentEntry() {
+  LoadSHEntryResult loadResult;
+  if (!SendReloadCurrentEntry(&loadResult)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (loadResult.type() == LoadSHEntryResult::Tnsresult) {
+    return loadResult;
+  }
+
+  return LoadURI(loadResult);
+}
+
+NS_IMETHODIMP
+SHistoryChild::GotoIndex(int32_t aIndex) {
+  LoadSHEntryResult loadResult;
+  if (!SendGotoIndex(aIndex, &loadResult)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (loadResult.type() == LoadSHEntryResult::Tnsresult) {
+    return loadResult;
+  }
+
+  return LoadURI(loadResult);
+}
+
+NS_IMETHODIMP_(int32_t)
+SHistoryChild::GetIndexOfEntry(nsISHEntry* aEntry) {
+  int32_t index;
+  if (!SendGetIndexOfEntry(static_cast<SHEntryChild*>(aEntry), &index)) {
+    return 0;
+  }
+  return index;
+}
+
+NS_IMETHODIMP
+SHistoryChild::AddEntry(nsISHEntry* aEntry, bool aPersist) {
+  NS_ENSURE_ARG(aEntry);
+
+  nsresult rv;
+  int32_t entriesPurged;
+  if (!SendAddEntry(static_cast<SHEntryChild*>(aEntry), aPersist, &rv,
+                    &entriesPurged)) {
+    return NS_ERROR_FAILURE;
+  }
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  aEntry->SetSHistory(this);
+  if (mRootDocShell) {
+    aEntry->SetDocshellID(mRootDocShell->HistoryID());
+
+    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;
+  if (!SendReplaceEntry(aIndex, static_cast<SHEntryChild*>(aReplaceEntry),
+                        &rv)) {
+    return NS_ERROR_FAILURE;
+  }
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  aReplaceEntry->SetSHistory(this);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHistoryChild::NotifyOnHistoryReload(bool* _retval) {
+  return SendNotifyOnHistoryReload(_retval) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHistoryChild::EvictOutOfRangeContentViewers(int32_t aIndex) {
+  // FIXME Need to get out of range entries and entries that are safe (to
+  //       compare content viewers so we don't evict live content viewers).
+  return SendEvictOutOfRangeContentViewers(aIndex) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SHistoryChild::EvictExpiredContentViewerForEntry(nsIBFCacheEntry* aBFEntry) {
+  SHEntryChildShared* shared = static_cast<SHEntryChildShared*>(aBFEntry);
+
+  MaybeNewPSHEntry entry;
+  int32_t index;
+  if (!SendFindEntryForBFCache(shared->GetID(), false, &entry, &index)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  RefPtr<SHEntryChild> shEntry = SHEntryChild::GetOrCreate(entry);
+  if (shEntry) {
+    shEntry->EvictContentViewer();
+    SendEvict(nsTArray<PSHEntryChild*>({shEntry.get()}));
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SHistoryChild::EvictAllContentViewers(void) {
+  nsTArray<MaybeNewPSHEntry> entries;
+  if (!SendGetAllEntries(&entries)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // Keep a strong reference to all the entries, we're going to send the array
+  // back to the parent!
+  nsTArray<RefPtr<SHEntryChild>> shEntries(entries.Length());
+  for (MaybeNewPSHEntry& entry : entries) {
+    RefPtr<SHEntryChild> shEntry = SHEntryChild::GetOrCreate(entry);
+    shEntry->EvictContentViewer();
+    shEntries.AppendElement(shEntry.forget());
+  }
+
+  nsTArray<PSHEntryChild*> pshEntries;
+  pshEntries.AppendElements(shEntries);
+  SendEvict(pshEntries);
+  return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+SHistoryChild::AddToExpirationTracker(nsIBFCacheEntry* aBFEntry) {
+  RefPtr<SHEntryChildShared> entry = static_cast<SHEntryChildShared*>(aBFEntry);
+  if (mHistoryTracker && entry) {
+    mHistoryTracker->AddObject(entry);
+  }
+}
+
+NS_IMETHODIMP_(void)
+SHistoryChild::RemoveFromExpirationTracker(nsIBFCacheEntry* aBFEntry) {
+  RefPtr<SHEntryChildShared> entry = static_cast<SHEntryChildShared*>(aBFEntry);
+  MOZ_ASSERT(mHistoryTracker && !mHistoryTracker->IsEmpty());
+  if (mHistoryTracker && entry) {
+    mHistoryTracker->RemoveObject(entry);
+  }
+}
+
+NS_IMETHODIMP_(void)
+SHistoryChild::RemoveDynEntries(int32_t aIndex, nsISHEntry* aEntry) {
+  SendRemoveDynEntries(aIndex, static_cast<SHEntryChild*>(aEntry));
+}
+
+NS_IMETHODIMP_(void)
+SHistoryChild::RemoveDynEntriesForBFCacheEntry(nsIBFCacheEntry* aBFEntry) {
+  MaybeNewPSHEntry entry;
+  int32_t index;
+  if (!SendFindEntryForBFCache(
+          static_cast<SHEntryChildShared*>(aBFEntry)->GetID(), true, &entry,
+          &index)) {
+    return;
+  }
+
+  RefPtr<SHEntryChild> shEntry = SHEntryChild::GetOrCreate(entry);
+  if (shEntry) {
+    RemoveDynEntries(index, shEntry);
+  }
+}
+
+NS_IMETHODIMP_(void)
+SHistoryChild::RemoveEntries(nsTArray<nsID>& aIDs, int32_t aStartIndex) {
+  bool didRemove = false;
+  if (SendRemoveEntries(aIDs, aStartIndex, &didRemove) && didRemove &&
+      mRootDocShell) {
+    mRootDocShell->DispatchLocationChangeEvent();
+  }
+}
+
+NS_IMETHODIMP
+SHistoryChild::Reload(uint32_t aReloadFlags) {
+  bool canNavigate = true;
+  nsAutoTObserverArray<nsWeakPtr, 2>::EndLimitedIterator iter(mListeners);
+  while (iter.HasMore()) {
+    nsCOMPtr<nsISHistoryListener> listener = do_QueryReferent(iter.GetNext());
+    if (listener) {
+      bool canceled = false;
+      listener->OnHistoryReload(&canceled);
+      if (canceled) {
+        canNavigate = false;
+      }
+    }
+  }
+  if (!canNavigate) {
+    return NS_OK;
+  }
+
+  LoadSHEntryResult loadResult;
+  if (!SendReload(aReloadFlags, &loadResult)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (loadResult.type() == LoadSHEntryResult::Tnsresult) {
+    return loadResult;
+  }
+
+  return LoadURI(loadResult);
+}
+
+nsresult SHistoryChild::LoadURI(LoadSHEntryData& aLoadData) {
+  nsCOMPtr<nsIDocShell> docShell = aLoadData.browsingContext()->GetDocShell();
+  NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+
+  RefPtr<SHEntryChild> entry = SHEntryChild::GetOrCreate(aLoadData.shEntry());
+
+  // FIXME Should this be sent through IPC?
+  aLoadData.loadState()->SetSHEntry(entry);
+  return docShell->LoadURI(aLoadData.loadState(), false);
+}
+
+}  // namespace dom
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/docshell/shistory/SHistoryChild.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_dom_SHistoryChild_h
+#define mozilla_dom_SHistoryChild_h
+
+#include "mozilla/dom/PSHistoryChild.h"
+#include "nsExpirationTracker.h"
+#include "nsISHistory.h"
+#include "nsWeakReference.h"
+
+class nsIDocShell;
+
+namespace mozilla {
+namespace dom {
+
+class LoadSHEntryData;
+class SHEntryChildShared;
+
+/**
+ * Session history actor for the child process.
+ */
+class SHistoryChild final : public PSHistoryChild,
+                            public nsISHistory,
+                            public nsSupportsWeakReference {
+  friend class PSHistoryChild;
+
+ public:
+  explicit SHistoryChild(BrowsingContext* aBrowsingContext);
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSISHISTORY
+
+ protected:
+  void ActorDestroy(ActorDestroyReason aWhy) override {
+    mIPCActorDeleted = true;
+  }
+
+ private:
+  // The timer based history tracker is used to evict bfcache on expiration.
+  class HistoryTracker final
+      : public nsExpirationTracker<SHEntryChildShared, 3> {
+   public:
+    explicit HistoryTracker(SHistoryChild* aSHistory, uint32_t aTimeout,
+                            nsIEventTarget* aEventTarget)
+        : nsExpirationTracker(1000 * aTimeout / 2, "HistoryTracker",
+                              aEventTarget),
+          mSHistory(aSHistory) {
+      MOZ_ASSERT(aSHistory);
+      mSHistory = aSHistory;
+    }
+
+   protected:
+    void NotifyExpired(SHEntryChildShared* aObj) override;
+
+   private:
+    // HistoryTracker is owned by SHistoryChild; it always outlives
+    // HistoryTracker so it's safe to use raw pointer here.
+    SHistoryChild* mSHistory;
+  };
+
+  ~SHistoryChild() = default;
+
+  nsresult LoadURI(LoadSHEntryData& aLoadData);
+
+  // Track all bfcache entries and evict on expiration.
+  mozilla::UniquePtr<HistoryTracker> mHistoryTracker;
+
+  // Session History listeners
+  nsAutoTObserverArray<nsWeakPtr, 2> mListeners;
+
+  WeakPtr<nsDocShell> mRootDocShell;
+
+  bool mIPCActorDeleted;
+};
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif /* mozilla_dom_SHistoryChild_h */
new file mode 100644
--- /dev/null
+++ b/docshell/shistory/SHistoryParent.cpp
@@ -0,0 +1,209 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "SHistoryParent.h"
+#include "mozilla/dom/SHEntryParent.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/ContentProcessManager.h"
+
+namespace mozilla {
+namespace dom {
+
+static void FillInLoadResult(PContentParent* aManager, nsresult aRv,
+                             const nsSHistory::LoadEntryResult& aLoadResult,
+                             LoadSHEntryResult* aResult) {
+  if (NS_SUCCEEDED(aRv)) {
+    MaybeNewPSHEntry entry;
+    static_cast<LegacySHEntry*>(aLoadResult.mLoadState->SHEntry())
+        ->GetOrCreateActor(aManager, entry);
+    *aResult = LoadSHEntryData(std::move(entry), aLoadResult.mBrowsingContext,
+                               aLoadResult.mLoadState);
+  } else {
+    *aResult = aRv;
+  }
+}
+
+SHistoryParent::SHistoryParent(CanonicalBrowsingContext* aContext)
+    : mContext(aContext), mHistory(new LegacySHistory(aContext, nsID())) {}
+
+SHistoryParent::~SHistoryParent() {}
+
+void SHistoryParent::ActorDestroy(ActorDestroyReason aWhy) {}
+
+bool SHistoryParent::RecvGetCount(int32_t* aCount) {
+  return NS_SUCCEEDED(mHistory->GetCount(aCount));
+}
+
+bool SHistoryParent::RecvGetIndex(int32_t* aIndex) {
+  return NS_SUCCEEDED(mHistory->GetIndex(aIndex));
+}
+
+bool SHistoryParent::RecvSetIndex(int32_t aIndex, nsresult* aResult) {
+  *aResult = mHistory->SetIndex(aIndex);
+  return true;
+}
+
+bool SHistoryParent::RecvGetRequestedIndex(int32_t* aIndex) {
+  return NS_SUCCEEDED(mHistory->GetRequestedIndex(aIndex));
+}
+
+bool SHistoryParent::RecvInternalSetRequestedIndex(int32_t aIndex) {
+  mHistory->InternalSetRequestedIndex(aIndex);
+  return true;
+}
+
+bool SHistoryParent::RecvGetEntryAtIndex(int32_t aIndex, nsresult* aResult,
+                                         MaybeNewPSHEntry* aEntry) {
+  nsCOMPtr<nsISHEntry> entry;
+  *aResult = mHistory->GetEntryAtIndex(aIndex, getter_AddRefs(entry));
+  SHEntryParent::GetOrCreate(Manager(), entry, *aEntry);
+  return true;
+}
+
+bool SHistoryParent::RecvPurgeHistory(int32_t aNumEntries, nsresult* aResult) {
+  *aResult = mHistory->PurgeHistory(aNumEntries);
+  return true;
+}
+
+bool SHistoryParent::RecvReloadCurrentEntry(LoadSHEntryResult* aLoadResult) {
+  nsSHistory::LoadEntryResult loadResult;
+  nsresult rv = mHistory->ReloadCurrentEntry(loadResult);
+  if (NS_SUCCEEDED(rv)) {
+    MaybeNewPSHEntry entry;
+    SHEntryParent::GetOrCreate(Manager(), loadResult.mLoadState->SHEntry(),
+                               entry);
+    *aLoadResult = LoadSHEntryData(
+        std::move(entry), loadResult.mBrowsingContext, loadResult.mLoadState);
+  } else {
+    *aLoadResult = rv;
+  }
+  return true;
+}
+
+bool SHistoryParent::RecvGotoIndex(int32_t aIndex,
+                                   LoadSHEntryResult* aLoadResult) {
+  nsSHistory::LoadEntryResult loadResult;
+  nsresult rv = mHistory->GotoIndex(aIndex, loadResult);
+  FillInLoadResult(Manager(), rv, loadResult, aLoadResult);
+  return true;
+}
+
+bool SHistoryParent::RecvGetIndexOfEntry(PSHEntryParent* aEntry,
+                                         int32_t* aIndex) {
+  MOZ_ASSERT(Manager() == aEntry->Manager());
+  *aIndex =
+      mHistory->GetIndexOfEntry(static_cast<SHEntryParent*>(aEntry)->mEntry);
+  return true;
+}
+
+bool SHistoryParent::RecvAddEntry(PSHEntryParent* aEntry, bool aPersist,
+                                  nsresult* aResult, int32_t* aEntriesPurged) {
+  MOZ_ASSERT(Manager() == aEntry->Manager());
+  *aResult = mHistory->AddEntry(static_cast<SHEntryParent*>(aEntry)->mEntry,
+                                aPersist, aEntriesPurged);
+  return true;
+}
+
+bool SHistoryParent::RecvUpdateIndex() {
+  return NS_SUCCEEDED(mHistory->UpdateIndex());
+}
+
+bool SHistoryParent::RecvReplaceEntry(int32_t aIndex, PSHEntryParent* aEntry,
+                                      nsresult* aResult) {
+  MOZ_ASSERT(Manager() == aEntry->Manager());
+  *aResult = mHistory->ReplaceEntry(
+      aIndex, static_cast<SHEntryParent*>(aEntry)->mEntry);
+  return true;
+}
+
+bool SHistoryParent::RecvNotifyOnHistoryReload(bool* aOk) {
+  return NS_SUCCEEDED(mHistory->NotifyOnHistoryReload(aOk));
+}
+
+bool SHistoryParent::RecvEvictOutOfRangeContentViewers(int32_t aIndex) {
+  // FIXME Implement this!
+  return true;
+}
+
+bool SHistoryParent::RecvEvictAllContentViewers() {
+  return NS_SUCCEEDED(mHistory->EvictAllContentViewers());
+}
+
+bool SHistoryParent::RecvRemoveDynEntries(int32_t aIndex,
+                                          PSHEntryParent* aEntry) {
+  MOZ_ASSERT(Manager() == aEntry->Manager());
+  mHistory->RemoveDynEntries(aIndex,
+                             static_cast<SHEntryParent*>(aEntry)->mEntry);
+  return true;
+}
+
+bool SHistoryParent::RecvRemoveEntries(nsTArray<nsID>&& aIds, int32_t aIndex,
+                                       bool* aDidRemove) {
+  mHistory->RemoveEntries(aIds, aIndex, aDidRemove);
+  return true;
+}
+
+bool SHistoryParent::RecvReload(const uint32_t& aReloadFlags,
+                                LoadSHEntryResult* aLoadResult) {
+  nsSHistory::LoadEntryResult loadResult;
+  nsresult rv = mHistory->Reload(aReloadFlags, loadResult);
+  FillInLoadResult(Manager(), rv, loadResult, aLoadResult);
+  return true;
+}
+
+bool SHistoryParent::RecvGetAllEntries(nsTArray<MaybeNewPSHEntry>* aEntries) {
+  nsTArray<nsCOMPtr<nsISHEntry>>& entries = mHistory->Entries();
+  uint32_t length = entries.Length();
+  aEntries->AppendElements(length);
+  for (uint32_t i = 0; i < length; ++i) {
+    SHEntryParent::GetOrCreate(Manager(), entries[i].get(),
+                               aEntries->ElementAt(i));
+  }
+  return true;
+}
+
+bool SHistoryParent::RecvFindEntryForBFCache(const uint64_t& aSharedID,
+                                             const bool& aIncludeCurrentEntry,
+                                             MaybeNewPSHEntry* aEntry,
+                                             int32_t* aIndex) {
+  int32_t currentIndex;
+  mHistory->GetIndex(&currentIndex);
+  int32_t startSafeIndex, endSafeIndex;
+  mHistory->WindowIndices(currentIndex, &startSafeIndex, &endSafeIndex);
+  for (int32_t i = startSafeIndex; i <= endSafeIndex; ++i) {
+    nsCOMPtr<nsISHEntry> entry;
+    nsresult rv = mHistory->GetEntryAtIndex(i, getter_AddRefs(entry));
+    NS_ENSURE_SUCCESS(rv, false);
+
+    if (static_cast<LegacySHEntry*>(entry.get())->GetSharedStateID() ==
+        aSharedID) {
+      if (!aIncludeCurrentEntry && i == currentIndex) {
+        *aEntry = (PSHEntryParent*)nullptr;
+      } else {
+        SHEntryParent::GetOrCreate(Manager(), entry, *aEntry);
+      }
+
+      return true;
+    }
+  }
+  *aEntry = (PSHEntryParent*)nullptr;
+  return true;
+}
+
+bool SHistoryParent::RecvEvict(nsTArray<PSHEntryParent*>&& aEntries) {
+  for (PSHEntryParent* entry : aEntries) {
+    int32_t index =
+        mHistory->GetIndexOfEntry(static_cast<SHEntryParent*>(entry)->mEntry);
+    if (index != -1) {
+      mHistory->RemoveDynEntries(index,
+                                 static_cast<SHEntryParent*>(entry)->mEntry);
+    }
+  }
+  return true;
+}
+
+}  // namespace dom
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/docshell/shistory/SHistoryParent.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_dom_SHistoryParent_h
+#define mozilla_dom_SHistoryParent_h
+
+#include "mozilla/dom/PSHistoryParent.h"
+#include "mozilla/RefPtr.h"
+#include "nsSHistory.h"
+
+namespace mozilla {
+namespace dom {
+
+class CanonicalBrowsingContext;
+class SHistoryParent;
+
+/**
+ * Session history implementation based on the legacy implementation that used
+ * to live in the child process. Ideally this wouldn't implement nsISHistory
+ * (it should only ever be accessed by SHistoryParent).
+ */
+class LegacySHistory final : public nsSHistory {
+ private:
+  virtual ~LegacySHistory() {}
+
+ public:
+  LegacySHistory(mozilla::dom::BrowsingContext* aRootBC,
+                 const nsID& aDocShellID)
+      : nsSHistory(aRootBC, aDocShellID) {
+    mIsRemote = true;
+  }
+};
+
+/**
+ * Session history actor for the parent process. Forwards to the legacy
+ * implementation that used to live in the child process (see LegacySHistory).
+ */
+class SHistoryParent final : public PSHistoryParent {
+  friend class PSHistoryParent;
+  friend class SHEntryParent;
+
+ public:
+  explicit SHistoryParent(CanonicalBrowsingContext* aContext);
+  virtual ~SHistoryParent();
+
+ protected:
+  void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ private:
+  bool RecvGetCount(int32_t* aCount);
+  bool RecvGetIndex(int32_t* aIndex);
+  bool RecvSetIndex(int32_t aIndex, nsresult* aResult);
+  bool RecvGetRequestedIndex(int32_t* aIndex);
+  bool RecvInternalSetRequestedIndex(int32_t aIndex);
+  bool RecvGetEntryAtIndex(int32_t aIndex, nsresult* aResult,
+                           MaybeNewPSHEntry* aEntry);
+  bool RecvPurgeHistory(int32_t aNumEntries, nsresult* aResult);
+  bool RecvReloadCurrentEntry(LoadSHEntryResult* aLoadResult);
+  bool RecvGotoIndex(int32_t aIndex, LoadSHEntryResult* aLoadResult);
+  bool RecvGetIndexOfEntry(PSHEntryParent* aEntry, int32_t* aIndex);
+  bool RecvAddEntry(PSHEntryParent* aEntry, bool aPersist, nsresult* aResult,
+                    int32_t* aEntriesPurged);
+  bool RecvUpdateIndex();
+  bool RecvReplaceEntry(int32_t aIndex, PSHEntryParent* aEntry,
+                        nsresult* aResult);
+  bool RecvNotifyOnHistoryReload(bool* aOk);
+  bool RecvEvictOutOfRangeContentViewers(int32_t aIndex);
+  bool RecvEvictAllContentViewers();
+  bool RecvRemoveDynEntries(int32_t aIndex, PSHEntryParent* aEntry);
+  bool RecvRemoveEntries(nsTArray<nsID>&& ids, int32_t aIndex,
+                         bool* aDidRemove);
+  bool RecvReload(const uint32_t& aReloadFlags, LoadSHEntryResult* aLoadResult);
+  bool RecvGetAllEntries(nsTArray<MaybeNewPSHEntry>* aEntries);
+  bool RecvFindEntryForBFCache(const uint64_t& aSharedID,
+                               const bool& aIncludeCurrentEntry,
+                               MaybeNewPSHEntry* aEntry, int32_t* aIndex);
+  bool RecvEvict(nsTArray<PSHEntryParent*>&& aEntries);
+
+  RefPtr<CanonicalBrowsingContext> mContext;
+  RefPtr<LegacySHistory> mHistory;
+};
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif /* mozilla_dom_SHistoryParent_h */
--- a/docshell/shistory/moz.build
+++ b/docshell/shistory/moz.build
@@ -8,31 +8,47 @@ XPIDL_SOURCES += [
     'nsIBFCacheEntry.idl',
     'nsISHEntry.idl',
     'nsISHistory.idl',
     'nsISHistoryListener.idl',
 ]
 
 XPIDL_MODULE = 'shistory'
 
+IPDL_SOURCES += [
+    'NewPSHEntry.ipdlh',
+    'PSHEntry.ipdl',
+    'PSHistory.ipdl',
+]
+
 EXPORTS += [
+    'nsSHEntry.h',
     'nsSHEntryShared.h',
+    'nsSHistory.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'ChildSHistory.h',
     'ParentSHistory.h',
+    'SHEntryChild.h',
+    'SHEntryParent.h',
+    'SHistoryChild.h',
+    'SHistoryParent.h',
 ]
 
 UNIFIED_SOURCES += [
     'ChildSHistory.cpp',
     'nsSHEntry.cpp',
     'nsSHEntryShared.cpp',
     'nsSHistory.cpp',
     'ParentSHistory.cpp',
+    'SHEntryChild.cpp',
+    'SHEntryParent.cpp',
+    'SHistoryChild.cpp',
+    'SHistoryParent.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/docshell/base',
     '/dom/base',
 ]
 
 FINAL_LIBRARY = 'xul'
--- a/docshell/shistory/nsIBFCacheEntry.idl
+++ b/docshell/shistory/nsIBFCacheEntry.idl
@@ -3,16 +3,16 @@
  * 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"
 
 /**
  * This interface lets you evict a document from the back/forward cache.
  */
-[scriptable, uuid(a576060e-c7df-4d81-aa8c-5b52bd6ad25d)]
+[scriptable, builtinclass, uuid(a576060e-c7df-4d81-aa8c-5b52bd6ad25d)]
 interface nsIBFCacheEntry : nsISupports
 {
   void RemoveFromBFCacheSync();
   void RemoveFromBFCacheAsync();
 
-  readonly attribute unsigned long long ID;
+  [infallible] readonly attribute unsigned long long ID;
 };
--- a/docshell/shistory/nsISHEntry.idl
+++ b/docshell/shistory/nsISHEntry.idl
@@ -22,21 +22,27 @@ interface nsIStructuredCloneContainer;
 interface nsIBFCacheEntry;
 interface nsIPrincipal;
 interface nsISHistory;
 interface nsIReferrerInfo;
 
 %{C++
 #include "nsRect.h"
 class nsDocShellEditorData;
-class nsSHEntryShared;
+
+namespace mozilla {
+namespace dom {
+
+class SHEntrySharedParentState;
+
+}
+}
 %}
 [ref] native nsIntRect(nsIntRect);
 [ptr] native nsDocShellEditorDataPtr(nsDocShellEditorData);
-[ptr] native nsSHEntryShared(nsSHEntryShared);
 
 [builtinclass, scriptable, uuid(0dad26b8-a259-42c7-93f1-2fa7fc076e45)]
 interface nsISHEntry : nsISupports
 {
     /**
      * The URI of the current entry.
      */
     [infallible] attribute nsIURI URI;
@@ -167,17 +173,17 @@ interface nsISHEntry : nsISupports
      * serialized using structured clone.
      **/
     [infallible] attribute nsIStructuredCloneContainer stateData;
 
     /**
      * The history ID of the docshell.
      */
     // Would be [infallible], but we don't support that property for nsIDPtr.
-    attribute nsIDPtr docshellID;
+    attribute nsIDRef docshellID;
 
     [infallible] readonly attribute nsIBFCacheEntry BFCacheEntry;
 
     /**
      * True if this SHEntry corresponds to a document created by a srcdoc
      * iframe. Set when a value is assigned to srcdocData.
      */
     [infallible] readonly attribute boolean isSrcdocEntry;
@@ -197,17 +203,17 @@ interface nsISHEntry : nsISupports
      * for example with view-source.
      */
     [infallible] attribute nsIURI baseURI;
 
     /**
      * Sets/gets the current scroll restoration state,
      * if true == "manual", false == "auto".
      */
-    [infallible] attribute boolean scrollRestorationIsManual;
+    attribute boolean scrollRestorationIsManual;
 
     /**
      * Flag to indicate that the history entry was originally loaded in the
      * current process. This flag does not survive a browser process switch.
      */
     [infallible] readonly attribute boolean loadedInThisProcess;
 
     /**
@@ -282,18 +288,18 @@ interface nsISHEntry : nsISupports
      * Initialises `layoutHistoryState` if it doesn't already exist
      * and returns a reference to it.
      */
     nsILayoutHistoryState initLayoutHistoryState();
 
     /** Additional ways to create an entry */
     [noscript] void create(in nsIURI URI, in AString title,
                            in nsIInputStream inputStream,
-                           in nsILayoutHistoryState layoutHistoryState,
-                           in unsigned long cacheKey, in ACString contentType,
+                           in unsigned long cacheKey,
+                           in ACString contentType,
                            in nsIPrincipal triggeringPrincipal,
                            in nsIPrincipal principalToInherit,
                            in nsIPrincipal storagePrincipalToInherit,
                            in nsIContentSecurityPolicy aCsp,
                            in nsIDRef docshellID,
                            in boolean dynamicCreation);
 
     nsISHEntry clone();
@@ -323,21 +329,16 @@ interface nsISHEntry : nsISupports
 
     /**
      * Returns true if any of the child entries returns true
      * when isDynamicallyAdded is called on it.
      */
     boolean hasDynamicallyAddedChild();
 
     /**
-     * Helper method for accessing `docshellID` from C++
-     */
-    [noscript, notxpcom] nsID DocshellID();
-
-    /**
      * Does this SHEntry point to the given BFCache entry? If so, evicting
      * the BFCache entry will evict the SHEntry, since the two entries
      * correspond to the same document.
      */
     [noscript, notxpcom] boolean hasBFCacheEntry(in nsIBFCacheEntry aEntry);
 
     /**
      * Adopt aEntry's BFCacheEntry, so now both this and aEntry point to
@@ -366,26 +367,16 @@ interface nsISHEntry : nsISupports
      *
      * shEntry.loadType = 4;
      *
      * in js, but easier to maintain and less opaque.
      */
     void setLoadTypeAsHistory();
 
     /**
-     * Some state, particularly that related to the back/forward cache, is
-     * shared between SHEntries which correspond to the same document.  This
-     * method gets a pointer to that shared state.
-     *
-     * This shared state is the SHEntry's BFCacheEntry.  So
-     * hasBFCacheEntry(getSharedState()) is guaranteed to return true.
-     */
-    [noscript, notxpcom] nsSHEntryShared getSharedState();
-
-    /**
      * Add a new child SHEntry. If offset is -1 adds to the end of the list.
      */
     void AddChild(in nsISHEntry aChild, in long aOffset,
                   [optional,default(false)] in bool aUseRemoteSubframes);
 
     /**
      * Remove a child SHEntry.
      */
--- a/docshell/shistory/nsISHistory.idl
+++ b/docshell/shistory/nsISHistory.idl
@@ -1,17 +1,16 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 nsIBFCacheEntry;
-interface nsIDocShell;
 interface nsISHEntry;
 interface nsISHistoryListener;
 interface nsIURI;
 
 %{C++
 #include "nsTArrayForwardDeclare.h"
 %}
 
@@ -243,9 +242,12 @@ interface nsISHistory: nsISupports
   void RemoveDynEntriesForBFCacheEntry(in nsIBFCacheEntry aEntry);
 
   /**
    * Removes entries from the history if their docshellID is in
    * aIDs array.
    */
   [noscript, notxpcom]
   void RemoveEntries(in nsDocshellIDArray aIDs, in long aStartIndex);
+
+  [noscript]
+  void Reload(in unsigned long aReloadFlags);
 };
--- a/docshell/shistory/nsSHEntry.cpp
+++ b/docshell/shistory/nsSHEntry.cpp
@@ -24,18 +24,18 @@
 #include "nsIReferrerInfo.h"
 
 extern mozilla::LazyLogModule gPageCacheLog;
 
 namespace dom = mozilla::dom;
 
 static uint32_t gEntryID = 0;
 
-nsSHEntry::nsSHEntry()
-    : mShared(new nsSHEntryShared()),
+nsSHEntry::nsSHEntry(dom::SHEntrySharedParentState* aState)
+    : mShared(aState),
       mLoadType(0),
       mID(gEntryID++),
       mScrollPositionX(0),
       mScrollPositionY(0),
       mParent(nullptr),
       mLoadReplace(false),
       mURIWasModified(false),
       mIsSrcdocEntry(false),
@@ -167,23 +167,27 @@ nsSHEntry::GetReferrerInfo(nsIReferrerIn
 NS_IMETHODIMP
 nsSHEntry::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
   mReferrerInfo = aReferrerInfo;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetContentViewer(nsIContentViewer* aViewer) {
-  return mShared->SetContentViewer(aViewer);
+  MOZ_CRASH(
+      "Classes inheriting from nsSHEntry should implement this. "
+      "Bug 1546344 will clean this up.");
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetContentViewer(nsIContentViewer** aResult) {
-  *aResult = mShared->mContentViewer;
-  NS_IF_ADDREF(*aResult);
+  MOZ_CRASH(
+      "Classes inheriting from nsSHEntry should implement this. "
+      "Bug 1546344 will clean this up.");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetSticky(bool aSticky) {
   mShared->mSticky = aSticky;
   return NS_OK;
 }
@@ -225,42 +229,35 @@ nsSHEntry::GetPostData(nsIInputStream** 
 NS_IMETHODIMP
 nsSHEntry::SetPostData(nsIInputStream* aPostData) {
   mPostData = aPostData;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetLayoutHistoryState(nsILayoutHistoryState** aResult) {
-  *aResult = mShared->mLayoutHistoryState;
-  NS_IF_ADDREF(*aResult);
+  MOZ_CRASH(
+      "Classes inheriting from nsSHEntry should implement this. "
+      "Bug 1546344 will clean this up.");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetLayoutHistoryState(nsILayoutHistoryState* aState) {
-  mShared->mLayoutHistoryState = aState;
-  if (mShared->mLayoutHistoryState) {
-    mShared->mLayoutHistoryState->SetScrollPositionOnly(
-        !mShared->mSaveLayoutState);
-  }
-
+  MOZ_CRASH(
+      "Classes inheriting from nsSHEntry should implement this. "
+      "Bug 1546344 will clean this up.");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::InitLayoutHistoryState(nsILayoutHistoryState** aState) {
-  if (!mShared->mLayoutHistoryState) {
-    nsCOMPtr<nsILayoutHistoryState> historyState;
-    historyState = NS_NewLayoutHistoryState();
-    SetLayoutHistoryState(historyState);
-  }
-
-  nsCOMPtr<nsILayoutHistoryState> state = GetLayoutHistoryState();
-  state.forget(aState);
+  MOZ_CRASH(
+      "Classes inheriting from nsSHEntry should implement this. "
+      "Bug 1546344 will clean this up.");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetLoadType(uint32_t* aResult) {
   *aResult = mLoadType;
   return NS_OK;
 }
@@ -278,18 +275,16 @@ nsSHEntry::GetID(uint32_t* aResult) {
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetID(uint32_t aID) {
   mID = aID;
   return NS_OK;
 }
 
-nsSHEntryShared* nsSHEntry::GetSharedState() { return mShared; }
-
 NS_IMETHODIMP
 nsSHEntry::GetIsSubFrame(bool* aFlag) {
   *aFlag = mShared->mIsFrameNavigation;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetIsSubFrame(bool aFlag) {
@@ -306,27 +301,27 @@ nsSHEntry::GetCacheKey(uint32_t* aResult
 NS_IMETHODIMP
 nsSHEntry::SetCacheKey(uint32_t aCacheKey) {
   mShared->mCacheKey = aCacheKey;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetSaveLayoutStateFlag(bool* aFlag) {
-  *aFlag = mShared->mSaveLayoutState;
+  MOZ_CRASH(
+      "Classes inheriting from nsSHEntry should implement this. "
+      "Bug 1546344 will clean this up.");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetSaveLayoutStateFlag(bool aFlag) {
-  mShared->mSaveLayoutState = aFlag;
-  if (mShared->mLayoutHistoryState) {
-    mShared->mLayoutHistoryState->SetScrollPositionOnly(!aFlag);
-  }
-
+  MOZ_CRASH(
+      "Classes inheriting from nsSHEntry should implement this. "
+      "Bug 1546344 will clean this up.");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetExpirationStatus(bool* aFlag) {
   *aFlag = mShared->mExpired;
   return NS_OK;
 }
@@ -346,19 +341,18 @@ nsSHEntry::GetContentType(nsACString& aC
 NS_IMETHODIMP
 nsSHEntry::SetContentType(const nsACString& aContentType) {
   mShared->mContentType = aContentType;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::Create(nsIURI* aURI, const nsAString& aTitle,
-                  nsIInputStream* aInputStream,
-                  nsILayoutHistoryState* aLayoutHistoryState,
-                  uint32_t aCacheKey, const nsACString& aContentType,
+                  nsIInputStream* aInputStream, uint32_t aCacheKey,
+                  const nsACString& aContentType,
                   nsIPrincipal* aTriggeringPrincipal,
                   nsIPrincipal* aPrincipalToInherit,
                   nsIPrincipal* aStoragePrincipalToInherit,
                   nsIContentSecurityPolicy* aCsp, const nsID& aDocShellID,
                   bool aDynamicCreation) {
   MOZ_ASSERT(
       aTriggeringPrincipal,
       "need a valid triggeringPrincipal to create a session history entry");
@@ -379,35 +373,32 @@ nsSHEntry::Create(nsIURI* aURI, const ns
   mShared->mDocShellID = aDocShellID;
   mShared->mDynamicallyCreated = aDynamicCreation;
 
   // By default all entries are set false for subframe flag.
   // nsDocShell::CloneAndReplace() which creates entries for
   // all subframe navigations, sets the flag to true.
   mShared->mIsFrameNavigation = false;
 
-  // By default we save LayoutHistoryState
-  mShared->mSaveLayoutState = true;
-  mShared->mLayoutHistoryState = aLayoutHistoryState;
-
   // By default the page is not expired
   mShared->mExpired = false;
 
   mIsSrcdocEntry = false;
   mSrcdocData = VoidString();
 
   mLoadedInThisProcess = true;
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::Clone(nsISHEntry** aResult) {
-  *aResult = new nsSHEntry(*this);
-  NS_ADDREF(*aResult);
+  MOZ_CRASH(
+      "Classes inheriting from nsSHEntry should implement this. "
+      "Bug 1546344 will clean this up.");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetParent(nsISHEntry** aResult) {
   *aResult = mParent;
   NS_IF_ADDREF(*aResult);
   return NS_OK;
@@ -421,23 +412,27 @@ nsSHEntry::SetParent(nsISHEntry* aParent
    * XXX this method should not be scriptable if this is the case!!
    */
   mParent = aParent;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetWindowState(nsISupports* aState) {
-  mShared->mWindowState = aState;
+  MOZ_CRASH(
+      "Classes inheriting from nsSHEntry should implement this. "
+      "Bug 1546344 will clean this up.");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetWindowState(nsISupports** aState) {
-  NS_IF_ADDREF(*aState = mShared->mWindowState);
+  MOZ_CRASH(
+      "Classes inheriting from nsSHEntry should implement this. "
+      "Bug 1546344 will clean this up.");
   return NS_OK;
 }
 
 NS_IMETHODIMP_(void)
 nsSHEntry::SetViewerBounds(const nsIntRect& aBounds) {
   mShared->mViewerBounds = aBounds;
 }
 
@@ -494,44 +489,52 @@ nsSHEntry::GetCsp(nsIContentSecurityPoli
 NS_IMETHODIMP
 nsSHEntry::SetCsp(nsIContentSecurityPolicy* aCsp) {
   mShared->mCsp = aCsp;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetBFCacheEntry(nsIBFCacheEntry** aEntry) {
-  NS_IF_ADDREF(*aEntry = mShared);
+  MOZ_CRASH(
+      "Classes inheriting from nsSHEntry should implement this. "
+      "Bug 1546344 will clean this up.");
   return NS_OK;
 }
 
 bool nsSHEntry::HasBFCacheEntry(nsIBFCacheEntry* aEntry) {
-  return static_cast<nsIBFCacheEntry*>(mShared) == aEntry;
+  MOZ_CRASH(
+      "Classes inheriting from nsSHEntry should implement this. "
+      "Bug 1546344 will clean this up.");
+  return false;
 }
 
 NS_IMETHODIMP
 nsSHEntry::AdoptBFCacheEntry(nsISHEntry* aEntry) {
-  nsSHEntryShared* shared = aEntry->GetSharedState();
+  dom::SHEntrySharedParentState* shared =
+      static_cast<nsSHEntry*>(aEntry)->mShared;
   NS_ENSURE_STATE(shared);
 
   mShared = shared;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SharesDocumentWith(nsISHEntry* aEntry, bool* aOut) {
   NS_ENSURE_ARG_POINTER(aOut);
 
-  *aOut = mShared == aEntry->GetSharedState();
+  *aOut = mShared == static_cast<nsSHEntry*>(aEntry)->mShared;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::AbandonBFCacheEntry() {
-  mShared = nsSHEntryShared::Duplicate(mShared);
+  MOZ_CRASH(
+      "Classes inheriting from nsSHEntry should implement this. "
+      "Bug 1546344 will clean this up.");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetIsSrcdocEntry(bool* aIsSrcdocEntry) {
   *aIsSrcdocEntry = mIsSrcdocEntry;
   return NS_OK;
 }
@@ -729,72 +732,98 @@ nsSHEntry::GetChildAt(int32_t aIndex, ns
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::ReplaceChild(nsISHEntry* aNewEntry) {
   NS_ENSURE_STATE(aNewEntry);
 
-  nsID docshellID = aNewEntry->DocshellID();
+  nsID docshellID;
+  aNewEntry->GetDocshellID(docshellID);
 
   for (int32_t i = 0; i < mChildren.Count(); ++i) {
-    if (mChildren[i] && docshellID == mChildren[i]->DocshellID()) {
-      mChildren[i]->SetParent(nullptr);
-      mChildren.ReplaceObjectAt(aNewEntry, i);
-      return aNewEntry->SetParent(this);
+    if (mChildren[i]) {
+      nsID childDocshellID;
+      nsresult rv = mChildren[i]->GetDocshellID(childDocshellID);
+      NS_ENSURE_SUCCESS(rv, rv);
+      if (docshellID == childDocshellID) {
+        mChildren[i]->SetParent(nullptr);
+        mChildren.ReplaceObjectAt(aNewEntry, i);
+        return aNewEntry->SetParent(this);
+      }
     }
   }
   return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP_(void)
 nsSHEntry::AddChildShell(nsIDocShellTreeItem* aShell) {
-  MOZ_ASSERT(aShell, "Null child shell added to history entry");
-  mShared->mChildShells.AppendObject(aShell);
+  MOZ_CRASH(
+      "Classes inheriting from nsSHEntry should implement this. "
+      "Bug 1546344 will clean this up.");
 }
 
 NS_IMETHODIMP
 nsSHEntry::ChildShellAt(int32_t aIndex, nsIDocShellTreeItem** aShell) {
-  NS_IF_ADDREF(*aShell = mShared->mChildShells.SafeObjectAt(aIndex));
+  MOZ_CRASH(
+      "Classes inheriting from nsSHEntry should implement this. "
+      "Bug 1546344 will clean this up.");
   return NS_OK;
 }
 
 NS_IMETHODIMP_(void)
-nsSHEntry::ClearChildShells() { mShared->mChildShells.Clear(); }
+nsSHEntry::ClearChildShells() {
+  MOZ_CRASH(
+      "Classes inheriting from nsSHEntry should implement this. "
+      "Bug 1546344 will clean this up.");
+}
 
 NS_IMETHODIMP
 nsSHEntry::GetRefreshURIList(nsIMutableArray** aList) {
-  NS_IF_ADDREF(*aList = mShared->mRefreshURIList);
+  MOZ_CRASH(
+      "Classes inheriting from nsSHEntry should implement this. "
+      "Bug 1546344 will clean this up.");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetRefreshURIList(nsIMutableArray* aList) {
-  mShared->mRefreshURIList = aList;
+  MOZ_CRASH(
+      "Classes inheriting from nsSHEntry should implement this. "
+      "Bug 1546344 will clean this up.");
   return NS_OK;
 }
 
 NS_IMETHODIMP_(void)
-nsSHEntry::SyncPresentationState() { mShared->SyncPresentationState(); }
+nsSHEntry::SyncPresentationState() {
+  MOZ_CRASH(
+      "Classes inheriting from nsSHEntry should implement this. "
+      "Bug 1546344 will clean this up.");
+}
 
 nsDocShellEditorData* nsSHEntry::ForgetEditorData() {
-  // XXX jlebar Check how this is used.
-  return mShared->mEditorData.forget();
+  MOZ_CRASH(
+      "Classes inheriting from nsSHEntry should implement this. "
+      "Bug 1546344 will clean this up.");
+  return nullptr;
 }
 
 void nsSHEntry::SetEditorData(nsDocShellEditorData* aData) {
-  NS_ASSERTION(!(aData && mShared->mEditorData),
-               "We're going to overwrite an owning ref!");
-  if (mShared->mEditorData != aData) {
-    mShared->mEditorData = aData;
-  }
+  MOZ_CRASH(
+      "Classes inheriting from nsSHEntry should implement this. "
+      "Bug 1546344 will clean this up.");
 }
 
-bool nsSHEntry::HasDetachedEditor() { return mShared->mEditorData != nullptr; }
+bool nsSHEntry::HasDetachedEditor() {
+  MOZ_CRASH(
+      "Classes inheriting from nsSHEntry should implement this. "
+      "Bug 1546344 will clean this up.");
+  return false;
+}
 
 NS_IMETHODIMP
 nsSHEntry::GetStateData(nsIStructuredCloneContainer** aContainer) {
   NS_IF_ADDREF(*aContainer = mStateData);
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -817,26 +846,24 @@ nsSHEntry::HasDynamicallyAddedChild(bool
         break;
       }
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsSHEntry::GetDocshellID(nsID** aID) {
-  *aID = mShared->mDocShellID.Clone();
+nsSHEntry::GetDocshellID(nsID& aID) {
+  aID = mShared->mDocShellID;
   return NS_OK;
 }
 
-const nsID nsSHEntry::DocshellID() { return mShared->mDocShellID; }
-
 NS_IMETHODIMP
-nsSHEntry::SetDocshellID(const nsID* aID) {
-  mShared->mDocShellID = *aID;
+nsSHEntry::SetDocshellID(const nsID& aID) {
+  mShared->mDocShellID = aID;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetLastTouched(uint32_t* aLastTouched) {
   *aLastTouched = mShared->mLastTouched;
   return NS_OK;
 }
@@ -876,8 +903,188 @@ nsSHEntry::GetPersist(bool* aPersist) {
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::SetPersist(bool aPersist) {
   mPersist = aPersist;
   return NS_OK;
 }
+
+void nsSHEntry::EvictContentViewer() {
+  nsCOMPtr<nsIContentViewer> viewer = GetContentViewer();
+  if (viewer) {
+    // Drop the presentation state before destroying the viewer, so that
+    // document teardown is able to correctly persist the state.
+    SetContentViewer(nullptr);
+    SyncPresentationState();
+    viewer->Destroy();
+  }
+}
+
+nsLegacySHEntry::nsLegacySHEntry(uint64_t aID)
+    : nsSHEntry(new nsSHEntryShared(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,
+                        nsIContentSecurityPolicy* aCsp, const nsID& aDocShellID,
+                        bool aDynamicCreation) {
+  GetState()->mLayoutHistoryState = nullptr;
+
+  // By default we save LayoutHistoryState
+  GetState()->mSaveLayoutState = true;
+
+  return nsSHEntry::Create(aURI, aTitle, aInputStream, aCacheKey, aContentType,
+                           aTriggeringPrincipal, aPrincipalToInherit, aCsp,
+                           aDocShellID, aDynamicCreation);
+}
+
+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;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLegacySHEntry::SetSaveLayoutStateFlag(bool aFlag) {
+  GetState()->mSaveLayoutState = aFlag;
+  if (GetState()->mLayoutHistoryState) {
+    GetState()->mLayoutHistoryState->SetScrollPositionOnly(!aFlag);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLegacySHEntry::SetWindowState(nsISupports* aState) {
+  GetState()->mWindowState = aState;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLegacySHEntry::GetWindowState(nsISupports** aState) {
+  NS_IF_ADDREF(*aState = GetState()->mWindowState);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLegacySHEntry::GetRefreshURIList(nsIMutableArray** aList) {
+  NS_IF_ADDREF(*aList = GetState()->mRefreshURIList);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLegacySHEntry::SetRefreshURIList(nsIMutableArray* aList) {
+  GetState()->mRefreshURIList = aList;
+  return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsLegacySHEntry::AddChildShell(nsIDocShellTreeItem* aShell) {
+  MOZ_ASSERT(aShell, "Null child shell added to history entry");
+  GetState()->mChildShells.AppendObject(aShell);
+}
+
+NS_IMETHODIMP
+nsLegacySHEntry::ChildShellAt(int32_t aIndex, nsIDocShellTreeItem** aShell) {
+  NS_IF_ADDREF(*aShell = GetState()->mChildShells.SafeObjectAt(aIndex));
+  return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsLegacySHEntry::ClearChildShells() { GetState()->mChildShells.Clear(); }
+
+NS_IMETHODIMP_(void)
+nsLegacySHEntry::SyncPresentationState() {
+  GetState()->SyncPresentationState();
+}
+
+nsDocShellEditorData* nsLegacySHEntry::ForgetEditorData() {
+  // XXX jlebar Check how this is used.
+  return GetState()->mEditorData.forget();
+}
+
+void nsLegacySHEntry::SetEditorData(nsDocShellEditorData* aData) {
+  NS_ASSERTION(!(aData && GetState()->mEditorData),
+               "We're going to overwrite an owning ref!");
+  if (GetState()->mEditorData != aData) {
+    GetState()->mEditorData = aData;
+  }
+}
+
+bool nsLegacySHEntry::HasDetachedEditor() {
+  return GetState()->mEditorData != nullptr;
+}
+
+NS_IMETHODIMP
+nsLegacySHEntry::GetBFCacheEntry(nsIBFCacheEntry** aEntry) {
+  NS_IF_ADDREF(*aEntry = GetState());
+  return NS_OK;
+}
+
+bool nsLegacySHEntry::HasBFCacheEntry(nsIBFCacheEntry* aEntry) {
+  return static_cast<nsIBFCacheEntry*>(GetState()) == aEntry;
+}
+
+NS_IMETHODIMP
+nsLegacySHEntry::AbandonBFCacheEntry() {
+  mShared = nsSHEntryShared::Duplicate(
+      GetState(), mozilla::dom::SHEntryChildShared::CreateSharedID());
+  return NS_OK;
+}
+
+nsSHEntryShared* nsLegacySHEntry::GetState() {
+  return static_cast<nsSHEntryShared*>(mShared.get());
+}
--- a/docshell/shistory/nsSHEntry.h
+++ b/docshell/shistory/nsSHEntry.h
@@ -9,40 +9,48 @@
 
 #include "nsCOMArray.h"
 #include "nsCOMPtr.h"
 #include "nsISHEntry.h"
 #include "nsString.h"
 
 #include "mozilla/Attributes.h"
 
+namespace mozilla {
+namespace dom {
+
+class SHEntrySharedChildState;
+class SHEntrySharedParentState;
+
+}  // namespace dom
+}  // namespace mozilla
+
 class nsSHEntryShared;
 class nsIInputStream;
 class nsIURI;
 class nsIReferrerInfo;
 
-class nsSHEntry final : public nsISHEntry {
+class nsSHEntry : public nsISHEntry {
  public:
-  nsSHEntry();
-  nsSHEntry(const nsSHEntry& aOther);
-
   NS_DECL_ISUPPORTS
   NS_DECL_NSISHENTRY
 
-  void DropPresentationState();
+  virtual void EvictContentViewer();
 
   static nsresult Startup();
   static void Shutdown();
 
- private:
-  ~nsSHEntry();
+ protected:
+  explicit nsSHEntry(mozilla::dom::SHEntrySharedParentState* aState);
+  explicit nsSHEntry(const nsSHEntry& aOther);
+  virtual ~nsSHEntry();
 
   // We share the state in here with other SHEntries which correspond to the
   // same document.
-  RefPtr<nsSHEntryShared> mShared;
+  RefPtr<mozilla::dom::SHEntrySharedParentState> mShared;
 
   // See nsSHEntry.idl for comments on these members.
   nsCOMPtr<nsIURI> mURI;
   nsCOMPtr<nsIURI> mOriginalURI;
   nsCOMPtr<nsIURI> mResultPrincipalURI;
   nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
   nsString mTitle;
   nsCOMPtr<nsIInputStream> mPostData;
@@ -58,9 +66,58 @@ class nsSHEntry final : public nsISHEntr
   bool mLoadReplace;
   bool mURIWasModified;
   bool mIsSrcdocEntry;
   bool mScrollRestorationIsManual;
   bool mLoadedInThisProcess;
   bool mPersist;
 };
 
+/**
+ * Session history entry class used for implementing session history for
+ * docshells in the parent process (a different solution would be to use the
+ * IPC actors for that too, with both parent and child actor created in the
+ * parent process).
+ */
+class nsLegacySHEntry final : public nsSHEntry {
+ public:
+  explicit nsLegacySHEntry(uint64_t aID);
+  explicit nsLegacySHEntry(const nsLegacySHEntry& aOther) : nsSHEntry(aOther) {}
+
+  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 GetBFCacheEntry(nsIBFCacheEntry** aEntry) 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,
+                    nsIContentSecurityPolicy* aCsp, const nsID& aDocshellID,
+                    bool aDynamicCreation) override;
+  NS_IMETHOD Clone(nsISHEntry** aResult) override;
+  NS_IMETHOD_(nsDocShellEditorData*) ForgetEditorData(void) override;
+  NS_IMETHOD_(void) SetEditorData(nsDocShellEditorData* aData) override;
+  NS_IMETHOD_(bool) HasDetachedEditor() override;
+  NS_IMETHOD_(bool) HasBFCacheEntry(nsIBFCacheEntry* aEntry) override;
+  NS_IMETHOD AbandonBFCacheEntry() override;
+
+ private:
+  nsSHEntryShared* GetState();
+};
+
 #endif /* nsSHEntry_h */
--- a/docshell/shistory/nsSHEntryShared.cpp
+++ b/docshell/shistory/nsSHEntryShared.cpp
@@ -17,36 +17,58 @@
 #include "nsIWebNavigation.h"
 #include "nsThreadUtils.h"
 
 #include "mozilla/Attributes.h"
 #include "mozilla/Preferences.h"
 
 namespace dom = mozilla::dom;
 
-namespace {
-
-uint64_t gSHEntrySharedID = 0;
-
-}  // namespace
-
 void nsSHEntryShared::Shutdown() {}
 
-nsSHEntryShared::nsSHEntryShared()
+dom::SHEntrySharedParentState::SHEntrySharedParentState(uint64_t aID)
     : mDocShellID({0}),
+      mViewerBounds(0, 0, 0, 0),
       mCacheKey(0),
       mLastTouched(0),
-      mID(gSHEntrySharedID++),
-      mViewerBounds(0, 0, 0, 0),
+      mID(aID),
       mIsFrameNavigation(false),
-      mSaveLayoutState(true),
       mSticky(true),
       mDynamicallyCreated(false),
       mExpired(false) {}
 
+dom::SHEntrySharedParentState::~SHEntrySharedParentState() {}
+
+void dom::SHEntrySharedParentState::CopyFrom(
+    dom::SHEntrySharedParentState* aEntry) {
+  mDocShellID = aEntry->mDocShellID;
+  mTriggeringPrincipal = aEntry->mTriggeringPrincipal;
+  mPrincipalToInherit = aEntry->mPrincipalToInherit;
+  mStoragePrincipalToInherit = aEntry->mStoragePrincipalToInherit;
+  mCsp = aEntry->mCsp;
+  mContentType.Assign(aEntry->mContentType);
+  mIsFrameNavigation = aEntry->mIsFrameNavigation;
+  mSticky = aEntry->mSticky;
+  mDynamicallyCreated = aEntry->mDynamicallyCreated;
+  mCacheKey = aEntry->mCacheKey;
+  mLastTouched = aEntry->mLastTouched;
+}
+
+dom::SHEntrySharedChildState::SHEntrySharedChildState()
+    : mSaveLayoutState(true) {}
+
+void dom::SHEntrySharedChildState::CopyFrom(
+    dom::SHEntrySharedChildState* aEntry) {
+  mChildShells.AppendObjects(aEntry->mChildShells);
+  mSaveLayoutState = aEntry->mSaveLayoutState;
+}
+
+nsSHEntryShared::nsSHEntryShared(uint64_t aID)
+    : dom::SHEntrySharedParentState(aID) {}
+
 nsSHEntryShared::~nsSHEntryShared() {
   // The destruction can be caused by either the entry is removed from session
   // history and no one holds the reference, or the whole session history is on
   // destruction. We want to ensure that we invoke
   // shistory->RemoveFromExpirationTracker for the former case.
   RemoveFromExpirationTracker();
 
   // Calling RemoveDynEntriesForBFCacheEntry on destruction is unnecessary since
@@ -55,35 +77,26 @@ nsSHEntryShared::~nsSHEntryShared() {
   // nsSHistory::Release can cause a crash, so set mSHistory to null explicitly
   // before RemoveFromBFCacheSync.
   mSHistory = nullptr;
   if (mContentViewer) {
     RemoveFromBFCacheSync();
   }
 }
 
-NS_IMPL_ISUPPORTS(nsSHEntryShared, nsIBFCacheEntry, nsIMutationObserver)
+NS_IMPL_QUERY_INTERFACE(nsSHEntryShared, nsIBFCacheEntry, nsIMutationObserver)
+NS_IMPL_ADDREF_INHERITED(nsSHEntryShared, dom::SHEntrySharedParentState)
+NS_IMPL_RELEASE_INHERITED(nsSHEntryShared, dom::SHEntrySharedParentState)
 
 already_AddRefed<nsSHEntryShared> nsSHEntryShared::Duplicate(
-    nsSHEntryShared* aEntry) {
-  RefPtr<nsSHEntryShared> newEntry = new nsSHEntryShared();
+    nsSHEntryShared* aEntry, uint64_t aNewSharedID) {
+  RefPtr<nsSHEntryShared> newEntry = new nsSHEntryShared(aNewSharedID);
 
-  newEntry->mDocShellID = aEntry->mDocShellID;
-  newEntry->mChildShells.AppendObjects(aEntry->mChildShells);
-  newEntry->mTriggeringPrincipal = aEntry->mTriggeringPrincipal;
-  newEntry->mPrincipalToInherit = aEntry->mPrincipalToInherit;
-  newEntry->mStoragePrincipalToInherit = aEntry->mStoragePrincipalToInherit;
-  newEntry->mCsp = aEntry->mCsp;
-  newEntry->mContentType.Assign(aEntry->mContentType);
-  newEntry->mIsFrameNavigation = aEntry->mIsFrameNavigation;
-  newEntry->mSaveLayoutState = aEntry->mSaveLayoutState;
-  newEntry->mSticky = aEntry->mSticky;
-  newEntry->mDynamicallyCreated = aEntry->mDynamicallyCreated;
-  newEntry->mCacheKey = aEntry->mCacheKey;
-  newEntry->mLastTouched = aEntry->mLastTouched;
+  newEntry->dom::SHEntrySharedParentState::CopyFrom(aEntry);
+  newEntry->dom::SHEntrySharedChildState::CopyFrom(aEntry);
 
   return newEntry.forget();
 }
 
 void nsSHEntryShared::RemoveFromExpirationTracker() {
   nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory);
   if (shistory && GetExpirationState()->IsTracked()) {
     shistory->RemoveFromExpirationTracker(this);
--- a/docshell/shistory/nsSHEntryShared.h
+++ b/docshell/shistory/nsSHEntryShared.h
@@ -22,91 +22,146 @@
 class nsSHEntry;
 class nsISHEntry;
 class nsIContentViewer;
 class nsIDocShellTreeItem;
 class nsILayoutHistoryState;
 class nsDocShellEditorData;
 class nsIMutableArray;
 
-namespace mozilla {
-namespace dom {
-class Document;
-}
-}  // namespace mozilla
-
 // A document may have multiple SHEntries, either due to hash navigations or
 // calls to history.pushState.  SHEntries corresponding to the same document
 // share many members; in particular, they share state related to the
 // back/forward cache.
 //
-// nsSHEntryShared is the vehicle for this sharing.
+// The classes defined here are the vehicle for this sharing.
+//
+// Some of the state can only be stored in the process where we did the actual
+// load, because that's where the objects live (eg. the content viewer).
+
+namespace mozilla {
+namespace dom {
+class Document;
+
+/**
+ * SHEntrySharedParentState holds the shared state that can live in the parent
+ * process.
+ */
+class SHEntrySharedParentState {
+ public:
+  explicit SHEntrySharedParentState(uint64_t aID);
+
+  uint64_t GetID() const { return mID; }
+
+ protected:
+  friend class nsSHEntry;
+
+  virtual ~SHEntrySharedParentState();
+  NS_INLINE_DECL_VIRTUAL_REFCOUNTING_WITH_DESTROY(SHEntrySharedParentState,
+                                                  Destroy())
+
+  virtual void Destroy() { delete this; }
+
+  void CopyFrom(SHEntrySharedParentState* aSource);
+
+  // 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;
+  nsCString mContentType;
+
+  nsIntRect mViewerBounds;
+
+  uint32_t mCacheKey;
+  uint32_t mLastTouched;
+
+  // These members aren't copied by SHEntrySharedParentState::CopyFrom() because
+  // they're specific to a particular content viewer.
+  uint64_t mID;
+  nsWeakPtr mSHistory;
+
+  bool mIsFrameNavigation;
+  bool mSticky;
+  bool mDynamicallyCreated;
+
+  // This flag is about necko cache, not bfcache.
+  bool mExpired;
+};
+
+/**
+ * SHEntrySharedChildState holds the shared state that needs to live in the
+ * process where the document was loaded.
+ */
+class SHEntrySharedChildState {
+ protected:
+  SHEntrySharedChildState();
+
+  void CopyFrom(SHEntrySharedChildState* aSource);
+
+ public:
+  // These members are copied by SHEntrySharedChildState::CopyFrom(). If you
+  // add a member here, be sure to update the CopyFrom() implementation.
+  nsCOMArray<nsIDocShellTreeItem> mChildShells;
+
+  // These members aren't copied by SHEntrySharedChildState::CopyFrom() because
+  // they're specific to a particular content viewer.
+  nsCOMPtr<nsIContentViewer> mContentViewer;
+  RefPtr<mozilla::dom::Document> mDocument;
+  // FIXME Move to parent?
+  nsCOMPtr<nsILayoutHistoryState> mLayoutHistoryState;
+  nsCOMPtr<nsISupports> mWindowState;
+  // FIXME Move to parent?
+  nsCOMPtr<nsIMutableArray> mRefreshURIList;
+  nsExpirationState mExpirationState;
+  nsAutoPtr<nsDocShellEditorData> mEditorData;
+
+  // FIXME Move to parent?
+  bool mSaveLayoutState;
+};
+
+}  // 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.
+ */
 class nsSHEntryShared final : public nsIBFCacheEntry,
-                              public nsStubMutationObserver {
+                              public nsStubMutationObserver,
+                              public mozilla::dom::SHEntrySharedParentState,
+                              public mozilla::dom::SHEntrySharedChildState {
  public:
   static void EnsureHistoryTracker();
   static void Shutdown();
 
-  nsSHEntryShared();
+  explicit nsSHEntryShared(uint64_t aID);
 
-  NS_DECL_ISUPPORTS
+  NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIBFCACHEENTRY
 
   // The nsIMutationObserver bits we actually care about.
   NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
   NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
 
   nsExpirationState* GetExpirationState() { return &mExpirationState; }
 
+  static already_AddRefed<nsSHEntryShared> Duplicate(nsSHEntryShared* aEntry,
+                                                     uint64_t aNewSharedID);
+
  private:
   ~nsSHEntryShared();
 
-  friend class nsSHEntry;
-
-  static already_AddRefed<nsSHEntryShared> Duplicate(nsSHEntryShared* aEntry);
+  friend class nsLegacySHEntry;
 
   void RemoveFromExpirationTracker();
   void SyncPresentationState();
   void DropPresentationState();
 
   nsresult SetContentViewer(nsIContentViewer* aViewer);
-
-  // See nsISHEntry.idl for an explanation of these members.
-
-  // These members are copied by nsSHEntryShared::Duplicate().  If you add a
-  // member here, be sure to update the Duplicate() implementation.
-  nsID mDocShellID;
-  nsCOMArray<nsIDocShellTreeItem> mChildShells;
-  nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
-  nsCOMPtr<nsIPrincipal> mPrincipalToInherit;
-  nsCOMPtr<nsIPrincipal> mStoragePrincipalToInherit;
-  nsCOMPtr<nsIContentSecurityPolicy> mCsp;
-  nsCString mContentType;
-
-  uint32_t mCacheKey;
-  uint32_t mLastTouched;
-
-  // These members aren't copied by nsSHEntryShared::Duplicate() because
-  // they're specific to a particular content viewer.
-  uint64_t mID;
-  nsCOMPtr<nsIContentViewer> mContentViewer;
-  RefPtr<mozilla::dom::Document> mDocument;
-  nsCOMPtr<nsILayoutHistoryState> mLayoutHistoryState;
-  nsCOMPtr<nsISupports> mWindowState;
-  nsIntRect mViewerBounds;
-  nsCOMPtr<nsIMutableArray> mRefreshURIList;
-  nsExpirationState mExpirationState;
-  nsAutoPtr<nsDocShellEditorData> mEditorData;
-  nsWeakPtr mSHistory;
-
-  bool mIsFrameNavigation;
-  bool mSaveLayoutState;
-  bool mSticky;
-  bool mDynamicallyCreated;
-
-  // This flag is about necko cache, not bfcache.
-  bool mExpired;
 };
 
 #endif
--- a/docshell/shistory/nsSHistory.cpp
+++ b/docshell/shistory/nsSHistory.cpp
@@ -188,17 +188,18 @@ void nsSHistory::EvictContentViewerForEn
   // well.
   int32_t index = GetIndexOfEntry(aEntry);
   if (index != -1) {
     RemoveDynEntries(index, aEntry);
   }
 }
 
 nsSHistory::nsSHistory(BrowsingContext* aRootBC, const nsID& aRootDocShellID)
-    : mIndex(-1),
+    : mIsRemote(false),
+      mIndex(-1),
       mRequestedIndex(-1),
       mRootBC(aRootBC),
       mRootDocShellID(aRootDocShellID) {
   // 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.
@@ -343,25 +344,25 @@ void nsSHistory::Shutdown() {
       obsSvc->RemoveObserver(gObserver, "cacheservice:empty-cache");
       obsSvc->RemoveObserver(gObserver, "memory-pressure");
     }
     gObserver = nullptr;
   }
 }
 
 // static
-nsISHEntry* nsSHistory::GetRootSHEntry(nsISHEntry* aEntry) {
+already_AddRefed<nsISHEntry> nsSHistory::GetRootSHEntry(nsISHEntry* aEntry) {
   nsCOMPtr<nsISHEntry> rootEntry = aEntry;
-  nsISHEntry* result = nullptr;
+  nsCOMPtr<nsISHEntry> result = nullptr;
   while (rootEntry) {
     result = rootEntry;
     rootEntry = result->GetParent();
   }
 
-  return result;
+  return result.forget();
 }
 
 // static
 nsresult nsSHistory::WalkHistoryEntries(nsISHEntry* aRootEntry,
                                         nsDocShell* aRootShell,
                                         WalkHistoryEntriesFunc aCallback,
                                         void* aData) {
   NS_ENSURE_TRUE(aRootEntry, NS_ERROR_FAILURE);
@@ -538,62 +539,70 @@ nsresult nsSHistory::SetChildHistoryEntr
   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) {
+  int32_t dummy = 0;
+  return AddEntry(aSHEntry, aPersist, &dummy);
+}
+
+nsresult nsSHistory::AddEntry(nsISHEntry* aSHEntry, bool aPersist,
+                              int32_t* aEntriesPurged) {
   NS_ENSURE_ARG(aSHEntry);
 
   nsCOMPtr<nsISHistory> shistoryOfEntry = aSHEntry->GetSHistory();
   if (shistoryOfEntry && shistoryOfEntry != this) {
     NS_WARNING(
         "The entry has been associated to another nsISHistory instance. "
         "Try nsISHEntry.clone() and nsISHEntry.abandonBFCacheEntry() "
         "first if you're copying an entry from another nsISHistory.");
     return NS_ERROR_FAILURE;
   }
 
-  nsCOMPtr<nsISHEntry> currentTxn;
-  if (mIndex >= 0) {
-    nsresult rv = GetEntryAtIndex(mIndex, getter_AddRefs(currentTxn));
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
   aSHEntry->SetSHistory(this);
 
   // If we have a root docshell, update the docshell id of the root shentry to
   // match the id of that docshell
   if (mRootBC) {
-    aSHEntry->SetDocshellID(&mRootDocShellID);
+    aSHEntry->SetDocshellID(mRootDocShellID);
   }
 
-  if (currentTxn && !currentTxn->GetPersist()) {
-    NOTIFY_LISTENERS(OnHistoryReplaceEntry, ());
-    aSHEntry->SetPersist(aPersist);
-    mEntries[mIndex] = aSHEntry;
-    return NS_OK;
+  if (mIndex >= 0) {
+    MOZ_ASSERT(mIndex < Length(), "Index out of range!");
+    if (mIndex >= Length()) {
+      return NS_ERROR_FAILURE;
+    }
+
+    if (mEntries[mIndex] && !mEntries[mIndex]->GetPersist()) {
+      NOTIFY_LISTENERS(OnHistoryReplaceEntry, ());
+      aSHEntry->SetPersist(aPersist);
+      mEntries[mIndex] = aSHEntry;
+      return NS_OK;
+    }
   }
 
   nsCOMPtr<nsIURI> uri = aSHEntry->GetURI();
   NOTIFY_LISTENERS(OnHistoryNewEntry, (uri, mIndex));
 
   // Remove all entries after the current one, add the new one, and set the new
   // one as the current one.
   MOZ_ASSERT(mIndex >= -1);
   aSHEntry->SetPersist(aPersist);
   mEntries.TruncateLength(mIndex + 1);
   mEntries.AppendElement(aSHEntry);
   mIndex++;
 
   // Purge History list if it is too long
   if (gHistoryMaxSize >= 0 && Length() > gHistoryMaxSize) {
-    PurgeHistory(Length() - gHistoryMaxSize);
+    *aEntriesPurged = Length() - gHistoryMaxSize;
+    PurgeHistory(*aEntriesPurged);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP_(void)
 nsSHistory::ClearRootBrowsingContext() { mRootBC = nullptr; }
 
@@ -796,17 +805,34 @@ nsSHistory::EvictAllContentViewers() {
   // we might have viewers quite far from mIndex.  So just evict everything.
   for (int32_t i = 0; i < Length(); i++) {
     EvictContentViewerForEntry(mEntries[i]);
   }
 
   return NS_OK;
 }
 
-nsresult nsSHistory::Reload(uint32_t aReloadFlags) {
+static nsresult LoadURI(nsSHistory::LoadEntryResult& aLoadResult) {
+  nsCOMPtr<nsIDocShell> docShell = aLoadResult.mBrowsingContext->GetDocShell();
+  NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+
+  return docShell->LoadURI(aLoadResult.mLoadState, false);
+}
+
+NS_IMETHODIMP
+nsSHistory::Reload(uint32_t aReloadFlags) {
+  LoadEntryResult loadResult;
+  nsresult rv = Reload(aReloadFlags, loadResult);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return LoadURI(loadResult);
+}
+
+nsresult nsSHistory::Reload(uint32_t aReloadFlags,
+                            LoadEntryResult& aLoadResult) {
   uint32_t loadType;
   if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY &&
       aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) {
     loadType = LOAD_RELOAD_BYPASS_PROXY_AND_CACHE;
   } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY) {
     loadType = LOAD_RELOAD_BYPASS_PROXY;
   } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) {
     loadType = LOAD_RELOAD_BYPASS_CACHE;
@@ -814,31 +840,42 @@ nsresult nsSHistory::Reload(uint32_t aRe
     loadType = LOAD_RELOAD_CHARSET_CHANGE;
   } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_ALLOW_MIXED_CONTENT) {
     loadType = LOAD_RELOAD_ALLOW_MIXED_CONTENT;
   } else {
     loadType = LOAD_RELOAD_NORMAL;
   }
 
   // We are reloading. Send Reload notifications.
+  // nsDocShellLoadFlagType is not public, where as nsIWebNavigation
+  // is public. So send the reload notifications with the
+  // nsIWebNavigation flags.
   bool canNavigate = true;
   NOTIFY_LISTENERS_CANCELABLE(OnHistoryReload, canNavigate, (&canNavigate));
   if (!canNavigate) {
     return NS_OK;
   }
 
-  return LoadEntry(mIndex, loadType, HIST_CMD_RELOAD);
+  return LoadEntry(mIndex, loadType, HIST_CMD_RELOAD, aLoadResult);
 }
 
 NS_IMETHODIMP
 nsSHistory::ReloadCurrentEntry() {
+  LoadEntryResult loadResult;
+  nsresult rv = ReloadCurrentEntry(loadResult);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return LoadURI(loadResult);
+}
+
+nsresult nsSHistory::ReloadCurrentEntry(LoadEntryResult& aLoadResult) {
   // Notify listeners
   NOTIFY_LISTENERS(OnHistoryGotoIndex, ());
 
-  return LoadEntry(mIndex, LOAD_HISTORY, HIST_CMD_RELOAD);
+  return LoadEntry(mIndex, LOAD_HISTORY, HIST_CMD_RELOAD, aLoadResult);
 }
 
 void nsSHistory::EvictOutOfRangeWindowContentViewers(int32_t aIndex) {
   // XXX rename method to EvictContentViewersExceptAroundIndex, or something.
 
   // We need to release all content viewers that are no longer in the range
   //
   //  aIndex - VIEWER_WINDOW to aIndex + VIEWER_WINDOW
@@ -950,16 +987,22 @@ class EntryAndDistance {
 void nsSHistory::GloballyEvictContentViewers() {
   // First, collect from each SHistory object the entries which have a cached
   // content viewer. Associate with each entry its distance from its SHistory's
   // current index.
 
   nsTArray<EntryAndDistance> entries;
 
   for (auto shist : gSHistoryList) {
+    // FIXME Bug 1546348: Make global eviction work for session history in the
+    //       parent and remove mIsRemote.
+    if (shist->mIsRemote) {
+      continue;
+    }
+
     // Maintain a list of the entries which have viewers and belong to
     // this particular shist object.  We'll add this list to the global list,
     // |entries|, eventually.
     nsTArray<EntryAndDistance> shEntries;
 
     // Content viewers are likely to exist only within shist->mIndex -/+
     // VIEWER_WINDOW, so only search within that range.
     //
@@ -1104,44 +1147,40 @@ void nsSHistory::GloballyEvictAllContent
 }
 
 void GetDynamicChildren(nsISHEntry* aEntry, nsTArray<nsID>& aDocshellIDs) {
   int32_t count = aEntry->GetChildCount();
   for (int32_t i = 0; i < count; ++i) {
     nsCOMPtr<nsISHEntry> child;
     aEntry->GetChildAt(i, getter_AddRefs(child));
     if (child) {
-      bool dynAdded = child->IsDynamicallyAdded();
-      if (dynAdded) {
-        nsID docshellID = child->DocshellID();
-        aDocshellIDs.AppendElement(docshellID);
+      if (child->IsDynamicallyAdded()) {
+        child->GetDocshellID(*aDocshellIDs.AppendElement());
       } else {
         GetDynamicChildren(child, aDocshellIDs);
       }
     }
   }
 }
 
 bool RemoveFromSessionHistoryEntry(nsISHEntry* aRoot,
                                    nsTArray<nsID>& aDocshellIDs) {
   bool didRemove = false;
   int32_t childCount = aRoot->GetChildCount();
   for (int32_t i = childCount - 1; i >= 0; --i) {
     nsCOMPtr<nsISHEntry> child;
     aRoot->GetChildAt(i, getter_AddRefs(child));
     if (child) {
-      nsID docshelldID = child->DocshellID();
+      nsID docshelldID;
+      child->GetDocshellID(docshelldID);
       if (aDocshellIDs.Contains(docshelldID)) {
         didRemove = true;
         aRoot->RemoveChild(child);
-      } else {
-        bool childRemoved = RemoveFromSessionHistoryEntry(child, aDocshellIDs);
-        if (childRemoved) {
-          didRemove = true;
-        }
+      } else if (RemoveFromSessionHistoryEntry(child, aDocshellIDs)) {
+        didRemove = true;
       }
     }
   }
   return didRemove;
 }
 
 bool RemoveChildEntries(nsISHistory* aHistory, int32_t aIndex,
                         nsTArray<nsID>& aEntryIDs) {
@@ -1196,20 +1235,21 @@ bool nsSHistory::RemoveDuplicate(int32_t
   rv = GetEntryAtIndex(compareIndex, getter_AddRefs(root2));
   if (NS_FAILED(rv)) {
     return false;
   }
 
   if (IsSameTree(root1, root2)) {
     mEntries.RemoveElementAt(aIndex);
 
-    if (mRootBC && mRootBC->GetDocShell()) {
-      static_cast<nsDocShell*>(mRootBC->GetDocShell())
-          ->HistoryEntryRemoved(aIndex);
-    }
+    // FIXME Bug 1546350: Reimplement history listeners.
+    // if (mRootBC && mRootBC->GetDocShell()) {
+    //  static_cast<nsDocShell*>(mRootBC->GetDocShell())
+    //      ->HistoryEntryRemoved(aIndex);
+    //}
 
     // Adjust our indices to reflect the removed entry.
     if (mIndex > aIndex) {
       mIndex = mIndex - 1;
     }
 
     // NB: If the entry we are removing is the entry currently
     // being navigated to (mRequestedIndex) then we adjust the index
@@ -1228,35 +1268,41 @@ bool nsSHistory::RemoveDuplicate(int32_t
     }
     return true;
   }
   return false;
 }
 
 NS_IMETHODIMP_(void)
 nsSHistory::RemoveEntries(nsTArray<nsID>& aIDs, int32_t aStartIndex) {
+  bool didRemove;
+  RemoveEntries(aIDs, aStartIndex, &didRemove);
+  if (didRemove && mRootBC && mRootBC->GetDocShell()) {
+    mRootBC->GetDocShell()->DispatchLocationChangeEvent();
+  }
+}
+
+void nsSHistory::RemoveEntries(nsTArray<nsID>& aIDs, int32_t aStartIndex,
+                               bool* aDidRemove) {
   int32_t index = aStartIndex;
   while (index >= 0 && RemoveChildEntries(this, --index, aIDs)) {
   }
   int32_t minIndex = index;
   index = aStartIndex;
   while (index >= 0 && RemoveChildEntries(this, index++, aIDs)) {
   }
 
   // We need to remove duplicate nsSHEntry trees.
-  bool didRemove = false;
+  *aDidRemove = false;
   while (index > minIndex) {
-    if (index != mIndex) {
-      didRemove = RemoveDuplicate(index, index < mIndex) || didRemove;
+    if (index != mIndex && RemoveDuplicate(index, index < mIndex)) {
+      *aDidRemove = true;
     }
     --index;
   }
-  if (didRemove && mRootBC && mRootBC->GetDocShell()) {
-    mRootBC->GetDocShell()->DispatchLocationChangeEvent();
-  }
 }
 
 void nsSHistory::RemoveDynEntries(int32_t aIndex, nsISHEntry* aEntry) {
   // Remove dynamic entries which are at the index and belongs to the container.
   nsCOMPtr<nsISHEntry> entry(aEntry);
   if (!entry) {
     GetEntryAtIndex(aIndex, getter_AddRefs(entry));
   }
@@ -1287,33 +1333,43 @@ nsSHistory::UpdateIndex() {
   }
 
   mRequestedIndex = -1;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHistory::GotoIndex(int32_t aIndex) {
-  return LoadEntry(aIndex, LOAD_HISTORY, HIST_CMD_GOTOINDEX);
+  LoadEntryResult loadResult;
+  nsresult rv = GotoIndex(aIndex, loadResult);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return LoadURI(loadResult);
+}
+
+nsresult nsSHistory::GotoIndex(int32_t aIndex, LoadEntryResult& aLoadResult) {
+  return LoadEntry(aIndex, LOAD_HISTORY, HIST_CMD_GOTOINDEX, aLoadResult);
 }
 
 nsresult nsSHistory::LoadNextPossibleEntry(int32_t aNewIndex, long aLoadType,
-                                           uint32_t aHistCmd) {
+                                           uint32_t aHistCmd,
+                                           LoadEntryResult& aLoadResult) {
   mRequestedIndex = -1;
   if (aNewIndex < mIndex) {
-    return LoadEntry(aNewIndex - 1, aLoadType, aHistCmd);
+    return LoadEntry(aNewIndex - 1, aLoadType, aHistCmd, aLoadResult);
   }
   if (aNewIndex > mIndex) {
-    return LoadEntry(aNewIndex + 1, aLoadType, aHistCmd);
+    return LoadEntry(aNewIndex + 1, aLoadType, aHistCmd, aLoadResult);
   }
   return NS_ERROR_FAILURE;
 }
 
 nsresult nsSHistory::LoadEntry(int32_t aIndex, long aLoadType,
-                               uint32_t aHistCmd) {
+                               uint32_t aHistCmd,
+                               LoadEntryResult& aLoadResult) {
   if (!mRootBC) {
     return NS_ERROR_FAILURE;
   }
 
   if (aIndex < 0 || aIndex >= Length()) {
     // The index is out of range
     return NS_ERROR_FAILURE;
   }
@@ -1344,51 +1400,49 @@ nsresult nsSHistory::LoadEntry(int32_t a
   // Send appropriate listener notifications.
   if (aHistCmd == HIST_CMD_GOTOINDEX) {
     // We are going somewhere else. This is not reload either
     NOTIFY_LISTENERS(OnHistoryGotoIndex, ());
   }
 
   if (mRequestedIndex == mIndex) {
     // Possibly a reload case
-    return InitiateLoad(nextEntry, mRootBC, aLoadType);
+    return InitiateLoad(nextEntry, mRootBC, aLoadType, aLoadResult);
   }
 
   // Going back or forward.
   bool differenceFound = false;
   nsresult rv = LoadDifferingEntries(prevEntry, nextEntry, mRootBC, aLoadType,
-                                     differenceFound);
+                                     differenceFound, aLoadResult);
   if (!differenceFound) {
     // We did not find any differences. Go further in the history.
-    return LoadNextPossibleEntry(aIndex, aLoadType, aHistCmd);
+    return LoadNextPossibleEntry(aIndex, aLoadType, aHistCmd, aLoadResult);
   }
 
   return rv;
 }
 
-nsresult nsSHistory::LoadDifferingEntries(nsISHEntry* aPrevEntry,
-                                          nsISHEntry* aNextEntry,
-                                          BrowsingContext* aParent,
-                                          long aLoadType,
-                                          bool& aDifferenceFound) {
+nsresult nsSHistory::LoadDifferingEntries(
+    nsISHEntry* aPrevEntry, nsISHEntry* aNextEntry, BrowsingContext* aParent,
+    long aLoadType, bool& aDifferenceFound, LoadEntryResult& aLoadResult) {
   if (!aPrevEntry || !aNextEntry || !aParent) {
     return NS_ERROR_FAILURE;
   }
 
   nsresult result = NS_OK;
   uint32_t prevID = aPrevEntry->GetID();
   uint32_t nextID = aNextEntry->GetID();
 
   // Check the IDs to verify if the pages are different.
   if (prevID != nextID) {
     aDifferenceFound = true;
 
     // Set the Subframe flag if not navigating the root docshell.
     aNextEntry->SetIsSubFrame(aParent != mRootBC);
-    return InitiateLoad(aNextEntry, aParent, aLoadType);
+    return InitiateLoad(aNextEntry, aParent, aLoadType, aLoadResult);
   }
 
   // The entries are the same, so compare any child frames
   int32_t pcnt = aPrevEntry->GetChildCount();
   int32_t ncnt = aNextEntry->GetChildCount();
 
   // Create an array for child browsing contexts.
   nsTArray<RefPtr<BrowsingContext>> browsingContexts;
@@ -1397,17 +1451,18 @@ nsresult nsSHistory::LoadDifferingEntrie
   // Search for something to load next.
   for (int32_t i = 0; i < ncnt; ++i) {
     // First get an entry which may cause a new page to be loaded.
     nsCOMPtr<nsISHEntry> nChild;
     aNextEntry->GetChildAt(i, getter_AddRefs(nChild));
     if (!nChild) {
       continue;
     }
-    nsID docshellID = nChild->DocshellID();
+    nsID docshellID;
+    nChild->GetDocshellID(docshellID);
 
     // Then find the associated docshell.
     RefPtr<BrowsingContext> bcChild;
     for (const RefPtr<BrowsingContext>& bc : browsingContexts) {
       if (bc->GetHistoryID() == docshellID) {
         bcChild = bc;
         break;
       }
@@ -1418,35 +1473,40 @@ nsresult nsSHistory::LoadDifferingEntrie
 
     // Then look at the previous entries to see if there was
     // an entry for the docshell.
     nsCOMPtr<nsISHEntry> pChild;
     for (int32_t k = 0; k < pcnt; ++k) {
       nsCOMPtr<nsISHEntry> child;
       aPrevEntry->GetChildAt(k, getter_AddRefs(child));
       if (child) {
-        nsID dID = child->DocshellID();
+        nsID dID;
+        child->GetDocshellID(dID);
         if (dID == docshellID) {
           pChild = child;
           break;
         }
       }
     }
 
     // Finally recursively call this method.
     // This will either load a new page to shell or some subshell or
     // do nothing.
-    LoadDifferingEntries(pChild, nChild, bcChild, aLoadType, aDifferenceFound);
+    LoadDifferingEntries(pChild, nChild, bcChild, aLoadType, aDifferenceFound,
+                         aLoadResult);
   }
   return result;
 }
 
 nsresult nsSHistory::InitiateLoad(nsISHEntry* aFrameEntry,
-                                  BrowsingContext* aFrameDS, long aLoadType) {
-  NS_ENSURE_STATE(aFrameDS && aFrameEntry);
+                                  BrowsingContext* aFrameBC, long aLoadType,
+                                  LoadEntryResult& aLoadResult) {
+  NS_ENSURE_STATE(aFrameBC && aFrameEntry);
+
+  aLoadResult.mBrowsingContext = aFrameBC;
 
   nsCOMPtr<nsIURI> newURI = aFrameEntry->GetURI();
   RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(newURI);
 
   /* Set the loadType in the SHEntry too to  what was passed on.
    * This will be passed on to child subframes later in nsDocShell,
    * so that proper loadType is maintained through out a frameset
    */
@@ -1463,16 +1523,12 @@ nsresult nsSHistory::InitiateLoad(nsISHE
   loadState->SetLoadFlags(nsIWebNavigation::LOAD_FLAGS_NONE);
   nsCOMPtr<nsIPrincipal> triggeringPrincipal =
       aFrameEntry->GetTriggeringPrincipal();
   loadState->SetTriggeringPrincipal(triggeringPrincipal);
   loadState->SetFirstParty(false);
   nsCOMPtr<nsIContentSecurityPolicy> csp = aFrameEntry->GetCsp();
   loadState->SetCsp(csp);
 
-  // Time to initiate a document load
-  return LoadURI(aFrameEntry, aFrameDS, loadState);
-}
+  aLoadResult.mLoadState = loadState.forget();
 
-nsresult nsSHistory::LoadURI(nsISHEntry* aFrameEntry, BrowsingContext* aFrameBC,
-                             nsDocShellLoadState* aLoadState) {
-  return aFrameBC->GetDocShell()->LoadURI(aLoadState, false);
+  return NS_OK;
 }
--- a/docshell/shistory/nsSHistory.h
+++ b/docshell/shistory/nsSHistory.h
@@ -18,19 +18,25 @@
 #include "mozilla/LinkedList.h"
 #include "mozilla/UniquePtr.h"
 
 class nsIDocShell;
 class nsDocShell;
 class nsSHistoryObserver;
 class nsISHEntry;
 
-class nsSHistory final : public mozilla::LinkedListElement<nsSHistory>,
-                         public nsISHistory,
-                         public nsSupportsWeakReference {
+namespace mozilla {
+namespace dom {
+class LoadSHEntryResult;
+}
+}  // namespace mozilla
+
+class nsSHistory : public mozilla::LinkedListElement<nsSHistory>,
+                   public nsISHistory,
+                   public nsSupportsWeakReference {
  public:
   // The timer based history tracker is used to evict bfcache on expiration.
   class HistoryTracker final : public nsExpirationTracker<nsSHEntryShared, 3> {
    public:
     explicit HistoryTracker(nsSHistory* aSHistory, uint32_t aTimeout,
                             nsIEventTarget* aEventTarget)
         : nsExpirationTracker(1000 * aTimeout / 2, "HistoryTracker",
                               aEventTarget) {
@@ -57,34 +63,29 @@ class nsSHistory final : public mozilla:
     nsISHEntry* destTreeParent;  // constant; the node under destTreeRoot
                                  // whose children will correspond to aEntry
   };
 
   nsSHistory(mozilla::dom::BrowsingContext* aRootBC, const nsID& aDocShellID);
   NS_DECL_ISUPPORTS
   NS_DECL_NSISHISTORY
 
-  nsresult Reload(uint32_t aReloadFlags);
-  virtual nsresult LoadURI(nsISHEntry* aFrameEntry,
-                           mozilla::dom::BrowsingContext* aFrameBc,
-                           nsDocShellLoadState* aLoadState);
-
   // One time initialization method called upon docshell module construction
   static nsresult Startup();
   static void Shutdown();
   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);
+  static already_AddRefed<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);
@@ -112,35 +113,58 @@ class nsSHistory final : public mozilla:
   // 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 nsSHistoryObserver;
+  nsTArray<nsCOMPtr<nsISHEntry>>& Entries() { return mEntries; }
+
+  nsresult AddEntry(nsISHEntry* aSHEntry, bool aPersist,
+                    int32_t* aEntriesPurged);
+  void RemoveEntries(nsTArray<nsID>& aIDs, int32_t aStartIndex,
+                     bool* aDidRemove);
 
   // The size of the window of SHEntries which can have alive viewers in the
   // bfcache around the currently active SHEntry.
   //
   // We try to keep viewers for SHEntries between index - VIEWER_WINDOW and
   // index + VIEWER_WINDOW alive.
   static const int32_t VIEWER_WINDOW = 3;
 
+  struct LoadEntryResult {
+    RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext;
+    RefPtr<nsDocShellLoadState> mLoadState;
+  };
+
+  nsresult Reload(uint32_t aReloadFlags, LoadEntryResult& aLoadResult);
+  nsresult ReloadCurrentEntry(LoadEntryResult& aLoadResult);
+  nsresult GotoIndex(int32_t aIndex, LoadEntryResult& aLoadResult);
+
+  void WindowIndices(int32_t aIndex, int32_t* aOutStartIndex,
+                     int32_t* aOutEndIndex);
+
+ protected:
+  virtual ~nsSHistory();
+
+ private:
+  friend class nsSHistoryObserver;
+
   nsresult LoadDifferingEntries(nsISHEntry* aPrevEntry, nsISHEntry* aNextEntry,
                                 mozilla::dom::BrowsingContext* aRootBC,
-                                long aLoadType, bool& aDifferenceFound);
-  virtual nsresult InitiateLoad(nsISHEntry* aFrameEntry,
-                                mozilla::dom::BrowsingContext* aFrameBC,
-                                long aLoadType);
+                                long aLoadType, bool& aDifferenceFound,
+                                LoadEntryResult& aLoadResult);
+  nsresult InitiateLoad(nsISHEntry* aFrameEntry,
+                        mozilla::dom::BrowsingContext* aFrameBC, long aLoadType,
+                        LoadEntryResult& aLoadResult);
 
-  nsresult LoadEntry(int32_t aIndex, long aLoadType, uint32_t aHistCmd);
+  nsresult LoadEntry(int32_t aIndex, long aLoadType, uint32_t aHistCmd,
+                     LoadEntryResult& aLoad);
 
 #ifdef DEBUG
   nsresult PrintHistory();
 #endif
 
   // Find the history entry for a given bfcache entry. It only looks up between
   // the range where alive viewers may exist (i.e nsSHistory::VIEWER_WINDOW).
   nsresult FindEntryForBFCache(nsIBFCacheEntry* aBFEntry, nsISHEntry** aResult,
@@ -153,36 +177,38 @@ class nsSHistory final : public mozilla:
   static void GloballyEvictContentViewers();
   static void GloballyEvictAllContentViewers();
 
   // Calculates a max number of total
   // content viewers to cache, based on amount of total memory
   static uint32_t CalcMaxTotalViewers();
 
   nsresult LoadNextPossibleEntry(int32_t aNewIndex, long aLoadType,
-                                 uint32_t aHistCmd);
+                                 uint32_t aHistCmd,
+                                 LoadEntryResult& aLoadResult);
 
   // aIndex is the index of the entry which may be removed.
   // If aKeepNext is true, aIndex is compared to aIndex + 1,
   // otherwise comparison is done to aIndex - 1.
   bool RemoveDuplicate(int32_t aIndex, bool aKeepNext);
 
+  // Length of mEntries.
+  int32_t Length() { return int32_t(mEntries.Length()); }
+
+ protected:
+  bool mIsRemote;
+
+ private:
   // Track all bfcache entries and evict on expiration.
   mozilla::UniquePtr<HistoryTracker> mHistoryTracker;
 
   nsTArray<nsCOMPtr<nsISHEntry>> mEntries;  // entries are never null
   int32_t mIndex;                           // -1 means "no index"
   int32_t mRequestedIndex;                  // -1 means "no requested index"
 
-  void WindowIndices(int32_t aIndex, int32_t* aOutStartIndex,
-                     int32_t* aOutEndIndex);
-
-  // Length of mEntries.
-  int32_t Length() { return int32_t(mEntries.Length()); }
-
   // Session History listeners
   nsAutoTObserverArray<nsWeakPtr, 2> mListeners;
 
   // Weak reference. Do not refcount this.
   mozilla::dom::BrowsingContext* mRootBC;
   nsID mRootDocShellID;
 
   // Max viewers allowed total, across all SHistory objects
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -9802,17 +9802,18 @@ bool nsContentUtils::IsLocalRefURL(const
 
 // We use only 53 bits for the ID so that it can be converted to and from a JS
 // value without loss of precision. The upper bits of the ID hold the process
 // ID. The lower bits identify the object itself.
 static constexpr uint64_t kIdTotalBits = 53;
 static constexpr uint64_t kIdProcessBits = 22;
 static constexpr uint64_t kIdBits = kIdTotalBits - kIdProcessBits;
 
-/* static */ uint64_t GenerateProcessSpecificId(uint64_t aId) {
+/* static */
+uint64_t nsContentUtils::GenerateProcessSpecificId(uint64_t aId) {
   uint64_t processId = 0;
   if (XRE_IsContentProcess()) {
     ContentChild* cc = ContentChild::GetSingleton();
     processId = cc->GetID();
   }
 
   MOZ_RELEASE_ASSERT(processId < (uint64_t(1) << kIdProcessBits));
   uint64_t processBits = processId & ((uint64_t(1) << kIdProcessBits) - 1);
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -3089,16 +3089,23 @@ class nsContentUtils {
 
   /**
    * Generate an id for a BrowsingContext using a range of serial
    * numbers reserved for the current process.
    */
   static uint64_t GenerateBrowsingContextId();
 
   /**
+   * Generate an id using a range of serial numbers reserved for the current
+   * process. aId should be a counter that's incremented every time
+   * GenerateProcessSpecificId is called.
+   */
+  static uint64_t GenerateProcessSpecificId(uint64_t aId);
+
+  /**
    * Generate a window ID which is unique across processes and will never be
    * recycled.
    */
   static uint64_t GenerateWindowId();
 
   /**
    * Determine whether or not the user is currently interacting with the web
    * browser. This method is safe to call from off of the main thread.
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -50,16 +50,18 @@
 #include "mozilla/dom/LSObject.h"
 #include "mozilla/dom/MemoryReportRequest.h"
 #include "mozilla/dom/PLoginReputationChild.h"
 #include "mozilla/dom/PSessionStorageObserverChild.h"
 #include "mozilla/dom/PostMessageEvent.h"
 #include "mozilla/dom/PushNotifier.h"
 #include "mozilla/dom/RemoteWorkerService.h"
 #include "mozilla/dom/ServiceWorkerManager.h"
+#include "mozilla/dom/SHEntryChild.h"
+#include "mozilla/dom/SHistoryChild.h"
 #include "mozilla/dom/TabGroup.h"
 #include "mozilla/dom/URLClassifierChild.h"
 #include "mozilla/dom/WindowGlobalChild.h"
 #include "mozilla/dom/WorkerDebugger.h"
 #include "mozilla/dom/WorkerDebuggerManager.h"
 #include "mozilla/dom/ipc/SharedMap.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/gfx/Logging.h"
@@ -3471,16 +3473,50 @@ ContentChild::AllocPSessionStorageObserv
 bool ContentChild::DeallocPSessionStorageObserverChild(
     PSessionStorageObserverChild* aActor) {
   MOZ_ASSERT(aActor);
 
   delete aActor;
   return true;
 }
 
+PSHEntryChild* ContentChild::AllocPSHEntryChild(
+    const PSHEntryOrSharedID& aEntryOrSharedID) {
+  // We take a strong reference for the IPC layer. The Release implementation
+  // for SHEntryChild will ask the IPC layer to release it (through
+  // DeallocPSHEntryChild) if that is the only remaining reference.
+  RefPtr<SHEntryChild> child;
+  if (aEntryOrSharedID.type() == PSHEntryOrSharedID::Tuint64_t) {
+    child = new SHEntryChild(aEntryOrSharedID.get_uint64_t());
+  } else {
+    child = new SHEntryChild(
+        static_cast<const SHEntryChild*>(aEntryOrSharedID.get_PSHEntryChild()));
+  }
+  return child.forget().take();
+}
+
+void ContentChild::DeallocPSHEntryChild(PSHEntryChild* aActor) {
+  // Release the strong reference we took in AllocPSHEntryChild for the IPC
+  // layer.
+  RefPtr<SHEntryChild> child(dont_AddRef(static_cast<SHEntryChild*>(aActor)));
+}
+
+PSHistoryChild* ContentChild::AllocPSHistoryChild(BrowsingContext* aContext) {
+  // We take a strong reference for the IPC layer. The Release implementation
+  // for SHistoryChild will ask the IPC layer to release it (through
+  // DeallocPSHistoryChild) if that is the only remaining reference.
+  return do_AddRef(new SHistoryChild(aContext)).take();
+}
+
+void ContentChild::DeallocPSHistoryChild(PSHistoryChild* aActor) {
+  // Release the strong reference we took in AllocPSHistoryChild for the IPC
+  // layer.
+  RefPtr<SHistoryChild> child(dont_AddRef(static_cast<SHistoryChild*>(aActor)));
+}
+
 mozilla::ipc::IPCResult ContentChild::RecvActivate(PBrowserChild* aTab) {
   BrowserChild* tab = static_cast<BrowserChild*>(aTab);
   return tab->RecvActivate();
 }
 
 mozilla::ipc::IPCResult ContentChild::RecvDeactivate(PBrowserChild* aTab) {
   BrowserChild* tab = static_cast<BrowserChild*>(aTab);
   return tab->RecvDeactivate();
@@ -3711,16 +3747,22 @@ mozilla::ipc::IPCResult ContentChild::Re
   MOZ_ASSERT(aContext);
   nsCOMPtr<nsPIDOMWindowOuter> window = aContext->GetDOMWindow();
   if (window) {
     window->UpdateMediaAction(aAction);
   }
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult ContentChild::RecvDestroySHEntrySharedState(
+    const uint64_t& aID) {
+  SHEntryChildShared::Remove(aID);
+  return IPC_OK();
+}
+
 already_AddRefed<nsIEventTarget> ContentChild::GetSpecificMessageEventTarget(
     const Message& aMsg) {
   switch (aMsg.type()) {
     // Javascript
     case PJavaScript::Msg_DropTemporaryStrongReferences__ID:
     case PJavaScript::Msg_DropObject__ID:
 
     // Navigation
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -619,16 +619,23 @@ class ContentChild final : public PConte
 
   bool DeallocPLoginReputationChild(PLoginReputationChild* aActor);
 
   PSessionStorageObserverChild* AllocPSessionStorageObserverChild();
 
   bool DeallocPSessionStorageObserverChild(
       PSessionStorageObserverChild* aActor);
 
+  PSHEntryChild* AllocPSHEntryChild(const PSHEntryOrSharedID& aEntryOrSharedID);
+  void DeallocPSHEntryChild(PSHEntryChild*);
+
+  PSHistoryChild* AllocPSHistoryChild(BrowsingContext* aContext);
+
+  void DeallocPSHistoryChild(PSHistoryChild* aActor);
+
   nsTArray<LookAndFeelInt>& LookAndFeelCache() { return mLookAndFeelCache; }
 
   /**
    * Helper function for protocols that use the GPU process when available.
    * Overrides FatalError to just be a warning when communicating with the
    * GPU process since we don't want to crash the content process when the
    * GPU process crashes.
    */
@@ -677,16 +684,18 @@ class ContentChild final : public PConte
   uint64_t GetBrowsingContextFieldEpoch() const {
     return mBrowsingContextFieldEpoch;
   }
   uint64_t NextBrowsingContextFieldEpoch() {
     mBrowsingContextFieldEpoch++;
     return mBrowsingContextFieldEpoch;
   }
 
+  mozilla::ipc::IPCResult RecvDestroySHEntrySharedState(const uint64_t& aID);
+
 #ifdef NIGHTLY_BUILD
   // Fetch the current number of pending input events.
   //
   // NOTE: This method performs an atomic read, and is safe to call from all
   // threads.
   uint32_t GetPendingInputEvents() { return mPendingInputEvents; }
 #endif
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -67,16 +67,18 @@
 #include "mozilla/dom/ServiceWorkerRegistrar.h"
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "mozilla/dom/Permissions.h"
 #include "mozilla/dom/PresentationParent.h"
 #include "mozilla/dom/PPresentationParent.h"
 #include "mozilla/dom/PushNotifier.h"
 #include "mozilla/dom/quota/QuotaManagerService.h"
 #include "mozilla/dom/ServiceWorkerUtils.h"
+#include "mozilla/dom/SHEntryParent.h"
+#include "mozilla/dom/SHistoryParent.h"
 #include "mozilla/dom/URLClassifierParent.h"
 #include "mozilla/dom/WindowGlobalParent.h"
 #include "mozilla/dom/ipc/SharedMap.h"
 #include "mozilla/embedding/printingui/PrintingParent.h"
 #include "mozilla/extensions/StreamFilterParent.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/gfx/GPUProcessManager.h"
 #include "mozilla/hal_sandbox/PHalParent.h"
@@ -5694,16 +5696,42 @@ mozilla::ipc::IPCResult ContentParent::R
 bool ContentParent::DeallocPSessionStorageObserverParent(
     PSessionStorageObserverParent* aActor) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aActor);
 
   return mozilla::dom::DeallocPSessionStorageObserverParent(aActor);
 }
 
+PSHEntryParent* ContentParent::AllocPSHEntryParent(
+    const PSHEntryOrSharedID& aEntryOrSharedID) {
+  RefPtr<LegacySHEntry> entry;
+  if (aEntryOrSharedID.type() == PSHEntryOrSharedID::Tuint64_t) {
+    entry = new LegacySHEntry(this, aEntryOrSharedID.get_uint64_t());
+  } else {
+    entry = new LegacySHEntry(*(
+        static_cast<const SHEntryParent*>(aEntryOrSharedID.get_PSHEntryParent())
+            ->mEntry));
+  }
+  return entry->CreateActor();
+}
+
+void ContentParent::DeallocPSHEntryParent(PSHEntryParent* aEntry) {
+  delete static_cast<SHEntryParent*>(aEntry);
+}
+
+PSHistoryParent* ContentParent::AllocPSHistoryParent(
+    BrowsingContext* aContext) {
+  return new SHistoryParent(aContext->Canonical());
+}
+
+void ContentParent::DeallocPSHistoryParent(PSHistoryParent* aActor) {
+  delete static_cast<SHistoryParent*>(aActor);
+}
+
 nsresult ContentParent::SaveRecording(nsIFile* aFile, bool* aRetval) {
   if (mRecordReplayState != eRecording) {
     *aRetval = false;
     return NS_OK;
   }
 
   PRFileDesc* prfd;
   nsresult rv = aFile->OpenNSPRFileDesc(
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -577,16 +577,25 @@ class ContentParent final : public PCont
   PSessionStorageObserverParent* AllocPSessionStorageObserverParent();
 
   virtual mozilla::ipc::IPCResult RecvPSessionStorageObserverConstructor(
       PSessionStorageObserverParent* aActor) override;
 
   bool DeallocPSessionStorageObserverParent(
       PSessionStorageObserverParent* aActor);
 
+  PSHEntryParent* AllocPSHEntryParent(
+      const PSHEntryOrSharedID& aEntryOrSharedID);
+
+  void DeallocPSHEntryParent(PSHEntryParent*);
+
+  PSHistoryParent* AllocPSHistoryParent(BrowsingContext* aContext);
+
+  void DeallocPSHistoryParent(PSHistoryParent* aActor);
+
   bool DeallocPURLClassifierLocalParent(PURLClassifierLocalParent* aActor);
 
   bool DeallocPURLClassifierParent(PURLClassifierParent* aActor);
 
   // Use the PHangMonitor channel to ask the child to repaint a tab.
   void PaintTabWhileInterruptingJS(BrowserParent* aBrowserParent,
                                    const layers::LayersObserverEpoch& aEpoch);
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -41,16 +41,18 @@ include protocol PWindowGlobal;
 include protocol PPresentation;
 include protocol PURLClassifier;
 include protocol PURLClassifierLocal;
 include protocol PVRManager;
 include protocol PRemoteDecoderManager;
 include protocol PProfiler;
 include protocol PScriptCache;
 include protocol PSessionStorageObserver;
+include protocol PSHEntry;
+include protocol PSHistory;
 include protocol PBenchmarkStorage;
 include DOMTypes;
 include JavaScriptTypes;
 include IPCBlob;
 include IPCStream;
 include PTabContext;
 include URIParams;
 include PluginTypes;
@@ -59,16 +61,17 @@ include PBackgroundSharedTypes;
 include PContentPermission;
 include ServiceWorkerConfiguration;
 include GraphicsMessages;
 include MemoryReportTypes;
 include ClientIPCTypes;
 include HangTypes;
 include PrefsTypes;
 include NeckoChannelParams;
+include NewPSHEntry;
 
 #if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
 include protocol PSandboxTesting;
 #endif
 
 using refcounted class nsIDOMGeoPosition from "nsGeoPositionIPCSerialiser.h";
 using refcounted class nsIAlertNotification from "mozilla/AlertNotificationIPCSerializer.h";
 
@@ -360,16 +363,18 @@ nested(upto inside_cpow) sync protocol P
     manages PWebBrowserPersistDocument;
     manages PWebrtcGlobal;
     manages PPresentation;
     manages PURLClassifier;
     manages PURLClassifierLocal;
     manages PScriptCache;
     manages PLoginReputation;
     manages PSessionStorageObserver;
+    manages PSHEntry;
+    manages PSHistory;
     manages PBenchmarkStorage;
 
     // Depending on exactly how the new browser is being created, it might be
     // created from either the child or parent process!
     //
     // The child creates the PBrowser as part of
     // BrowserChild::BrowserFrameProvideWindow (which happens when the child's
     // content calls window.open()), and the parent creates the PBrowser as part
@@ -830,16 +835,18 @@ child:
     // value for every individual BrowsingContext.
     async RegisterBrowsingContextGroup(BrowsingContextInitializer[] aInits);
 
 #if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
     // Initialize top-level actor for testing content process sandbox.
     async InitSandboxTesting(Endpoint<PSandboxTestingChild> aEndpoint);
 #endif
 
+    async DestroySHEntrySharedState(uint64_t aID);
+
 parent:
     async InitBackground(Endpoint<PBackgroundParent> aEndpoint);
 
     sync OpenRecordReplayChannel(uint32_t channelId)
         returns (FileDescriptor connection);
     async CreateReplayingProcess(uint32_t channelId);
     async GenerateReplayCrashReport(uint32_t channelId);
 
@@ -906,16 +913,21 @@ parent:
         returns (bool success);
 
     async PURLClassifierLocal(URIParams uri, IPCURLClassifierFeature[] features);
 
     async PLoginReputation(URIParams formURI);
 
     async PSessionStorageObserver();
 
+    async PSHistory(BrowsingContext aContext);
+
+    // Clone from entry or use shared id.
+    sync PSHEntry(PSHEntryOrSharedID entryOrSharedID);
+
     async PBenchmarkStorage();
 
     // Services remoting
 
     async StartVisitedQuery(URIParams uri);
     async SetURITitle(URIParams uri, nsString title);
 
     async LoadURIExternal(URIParams uri, PBrowser windowContext);
--- a/ipc/glue/ProtocolUtils.h
+++ b/ipc/glue/ProtocolUtils.h
@@ -871,16 +871,18 @@ class ManagedEndpoint {
     aOther.mId = 0;
     return *this;
   }
 
   bool IsValid() const { return mId != 0; }
 
   Maybe<int32_t> ActorId() const { return IsValid() ? Some(mId) : Nothing(); }
 
+  bool operator==(const ManagedEndpoint& _o) const { return mId == _o.mId; }
+
  private:
   friend struct IPC::ParamTraits<ManagedEndpoint<PFooSide>>;
 
   ManagedEndpoint(const ManagedEndpoint&) = delete;
   ManagedEndpoint& operator=(const ManagedEndpoint&) = delete;
 
   // The routing ID for the to-be-created endpoint.
   int32_t mId;
--- a/ipc/ipdl/sync-messages.ini
+++ b/ipc/ipdl/sync-messages.ini
@@ -800,16 +800,212 @@ description =
 description =
 [PPluginScriptableObject::GetChildProperty]
 description =
 [PPluginStream::NPN_Write]
 description =
 [PPluginStream::__delete__]
 description =
 
+# Session history
+[PSHistory::GetCount]
+description = Standing up Fission
+[PSHistory::GetIndex]
+description = Standing up Fission
+[PSHistory::SetIndex]
+description = Standing up Fission
+[PSHistory::GetRequestedIndex]
+description = Standing up Fission
+[PSHistory::InternalSetRequestedIndex]
+description = Standing up Fission
+[PSHistory::GetEntryAtIndex]
+description = Standing up Fission
+[PSHistory::PurgeHistory]
+description = Standing up Fission
+[PSHistory::ReloadCurrentEntry]
+description = Standing up Fission
+[PSHistory::GotoIndex]
+description = Standing up Fission
+[PSHistory::GetIndexOfEntry]
+description = Standing up Fission
+[PSHistory::AddEntry]
+description = Standing up Fission
+[PSHistory::UpdateIndex]
+description = Standing up Fission
+[PSHistory::ReplaceEntry]
+description = Standing up Fission
+[PSHistory::NotifyOnHistoryReload]
+description = Standing up Fission
+[PSHistory::EvictOutOfRangeContentViewers]
+description = Standing up Fission
+[PSHistory::EvictAllContentViewers]
+description = Standing up Fission
+[PSHistory::RemoveDynEntries]
+description = Standing up Fission
+[PSHistory::RemoveEntries]
+description = Standing up Fission
+[PSHistory::Reload]
+description = Standing up Fission
+[PSHistory::GetAllEntries]
+description = Standing up Fission
+[PSHistory::FindEntryForBFCache]
+description = Standing up Fission
+[PSHistory::Evict]
+description = Standing up Fission
+[PContent::PSHEntry]
+description = Standing up Fission
+[PSHEntry::GetURI]
+description = Standing up Fission
+[PSHEntry::SetURI]
+description = Standing up Fission
+[PSHEntry::GetOriginalURI]
+description = Standing up Fission
+[PSHEntry::SetOriginalURI]
+description = Standing up Fission
+[PSHEntry::GetResultPrincipalURI]
+description = Standing up Fission
+[PSHEntry::SetResultPrincipalURI]
+description = Standing up Fission
+[PSHEntry::GetLoadReplace]
+description = Standing up Fission
+[PSHEntry::SetLoadReplace]
+description = Standing up Fission
+[PSHEntry::GetTitle]
+description = Standing up Fission
+[PSHEntry::SetTitle]
+description = Standing up Fission
+[PSHEntry::GetIsSubFrame]
+description = Standing up Fission
+[PSHEntry::SetIsSubFrame]
+description = Standing up Fission
+[PSHEntry::GetReferrerInfo]
+description = Standing up Fission
+[PSHEntry::SetReferrerInfo]
+description = Standing up Fission
+[PSHEntry::GetSticky]
+description = Standing up Fission
+[PSHEntry::SetSticky]
+description = Standing up Fission
+[PSHEntry::GetPostData]
+description = Standing up Fission
+[PSHEntry::SetPostData]
+description = Standing up Fission
+[PSHEntry::GetParent]
+description = Standing up Fission
+[PSHEntry::SetParent]
+description = Standing up Fission
+[PSHEntry::GetLoadType]
+description = Standing up Fission
+[PSHEntry::SetLoadType]
+description = Standing up Fission
+[PSHEntry::GetID]
+description = Standing up Fission
+[PSHEntry::SetID]
+description = Standing up Fission
+[PSHEntry::GetCacheKey]
+description = Standing up Fission
+[PSHEntry::SetCacheKey]
+description = Standing up Fission
+[PSHEntry::GetExpirationStatus]
+description = Standing up Fission
+[PSHEntry::SetExpirationStatus]
+description = Standing up Fission
+[PSHEntry::GetContentType]
+description = Standing up Fission
+[PSHEntry::SetContentType]
+description = Standing up Fission
+[PSHEntry::GetURIWasModified]
+description = Standing up Fission
+[PSHEntry::SetURIWasModified]
+description = Standing up Fission
+[PSHEntry::GetTriggeringPrincipal]
+description = Standing up Fission
+[PSHEntry::SetTriggeringPrincipal]
+description = Standing up Fission
+[PSHEntry::GetPrincipalToInherit]
+description = Standing up Fission
+[PSHEntry::SetPrincipalToInherit]
+description = Standing up Fission
+[PSHEntry::GetStoragePrincipalToInherit]
+description = Standing up Fission
+[PSHEntry::SetStoragePrincipalToInherit]
+description = Standing up Fission
+[PSHEntry::GetCsp]
+description = Standing up Fission
+[PSHEntry::SetCsp]
+description = Standing up Fission
+[PSHEntry::GetStateData]
+description = Standing up Fission
+[PSHEntry::SetStateData]
+description = Standing up Fission
+[PSHEntry::GetDocshellID]
+description = Standing up Fission
+[PSHEntry::SetDocshellID]
+description = Standing up Fission
+[PSHEntry::GetIsSrcdocEntry]
+description = Standing up Fission
+[PSHEntry::GetSrcdocData]
+description = Standing up Fission
+[PSHEntry::SetSrcdocData]
+description = Standing up Fission
+[PSHEntry::GetBaseURI]
+description = Standing up Fission
+[PSHEntry::SetBaseURI]
+description = Standing up Fission
+[PSHEntry::GetScrollRestorationIsManual]
+description = Standing up Fission
+[PSHEntry::SetScrollRestorationIsManual]
+description = Standing up Fission
+[PSHEntry::GetLoadedInThisProcess]
+description = Standing up Fission
+[PSHEntry::GetLastTouched]
+description = Standing up Fission
+[PSHEntry::SetLastTouched]
+description = Standing up Fission
+[PSHEntry::GetChildCount]
+description = Standing up Fission
+[PSHEntry::GetPersist]
+description = Standing up Fission
+[PSHEntry::SetPersist]
+description = Standing up Fission
+[PSHEntry::SetScrollPosition]
+description = Standing up Fission
+[PSHEntry::GetScrollPosition]
+description = Standing up Fission
+[PSHEntry::GetViewerBounds]
+description = Standing up Fission
+[PSHEntry::SetViewerBounds]
+description = Standing up Fission
+[PSHEntry::Create]
+description = Standing up Fission
+[PSHEntry::HasDetachedEditor]
+description = Standing up Fission
+[PSHEntry::IsDynamicallyAdded]
+description = Standing up Fission
+[PSHEntry::HasDynamicallyAddedChild]
+description = Standing up Fission
+[PSHEntry::AdoptBFCacheEntry]
+description = Standing up Fission
+[PSHEntry::AbandonBFCacheEntry]
+description = Standing up Fission
+[PSHEntry::SharesDocumentWith]
+description = Standing up Fission
+[PSHEntry::SetLoadTypeAsHistory]
+description = Standing up Fission
+[PSHEntry::AddChild]
+description = Standing up Fission
+[PSHEntry::RemoveChild]
+description = Standing up Fission
+[PSHEntry::GetChildAt]
+description = Standing up Fission
+[PSHEntry::ReplaceChild]
+description = Standing up Fission
+[PSHEntry::__delete__]
+description = Standing up Fission
+
 # The rest
 [PHeapSnapshotTempFileHelper::OpenHeapSnapshotTempFile]
 description =
 [PBackgroundMutableFile::GetFileId]
 description =
 [PBackgroundIndexedDBUtils::GetFileReferences]
 description =
 [PBrowser::SyncMessage]
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -2830,16 +2830,23 @@
 # This pref has no effect within fission windows, it only controls the
 # behaviour within non-fission windows. If true, preserve browsing contexts
 # between process swaps. Should be set to true in bug 1550571.
 - name: fission.preserve_browsing_contexts
   type: bool
   value: false
   mirror: always
 
+# Store the session history in the parent process, and access it over IPC
+# from the child processes.
+- name: fission.sessionHistoryInParent
+  type: bool
+  value: false
+  mirror: always
+
 #---------------------------------------------------------------------------
 # Prefs starting with "font."
 #---------------------------------------------------------------------------
 
 # A value greater than zero enables font size inflation for
 # pan-and-zoom UIs, so that the fonts in a block are at least the size
 # that, if a block's width is scaled to match the device's width, the
 # fonts in the block are big enough that at most the pref value ems of
--- a/toolkit/modules/sessionstore/SessionHistory.jsm
+++ b/toolkit/modules/sessionstore/SessionHistory.jsm
@@ -257,18 +257,19 @@ var SessionHistoryInternal = {
 
     if (shEntry.csp) {
       entry.csp = E10SUtils.serializeCSP(shEntry.csp);
     }
 
     entry.docIdentifier = shEntry.BFCacheEntry.ID;
 
     if (shEntry.stateData != null) {
-      entry.structuredCloneState = shEntry.stateData.getDataAsBase64();
-      entry.structuredCloneVersion = shEntry.stateData.formatVersion;
+      let stateData = shEntry.stateData;
+      entry.structuredCloneState = stateData.getDataAsBase64();
+      entry.structuredCloneVersion = stateData.formatVersion;
     }
 
     if (shEntry.childCount > 0 && !shEntry.hasDynamicallyAddedChild()) {
       let children = [];
       for (let i = 0; i < shEntry.childCount; i++) {
         let child = shEntry.GetChildAt(i);
 
         if (child) {
@@ -462,24 +463,25 @@ var SessionHistoryInternal = {
       delete entry.docshellID;
     }
 
     if (entry.docshellUUID) {
       shEntry.docshellID = Components.ID(entry.docshellUUID);
     }
 
     if (entry.structuredCloneState && entry.structuredCloneVersion) {
-      shEntry.stateData = Cc[
+      var stateData = Cc[
         "@mozilla.org/docshell/structured-clone-container;1"
       ].createInstance(Ci.nsIStructuredCloneContainer);
 
-      shEntry.stateData.initFromBase64(
+      stateData.initFromBase64(
         entry.structuredCloneState,
         entry.structuredCloneVersion
       );
+      shEntry.stateData = stateData;
     }
 
     if (entry.scrollRestorationIsManual) {
       shEntry.scrollRestorationIsManual = true;
     } else {
       if (entry.scroll) {
         shEntry.setScrollPosition(
           ...this._deserializeScrollPosition(entry.scroll)
--- a/xpcom/base/nsISupportsImpl.h
+++ b/xpcom/base/nsISupportsImpl.h
@@ -535,46 +535,67 @@ typedef ThreadSafeAutoRefCntWithRecordin
 
 /**
  * Use this macro to declare and implement the AddRef & Release methods for a
  * given non-XPCOM <i>_class</i>.
  *
  * @param _class The name of the class implementing the method
  * @param _destroy A statement that is executed when the object's
  *   refcount drops to zero.
+ * @param _decl Name of the macro to be used for the return type of the
+ *   AddRef & Releas methods (typically NS_IMETHOD_ or NS_METHOD_).
+ * @param optional override Mark the AddRef & Release methods as overrides.
+ */
+#define NS_INLINE_DECL_REFCOUNTING_META(_class, _decl, _destroy, ...) \
+ public:                                                              \
+  _decl(MozExternalRefCountType) AddRef(void) __VA_ARGS__ {           \
+    MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class)                        \
+    MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt");              \
+    NS_ASSERT_OWNINGTHREAD(_class);                                   \
+    ++mRefCnt;                                                        \
+    NS_LOG_ADDREF(this, mRefCnt, #_class, sizeof(*this));             \
+    return mRefCnt;                                                   \
+  }                                                                   \
+  _decl(MozExternalRefCountType) Release(void) __VA_ARGS__ {          \
+    MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");                  \
+    NS_ASSERT_OWNINGTHREAD(_class);                                   \
+    --mRefCnt;                                                        \
+    NS_LOG_RELEASE(this, mRefCnt, #_class);                           \
+    if (mRefCnt == 0) {                                               \
+      mRefCnt = 1; /* stabilize */                                    \
+      _destroy;                                                       \
+      return 0;                                                       \
+    }                                                                 \
+    return mRefCnt;                                                   \
+  }                                                                   \
+  typedef mozilla::FalseType HasThreadSafeRefCnt;                     \
+                                                                      \
+ protected:                                                           \
+  nsAutoRefCnt mRefCnt;                                               \
+  NS_DECL_OWNINGTHREAD                                                \
+ public:
+
+/**
+ * Use this macro to declare and implement the AddRef & Release methods for a
+ * given non-XPCOM <i>_class</i>.
+ *
+ * @param _class The name of the class implementing the method
+ * @param _destroy A statement that is executed when the object's
+ *   refcount drops to zero.
  * @param optional override Mark the AddRef & Release methods as overrides.
  */
 #define NS_INLINE_DECL_REFCOUNTING_WITH_DESTROY(_class, _destroy, ...) \
- public:                                                               \
-  NS_METHOD_(MozExternalRefCountType) AddRef(void) __VA_ARGS__ {       \
-    MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class)                         \
-    MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt");               \
-    NS_ASSERT_OWNINGTHREAD(_class);                                    \
-    ++mRefCnt;                                                         \
-    NS_LOG_ADDREF(this, mRefCnt, #_class, sizeof(*this));              \
-    return mRefCnt;                                                    \
-  }                                                                    \
-  NS_METHOD_(MozExternalRefCountType) Release(void) __VA_ARGS__ {      \
-    MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");                   \
-    NS_ASSERT_OWNINGTHREAD(_class);                                    \
-    --mRefCnt;                                                         \
-    NS_LOG_RELEASE(this, mRefCnt, #_class);                            \
-    if (mRefCnt == 0) {                                                \
-      mRefCnt = 1; /* stabilize */                                     \
-      _destroy;                                                        \
-      return 0;                                                        \
-    }                                                                  \
-    return mRefCnt;                                                    \
-  }                                                                    \
-  typedef mozilla::FalseType HasThreadSafeRefCnt;                      \
-                                                                       \
- protected:                                                            \
-  nsAutoRefCnt mRefCnt;                                                \
-  NS_DECL_OWNINGTHREAD                                                 \
- public:
+  NS_INLINE_DECL_REFCOUNTING_META(_class, NS_METHOD_, _destroy, __VA_ARGS__)
+
+/**
+ * Like NS_INLINE_DECL_REFCOUNTING_WITH_DESTROY with AddRef & Release declared
+ * virtual.
+ */
+#define NS_INLINE_DECL_VIRTUAL_REFCOUNTING_WITH_DESTROY(_class, _destroy, ...) \
+  NS_INLINE_DECL_REFCOUNTING_META(_class, NS_IMETHOD_, _destroy, __VA_ARGS__)
 
 /**
  * Use this macro to declare and implement the AddRef & Release methods for a
  * given non-XPCOM <i>_class</i>.
  *
  * @param _class The name of the class implementing the method
  * @param optional override Mark the AddRef & Release methods as overrides.
  */