Bug 1593246 - Part 5: Replicate session storage data r=janv,asuth
authorYaron Tausky <ytausky@mozilla.com>
Fri, 13 Dec 2019 09:45:55 +0000
changeset 506853 aea23435804b56798dbae8fa46dfccb8cbdca131
parent 506852 da1672f831d1643342bf1098723f0f8d750fd8a3
child 506854 d32da5fc94823e270bd0ac201bb65dafa6b470b9
push id36913
push useropoprus@mozilla.com
push dateFri, 13 Dec 2019 16:53:24 +0000
treeherdermozilla-central@1ed684598bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjanv, asuth
bugs1593246
milestone73.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1593246 - Part 5: Replicate session storage data r=janv,asuth This commit implements a simple data replication scheme, where each content process receives a copy of the relevant session storage data when navigating, and sending all its session storage data to the parent process before shutting down. Differential Revision: https://phabricator.services.mozilla.com/D55662
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
dom/storage/SessionStorageCache.cpp
dom/storage/SessionStorageCache.h
dom/storage/SessionStorageManager.cpp
dom/storage/SessionStorageManager.h
dom/storage/SessionStorageService.cpp
dom/storage/SessionStorageService.h
dom/storage/moz.build
dom/tests/mochitest/sessionstorage/mochitest.ini
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -3801,16 +3801,25 @@ mozilla::ipc::IPCResult ContentChild::Re
 }
 
 mozilla::ipc::IPCResult ContentChild::RecvEvictContentViewers(
     nsTArray<uint64_t>&& aToEvictSharedStateIDs) {
   SHEntryChildShared::EvictContentViewers(std::move(aToEvictSharedStateIDs));
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult ContentChild::RecvSessionStorageData(
+    BrowsingContext* const aTop, const nsACString& aOriginAttrs,
+    const nsACString& aOriginKey, const nsTArray<KeyValuePair>& aDefaultData,
+    const nsTArray<KeyValuePair>& aSessionData) {
+  aTop->GetSessionStorageManager()->LoadSessionStorageData(
+      nullptr, aOriginAttrs, aOriginKey, aDefaultData, aSessionData);
+  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
@@ -688,16 +688,21 @@ class ContentChild final
     return mBrowsingContextFieldEpoch;
   }
 
   mozilla::ipc::IPCResult RecvDestroySHEntrySharedState(const uint64_t& aID);
 
   mozilla::ipc::IPCResult RecvEvictContentViewers(
       nsTArray<uint64_t>&& aToEvictSharedStateIDs);
 
+  mozilla::ipc::IPCResult RecvSessionStorageData(
+      BrowsingContext* aTop, const nsACString& aOriginAttrs,
+      const nsACString& aOriginKey, const nsTArray<KeyValuePair>& aDefaultData,
+      const nsTArray<KeyValuePair>& aSessionData);
+
 #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
@@ -5393,16 +5393,29 @@ nsresult ContentParent::AboutToLoadHttpF
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsLoadFlags newLoadFlags;
   aChannel->GetLoadFlags(&newLoadFlags);
   if (newLoadFlags & nsIRequest::LOAD_DOCUMENT_NEEDS_COOKIE) {
     UpdateCookieStatus(aChannel);
   }
 
+  RefPtr<nsILoadInfo> loadInfo;
+  rv = aChannel->GetLoadInfo(getter_AddRefs(loadInfo));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  RefPtr<BrowsingContext> browsingContext;
+  rv = loadInfo->GetBrowsingContext(getter_AddRefs(browsingContext));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (browsingContext && !browsingContext->IsDiscarded()) {
+    browsingContext->GetSessionStorageManager()
+        ->SendSessionStorageDataToContentProcess(this, principal);
+  }
+
   if (!NextGenLocalStorageEnabled()) {
     return NS_OK;
   }
 
   if (principal->GetIsContentPrincipal()) {
     nsCOMPtr<nsILocalStorageManager> lsm =
         do_GetService("@mozilla.org/dom/localStorage-manager;1");
     if (NS_WARN_IF(!lsm)) {
@@ -5805,16 +5818,25 @@ mozilla::ipc::IPCResult ContentParent::R
           },
           [aResolver](nsresult aRv) { aResolver(Nothing()); });
   return IPC_OK();
 #else
   return IPC_FAIL(this, "Unsupported on this platform");
 #endif  // defined(XP_WIN)
 }
 
+mozilla::ipc::IPCResult ContentParent::RecvSessionStorageData(
+    BrowsingContext* const aTop, const nsACString& aOriginAttrs,
+    const nsACString& aOriginKey, const nsTArray<KeyValuePair>& aDefaultData,
+    const nsTArray<KeyValuePair>& aSessionData) {
+  aTop->GetSessionStorageManager()->LoadSessionStorageData(
+      this, aOriginAttrs, aOriginKey, aDefaultData, aSessionData);
+  return IPC_OK();
+}
+
 mozilla::ipc::IPCResult ContentParent::RecvAttachBrowsingContext(
     BrowsingContext::IPCInitializer&& aInit) {
   RefPtr<CanonicalBrowsingContext> parent;
   if (aInit.mParentId != 0) {
     parent = CanonicalBrowsingContext::Get(aInit.mParentId);
     MOZ_RELEASE_ASSERT(parent, "Parent doesn't exist in parent process");
   }
 
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -1205,16 +1205,21 @@ class ContentParent final
 
   mozilla::ipc::IPCResult RecvNotifyMediaAudibleChanged(
       BrowsingContext* aContext, bool aAudible);
 
   mozilla::ipc::IPCResult RecvGetModulesTrust(
       ModulePaths&& aModPaths, bool aRunAtNormalPriority,
       GetModulesTrustResolver&& aResolver);
 
+  mozilla::ipc::IPCResult RecvSessionStorageData(
+      BrowsingContext* aTop, const nsACString& aOriginAttrs,
+      const nsACString& aOriginKey, const nsTArray<KeyValuePair>& aDefaultData,
+      const nsTArray<KeyValuePair>& aSessionData);
+
   // Notify the ContentChild to enable the input event prioritization when
   // initializing.
   void MaybeEnableRemoteInputEventQueue();
 
 #if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
   void AppendSandboxParams(std::vector<std::string>& aArgs);
   void AppendDynamicSandboxParams(std::vector<std::string>& aArgs);
 #endif
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -338,16 +338,22 @@ struct PostMessageData
 };
 
 union PSHEntryOrSharedID
 {
   PSHEntry;
   uint64_t;
 };
 
+struct KeyValuePair
+{
+    nsString key;
+    nsString value;
+};
+
 /**
  * The PContent protocol is a top-level protocol between the UI process
  * and a content process. There is exactly one PContentParent/PContentChild pair
  * for each content process.
  */
 nested(upto inside_cpow) sync protocol PContent
 {
     manages PBrowser;
@@ -1568,12 +1574,20 @@ both:
     async RestoreBrowsingContextChildren(BrowsingContext aContext,
                                          BrowsingContext[] aChildren);
 
     async WindowClose(BrowsingContext aContext, bool aTrustedCaller);
     async WindowFocus(BrowsingContext aContext);
     async WindowBlur(BrowsingContext aContext);
     async WindowPostMessage(BrowsingContext aContext, ClonedMessageData aMessage,
                             PostMessageData aData);
+
+    /**
+     * Move sessionStorage data between parent and content processes. See
+     * SessionStorageManager documentation for more details.
+     */
+    async SessionStorageData(BrowsingContext aTop, nsCString aOriginAttrs,
+                             nsCString aOriginKey, KeyValuePair[] aDefaultData,
+                             KeyValuePair[] aSessionData);
 };
 
 }
 }
--- a/dom/storage/SessionStorageCache.cpp
+++ b/dom/storage/SessionStorageCache.cpp
@@ -1,16 +1,18 @@
 /* -*- 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 "SessionStorageCache.h"
 
+#include "mozilla/dom/PContent.h"
+
 namespace mozilla {
 namespace dom {
 
 SessionStorageCache::SessionStorageCache() = default;
 
 SessionStorageCache::DataSet* SessionStorageCache::Set(
     DataSetType aDataSetType) {
   if (aDataSetType == eDefaultSetType) {
@@ -124,16 +126,37 @@ already_AddRefed<SessionStorageCache> Se
   cache->mSessionSet.mOriginQuotaUsage = mSessionSet.mOriginQuotaUsage;
   for (auto iter = mSessionSet.mKeys.ConstIter(); !iter.Done(); iter.Next()) {
     cache->mSessionSet.mKeys.Put(iter.Key(), iter.Data());
   }
 
   return cache.forget();
 }
 
+nsTArray<KeyValuePair> SessionStorageCache::SerializeData(
+    DataSetType aDataSetType) {
+  nsTArray<KeyValuePair> data;
+  for (auto iter = Set(aDataSetType)->mKeys.Iter(); !iter.Done(); iter.Next()) {
+    KeyValuePair keyValuePair;
+    keyValuePair.key() = iter.Key();
+    keyValuePair.value() = iter.Data();
+    data.EmplaceBack(std::move(keyValuePair));
+  }
+  return data;
+}
+
+void SessionStorageCache::DeserializeData(DataSetType aDataSetType,
+                                          const nsTArray<KeyValuePair>& aData) {
+  Clear(aDataSetType, false);
+  for (const auto& keyValuePair : aData) {
+    nsString oldValue;
+    SetItem(aDataSetType, keyValuePair.key(), keyValuePair.value(), oldValue);
+  }
+}
+
 bool SessionStorageCache::DataSet::ProcessUsageDelta(int64_t aDelta) {
   // Check limit per this origin
   uint64_t newOriginUsage = mOriginQuotaUsage + aDelta;
   if (aDelta > 0 && newOriginUsage > LocalStorageManager::GetQuota()) {
     return false;
   }
 
   // Update size in our data set
--- a/dom/storage/SessionStorageCache.h
+++ b/dom/storage/SessionStorageCache.h
@@ -7,16 +7,18 @@
 #ifndef mozilla_dom_SessionStorageCache_h
 #define mozilla_dom_SessionStorageCache_h
 
 #include "nsDataHashtable.h"
 
 namespace mozilla {
 namespace dom {
 
+class KeyValuePair;
+
 class SessionStorageCache final {
  public:
   NS_INLINE_DECL_REFCOUNTING(SessionStorageCache)
 
   SessionStorageCache();
 
   enum DataSetType {
     eDefaultSetType,
@@ -39,16 +41,20 @@ class SessionStorageCache final {
 
   nsresult RemoveItem(DataSetType aDataSetType, const nsAString& aKey,
                       nsString& aOldValue);
 
   void Clear(DataSetType aDataSetType, bool aByUserInteraction = true);
 
   already_AddRefed<SessionStorageCache> Clone() const;
 
+  nsTArray<KeyValuePair> SerializeData(DataSetType aDataSetType);
+  void DeserializeData(DataSetType aDataSetType,
+                       const nsTArray<KeyValuePair>& aData);
+
  private:
   ~SessionStorageCache() = default;
 
   struct DataSet {
     DataSet() : mOriginQuotaUsage(0) {}
 
     bool ProcessUsageDelta(int64_t aDelta);
 
--- a/dom/storage/SessionStorageManager.cpp
+++ b/dom/storage/SessionStorageManager.cpp
@@ -2,19 +2,21 @@
 /* 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 "SessionStorageManager.h"
 
 #include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
 #include "SessionStorage.h"
 #include "SessionStorageCache.h"
 #include "SessionStorageObserver.h"
+#include "SessionStorageService.h"
 #include "StorageUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 using namespace StorageUtils;
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SessionStorageManager)
@@ -25,16 +27,20 @@ NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTION(SessionStorageManager, mBrowsingContext)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(SessionStorageManager)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(SessionStorageManager)
 
 SessionStorageManager::SessionStorageManager(
     RefPtr<BrowsingContext> aBrowsingContext)
     : mBrowsingContext(aBrowsingContext.forget()) {
+  if (const auto service = SessionStorageService::Get()) {
+    service->RegisterSessionStorageManager(this);
+  }
+
   StorageObserver* observer = StorageObserver::Self();
   NS_ASSERTION(
       observer,
       "No StorageObserver, cannot observe private data delete notifications!");
 
   if (observer) {
     observer->AddSink(this);
   }
@@ -64,16 +70,20 @@ SessionStorageManager::SessionStorageMan
   }
 }
 
 SessionStorageManager::~SessionStorageManager() {
   StorageObserver* observer = StorageObserver::Self();
   if (observer) {
     observer->RemoveSink(this);
   }
+
+  if (const auto service = SessionStorageService::Get()) {
+    service->UnregisterSessionStorageManager(this);
+  }
 }
 
 NS_IMETHODIMP
 SessionStorageManager::PrecacheStorage(nsIPrincipal* aPrincipal,
                                        nsIPrincipal* aStoragePrincipal,
                                        Storage** aRetval) {
   // Nothing to preload.
   return NS_OK;
@@ -99,45 +109,54 @@ nsresult SessionStorageManager::GetSessi
   return GetSessionStorageCacheHelper(originAttributes, originKey,
                                       aMakeIfNeeded, aCloneFrom, aRetVal);
 }
 
 nsresult SessionStorageManager::GetSessionStorageCacheHelper(
     const nsACString& aOriginAttrs, const nsACString& aOriginKey,
     bool aMakeIfNeeded, SessionStorageCache* aCloneFrom,
     RefPtr<SessionStorageCache>* aRetVal) {
-  *aRetVal = nullptr;
+  if (OriginRecord* const originRecord = GetOriginRecord(
+          aOriginAttrs, aOriginKey, aMakeIfNeeded, aCloneFrom)) {
+    *aRetVal = originRecord->mCache;
+  } else {
+    *aRetVal = nullptr;
+  }
+  return NS_OK;
+}
 
+SessionStorageManager::OriginRecord* SessionStorageManager::GetOriginRecord(
+    const nsACString& aOriginAttrs, const nsACString& aOriginKey,
+    const bool aMakeIfNeeded, SessionStorageCache* const aCloneFrom) {
   OriginKeyHashTable* table;
   if (!mOATable.Get(aOriginAttrs, &table)) {
     if (aMakeIfNeeded) {
       table = new OriginKeyHashTable();
       mOATable.Put(aOriginAttrs, table);
     } else {
-      return NS_OK;
+      return nullptr;
     }
   }
 
-  RefPtr<SessionStorageCache> cache;
-  if (!table->Get(aOriginKey, getter_AddRefs(cache))) {
+  OriginRecord* originRecord;
+  if (!table->Get(aOriginKey, &originRecord)) {
     if (aMakeIfNeeded) {
+      originRecord = new OriginRecord();
       if (aCloneFrom) {
-        cache = aCloneFrom->Clone();
+        originRecord->mCache = aCloneFrom->Clone();
       } else {
-        cache = new SessionStorageCache();
+        originRecord->mCache = new SessionStorageCache();
       }
-      table->Put(aOriginKey, cache);
+      table->Put(aOriginKey, originRecord);
     } else {
-      return NS_OK;
+      return nullptr;
     }
   }
 
-  *aRetVal = std::move(cache);
-
-  return NS_OK;
+  return originRecord;
 }
 
 NS_IMETHODIMP
 SessionStorageManager::CreateStorage(mozIDOMWindow* aWindow,
                                      nsIPrincipal* aPrincipal,
                                      nsIPrincipal* aStoragePrincipal,
                                      const nsAString& aDocumentURI,
                                      bool aPrivate, Storage** aRetval) {
@@ -245,28 +264,97 @@ void SessionStorageManager::ClearStorage
       // This table doesn't match the given origin attributes pattern
       continue;
     }
 
     OriginKeyHashTable* table = iter1.Data();
     for (auto iter2 = table->Iter(); !iter2.Done(); iter2.Next()) {
       if (aOriginScope.IsEmpty() ||
           StringBeginsWith(iter2.Key(), aOriginScope)) {
+        const auto cache = iter2.Data()->mCache;
         if (aType == eAll) {
-          iter2.Data()->Clear(SessionStorageCache::eDefaultSetType, false);
-          iter2.Data()->Clear(SessionStorageCache::eSessionSetType, false);
+          cache->Clear(SessionStorageCache::eDefaultSetType, false);
+          cache->Clear(SessionStorageCache::eSessionSetType, false);
         } else {
           MOZ_ASSERT(aType == eSessionOnly);
-          iter2.Data()->Clear(SessionStorageCache::eSessionSetType, false);
+          cache->Clear(SessionStorageCache::eSessionSetType, false);
         }
       }
     }
   }
 }
 
+void SessionStorageManager::SendSessionStorageDataToParentProcess() {
+  if (!mBrowsingContext || mBrowsingContext->IsDiscarded()) {
+    return;
+  }
+
+  for (auto oaIter = mOATable.Iter(); !oaIter.Done(); oaIter.Next()) {
+    for (auto originIter = oaIter.Data()->Iter(); !originIter.Done();
+         originIter.Next()) {
+      SendSessionStorageCache(ContentChild::GetSingleton(), oaIter.Key(),
+                              originIter.Key(), originIter.Data()->mCache);
+    }
+  }
+}
+
+void SessionStorageManager::SendSessionStorageDataToContentProcess(
+    ContentParent* const aActor, nsIPrincipal* const aPrincipal) {
+  nsAutoCString originAttrs;
+  nsAutoCString originKey;
+  auto rv = GenerateOriginKey(aPrincipal, originAttrs, originKey);
+  if (NS_FAILED(rv)) {
+    return;
+  }
+
+  const auto originRecord =
+      GetOriginRecord(originAttrs, originKey, false, nullptr);
+  if (!originRecord) {
+    return;
+  }
+
+  const auto id = aActor->ChildID();
+  if (!originRecord->mKnownTo.Contains(id)) {
+    originRecord->mKnownTo.PutEntry(id);
+    SendSessionStorageCache(aActor, originAttrs, originKey,
+                            originRecord->mCache);
+  }
+}
+
+template <typename Actor>
+void SessionStorageManager::SendSessionStorageCache(
+    Actor* const aActor, const nsACString& aOriginAttrs,
+    const nsACString& aOriginKey, SessionStorageCache* const aCache) {
+  nsTArray<KeyValuePair> defaultData =
+      aCache->SerializeData(SessionStorageCache::eDefaultSetType);
+  nsTArray<KeyValuePair> sessionData =
+      aCache->SerializeData(SessionStorageCache::eSessionSetType);
+  Unused << aActor->SendSessionStorageData(
+      mBrowsingContext, nsCString{aOriginAttrs}, nsCString{aOriginKey},
+      defaultData, sessionData);
+}
+
+void SessionStorageManager::LoadSessionStorageData(
+    ContentParent* const aSource, const nsACString& aOriginAttrs,
+    const nsACString& aOriginKey, const nsTArray<KeyValuePair>& aDefaultData,
+    const nsTArray<KeyValuePair>& aSessionData) {
+  const auto originRecord =
+      GetOriginRecord(aOriginAttrs, aOriginKey, true, nullptr);
+  MOZ_ASSERT(originRecord);
+
+  if (aSource) {
+    originRecord->mKnownTo.RemoveEntry(aSource->ChildID());
+  }
+
+  originRecord->mCache->DeserializeData(SessionStorageCache::eDefaultSetType,
+                                        aDefaultData);
+  originRecord->mCache->DeserializeData(SessionStorageCache::eSessionSetType,
+                                        aSessionData);
+}
+
 nsresult SessionStorageManager::Observe(
     const char* aTopic, const nsAString& aOriginAttributesPattern,
     const nsACString& aOriginScope) {
   OriginAttributesPattern pattern;
   if (!pattern.Init(aOriginAttributesPattern)) {
     NS_ERROR("Cannot parse origin attributes pattern");
     return NS_ERROR_FAILURE;
   }
--- a/dom/storage/SessionStorageManager.h
+++ b/dom/storage/SessionStorageManager.h
@@ -4,40 +4,72 @@
  * 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_SessionStorageManager_h
 #define mozilla_dom_SessionStorageManager_h
 
 #include "nsIDOMStorageManager.h"
 #include "nsClassHashtable.h"
+#include "nsHashKeys.h"
 #include "nsRefPtrHashtable.h"
 #include "StorageObserver.h"
 
 namespace mozilla {
 namespace dom {
 
+class ContentParent;
+class KeyValuePair;
 class SessionStorageCache;
 class SessionStorageObserver;
 
+// sessionStorage is a data store that's unique to each tab (i.e. top-level
+// browsing context) and origin. Before the advent of Fission all the data
+// for a given tab could be stored in a single content process; now each
+// site-specific process stores only its portion of the data. As content
+// processes terminate, their sessionStorage data needs to be saved in the
+// parent process, in case the same origin appears again in the tab (e.g.
+// by navigating an iframe element). Therefore SessionStorageManager
+// objects exist in both the parent and content processes.
+//
+// Whenever a content process terminates it sends its sessionStorage data
+// to the parent process (see SessionStorageService); whenever a content
+// process navigates to an origin for the first time in a given tab, the
+// parent process sends it the saved data. To avoid sending the data
+// multiple times, the parent process maintains a table of content
+// processes that already received it; this table is empty in content
+// processes.
+//
+// Note: the current implementation is expected to be replaced by a new
+// implementation using LSNG.
 class SessionStorageManager final : public nsIDOMSessionStorageManager,
                                     public StorageObserverSink {
  public:
   explicit SessionStorageManager(RefPtr<BrowsingContext> aBrowsingContext);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_NSIDOMSTORAGEMANAGER
   NS_DECL_NSIDOMSESSIONSTORAGEMANAGER
 
   NS_DECL_CYCLE_COLLECTION_CLASS(SessionStorageManager)
 
   RefPtr<BrowsingContext> GetBrowsingContext() const {
     return mBrowsingContext;
   }
 
+  void SendSessionStorageDataToParentProcess();
+  void SendSessionStorageDataToContentProcess(ContentParent* aActor,
+                                              nsIPrincipal* aPrincipal);
+
+  void LoadSessionStorageData(ContentParent* aSource,
+                              const nsACString& aOriginAttrs,
+                              const nsACString& aOriginKey,
+                              const nsTArray<KeyValuePair>& aDefaultData,
+                              const nsTArray<KeyValuePair>& aSessionData);
+
  private:
   ~SessionStorageManager();
 
   // StorageObserverSink, handler to various chrome clearing notification
   nsresult Observe(const char* aTopic,
                    const nsAString& aOriginAttributesPattern,
                    const nsACString& aOriginScope) override;
 
@@ -56,18 +88,32 @@ class SessionStorageManager final : publ
                                         RefPtr<SessionStorageCache>* aRetVal);
 
   nsresult GetSessionStorageCacheHelper(const nsACString& aOriginAttrs,
                                         const nsACString& aOriginKey,
                                         bool aMakeIfNeeded,
                                         SessionStorageCache* aCloneFrom,
                                         RefPtr<SessionStorageCache>* aRetVal);
 
-  typedef nsRefPtrHashtable<nsCStringHashKey, SessionStorageCache>
-      OriginKeyHashTable;
+  struct OriginRecord {
+    RefPtr<SessionStorageCache> mCache;
+    nsTHashtable<nsUint64HashKey> mKnownTo;
+  };
+
+  OriginRecord* GetOriginRecord(const nsACString& aOriginAttrs,
+                                const nsACString& aOriginKey,
+                                bool aMakeIfNeeded,
+                                SessionStorageCache* aCloneFrom);
+
+  template <typename Actor>
+  void SendSessionStorageCache(Actor* aActor, const nsACString& aOriginAttrs,
+                               const nsACString& aOriginKey,
+                               SessionStorageCache* aCache);
+
+  using OriginKeyHashTable = nsClassHashtable<nsCStringHashKey, OriginRecord>;
   nsClassHashtable<nsCStringHashKey, OriginKeyHashTable> mOATable;
 
   RefPtr<SessionStorageObserver> mObserver;
 
   RefPtr<BrowsingContext> mBrowsingContext;
 };
 
 }  // namespace dom
new file mode 100644
--- /dev/null
+++ b/dom/storage/SessionStorageService.cpp
@@ -0,0 +1,95 @@
+/* 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 "SessionStorageService.h"
+
+#include "mozilla/Services.h"
+#include "mozilla/Unused.h"
+#include "nsIObserverService.h"
+#include "nsXULAppAPI.h"
+
+#include <cstring>
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+const char* const kContentProcessShutdownTopic = "content-child-will-shutdown";
+
+}
+
+RefPtr<SessionStorageService> SessionStorageService::sService = nullptr;
+bool SessionStorageService::sShutdown = false;
+
+NS_IMPL_ISUPPORTS(SessionStorageService, nsIObserver)
+
+SessionStorageService::SessionStorageService() {
+  if (const nsCOMPtr<nsIObserverService> observerService =
+          services::GetObserverService()) {
+    Unused << observerService->AddObserver(this, kContentProcessShutdownTopic,
+                                           false);
+  }
+}
+
+SessionStorageService::~SessionStorageService() {
+  if (const nsCOMPtr<nsIObserverService> observerService =
+          services::GetObserverService()) {
+    Unused << observerService->RemoveObserver(this,
+                                              kContentProcessShutdownTopic);
+  }
+}
+
+SessionStorageService* SessionStorageService::Get() {
+  if (sShutdown) {
+    return nullptr;
+  }
+
+  if (XRE_IsParentProcess()) {
+    ShutDown();
+    return nullptr;
+  }
+
+  if (!sService) {
+    sService = new SessionStorageService();
+  }
+
+  return sService;
+}
+
+NS_IMETHODIMP SessionStorageService::Observe(nsISupports* const aSubject,
+                                             const char* const aTopic,
+                                             const char16_t* const aData) {
+  if (std::strcmp(aTopic, kContentProcessShutdownTopic) == 0) {
+    SendSessionStorageDataToParentProcess();
+    ShutDown();
+  }
+  return NS_OK;
+}
+
+void SessionStorageService::RegisterSessionStorageManager(
+    SessionStorageManager* aManager) {
+  mManagers.PutEntry(aManager);
+}
+
+void SessionStorageService::UnregisterSessionStorageManager(
+    SessionStorageManager* aManager) {
+  if (const auto entry = mManagers.GetEntry(aManager)) {
+    mManagers.RemoveEntry(entry);
+  }
+}
+
+void SessionStorageService::SendSessionStorageDataToParentProcess() {
+  for (auto iter = mManagers.Iter(); !iter.Done(); iter.Next()) {
+    iter.Get()->GetKey()->SendSessionStorageDataToParentProcess();
+  }
+}
+
+void SessionStorageService::ShutDown() {
+  sShutdown = true;
+  sService = nullptr;
+}
+
+}  // namespace dom
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/storage/SessionStorageService.h
@@ -0,0 +1,47 @@
+/* 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_SessionStorageService_h
+#define mozilla_dom_SessionStorageService_h
+
+#include "mozilla/UniquePtr.h"
+#include "nsIObserver.h"
+#include "nsISupportsImpl.h"
+#include "nsPointerHashKeys.h"
+#include "nsTHashtable.h"
+
+namespace mozilla {
+namespace dom {
+
+class SessionStorageManager;
+
+class SessionStorageService final : public nsIObserver {
+ public:
+  SessionStorageService();
+
+  static SessionStorageService* Get();
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+  void RegisterSessionStorageManager(SessionStorageManager* aManager);
+  void UnregisterSessionStorageManager(SessionStorageManager* aManager);
+
+ private:
+  ~SessionStorageService();
+
+  static void ShutDown();
+
+  void SendSessionStorageDataToParentProcess();
+
+  static RefPtr<SessionStorageService> sService;
+  static bool sShutdown;
+
+  nsTHashtable<nsPtrHashKey<SessionStorageManager>> mManagers;
+};
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif  // mozilla_dom_SessionStorageService_h
--- a/dom/storage/moz.build
+++ b/dom/storage/moz.build
@@ -25,16 +25,17 @@ UNIFIED_SOURCES += [
     'LocalStorage.cpp',
     'LocalStorageCache.cpp',
     'LocalStorageManager.cpp',
     'PartitionedLocalStorage.cpp',
     'SessionStorage.cpp',
     'SessionStorageCache.cpp',
     'SessionStorageManager.cpp',
     'SessionStorageObserver.cpp',
+    'SessionStorageService.cpp',
     'Storage.cpp',
     'StorageActivityService.cpp',
     'StorageDBThread.cpp',
     'StorageDBUpdater.cpp',
     'StorageIPC.cpp',
     'StorageNotifierService.cpp',
     'StorageObserver.cpp',
     'StorageUtils.cpp',
--- a/dom/tests/mochitest/sessionstorage/mochitest.ini
+++ b/dom/tests/mochitest/sessionstorage/mochitest.ini
@@ -8,10 +8,9 @@ support-files =
   interOriginSlave.js
   interOriginTest.js
 
 [test_sessionStorageBase.html]
 [test_sessionStorageBaseSessionOnly.html]
 [test_sessionStorageClone.html]
 [test_sessionStorageHttpHttps.html]
 [test_sessionStorageReplace.html]
-fail-if = fission
 [test_sessionStorageUsage.html]