Bug 1462162 - Filter local storage cache synchronization messages based on origin; r=asuth a=ritu
authorJan Varga <jan.varga@gmail.com>
Sat, 14 Jul 2018 08:34:14 +0200
changeset 480628 f0cd2fb6e875cd05fed58ab2c93461b9b7172325
parent 480627 843220600fd146ec23c05d62766d5832cc086852
child 480629 39837622272f4606a18c3900d0f4c9d7e4fb1137
push id1757
push userffxbld-merge
push dateFri, 24 Aug 2018 17:02:43 +0000
treeherdermozilla-release@736023aebdb1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth, ritu
bugs1462162
milestone62.0
Bug 1462162 - Filter local storage cache synchronization messages based on origin; r=asuth a=ritu This patch adds a new IPDL protocol PBackgroundLocalStorageCache. It is used by LocalStorageCache object to broadcast changes in local storage cache to other content processes. Each origin has its own PBackgroundLocalStorageCache, so now we can notify content processes that actually have a local storage cache for given origin. This greatly improves performance and reduces memory footprint especialy when local storage changes carry big strings and/or happen very quickly (before this patch all child processes were blindly notified).
dom/storage/LocalStorage.cpp
dom/storage/LocalStorage.h
dom/storage/LocalStorageCache.cpp
dom/storage/LocalStorageCache.h
dom/storage/LocalStorageManager.cpp
dom/storage/PBackgroundLocalStorageCache.ipdl
dom/storage/Storage.h
dom/storage/StorageIPC.cpp
dom/storage/StorageIPC.h
dom/storage/moz.build
ipc/glue/BackgroundChildImpl.cpp
ipc/glue/BackgroundChildImpl.h
ipc/glue/BackgroundParentImpl.cpp
ipc/glue/BackgroundParentImpl.h
ipc/glue/PBackground.ipdl
--- a/dom/storage/LocalStorage.cpp
+++ b/dom/storage/LocalStorage.cpp
@@ -129,17 +129,17 @@ LocalStorage::SetItem(const nsAString& a
 
   nsString old;
   aRv = mCache->SetItem(this, aKey, data, old);
   if (aRv.Failed()) {
     return;
   }
 
   if (!aRv.ErrorCodeIs(NS_SUCCESS_DOM_NO_OPERATION)) {
-    BroadcastChangeNotification(aKey, old, aData);
+    OnChange(aKey, old, aData);
   }
 }
 
 void
 LocalStorage::RemoveItem(const nsAString& aKey, nsIPrincipal& aSubjectPrincipal,
                          ErrorResult& aRv)
 {
   if (!CanUseStorage(aSubjectPrincipal)) {
@@ -149,17 +149,17 @@ LocalStorage::RemoveItem(const nsAString
 
   nsAutoString old;
   aRv = mCache->RemoveItem(this, aKey, old);
   if (aRv.Failed()) {
     return;
   }
 
   if (!aRv.ErrorCodeIs(NS_SUCCESS_DOM_NO_OPERATION)) {
-    BroadcastChangeNotification(aKey, old, VoidString());
+    OnChange(aKey, old, VoidString());
   }
 }
 
 void
 LocalStorage::Clear(nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv)
 {
   if (!CanUseStorage(aSubjectPrincipal)) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
@@ -167,57 +167,34 @@ LocalStorage::Clear(nsIPrincipal& aSubje
   }
 
   aRv = mCache->Clear(this);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   if (!aRv.ErrorCodeIs(NS_SUCCESS_DOM_NO_OPERATION)) {
-    BroadcastChangeNotification(VoidString(), VoidString(), VoidString());
+    OnChange(VoidString(), VoidString(), VoidString());
   }
 }
 
 void
-LocalStorage::BroadcastChangeNotification(const nsAString& aKey,
-                                          const nsAString& aOldValue,
-                                          const nsAString& aNewValue)
+LocalStorage::OnChange(const nsAString& aKey,
+                       const nsAString& aOldValue,
+                       const nsAString& aNewValue)
 {
-  if (Principal()) {
-    // We want to send a message to the parent in order to broadcast the
-    // StorageEvent correctly to any child process.
-
-    PBackgroundChild* actor = BackgroundChild::GetForCurrentThread();
-    MOZ_ASSERT(actor);
-
-    PrincipalInfo principalInfo;
-    nsresult rv = PrincipalToPrincipalInfo(Principal(), &principalInfo);
-    if (!NS_WARN_IF(NS_FAILED(rv))) {
-      Unused << NS_WARN_IF(!actor->SendBroadcastLocalStorageChange(
-        mDocumentURI, nsString(aKey), nsString(aOldValue), nsString(aNewValue),
-        principalInfo, mIsPrivate));
-    }
-  }
-
-  DispatchStorageEvent(mDocumentURI, aKey, aOldValue, aNewValue,
-                       Principal(), mIsPrivate, this, false);
-}
-
-/* static */ void
-LocalStorage::DispatchStorageEvent(const nsAString& aDocumentURI,
-                                   const nsAString& aKey,
-                                   const nsAString& aOldValue,
-                                   const nsAString& aNewValue,
-                                   nsIPrincipal* aPrincipal,
-                                   bool aIsPrivate,
-                                   Storage* aStorage,
-                                   bool aImmediateDispatch)
-{
-  NotifyChange(aStorage, aPrincipal, aKey, aOldValue, aNewValue,
-               u"localStorage", aDocumentURI, aIsPrivate, aImmediateDispatch);
+  NotifyChange(/* aStorage */ this,
+               Principal(),
+               aKey,
+               aOldValue,
+               aNewValue,
+               /* aStorageType */ u"localStorage",
+               mDocumentURI,
+               mIsPrivate,
+               /* aImmediateDispatch */ false);
 }
 
 void
 LocalStorage::ApplyEvent(StorageEvent* aStorageEvent)
 {
   MOZ_ASSERT(aStorageEvent);
 
   nsAutoString key;
--- a/dom/storage/LocalStorage.h
+++ b/dom/storage/LocalStorage.h
@@ -31,16 +31,22 @@ public:
     return mManager;
   }
 
   LocalStorageCache const* GetCache() const
   {
     return mCache;
   }
 
+  const nsString&
+  DocumentURI() const
+  {
+    return mDocumentURI;
+  }
+
   bool PrincipalEquals(nsIPrincipal* aPrincipal);
 
   LocalStorage(nsPIDOMWindowInner* aWindow,
                LocalStorageManager* aManager,
                LocalStorageCache* aCache,
                const nsAString& aDocumentURI,
                nsIPrincipal* aPrincipal,
                bool aIsPrivate);
@@ -72,34 +78,16 @@ public:
                   nsIPrincipal& aSubjectPrincipal,
                   ErrorResult& aRv) override;
 
   void Clear(nsIPrincipal& aSubjectPrincipal,
              ErrorResult& aRv) override;
 
   bool IsPrivate() const { return mIsPrivate; }
 
-  // aStorage can be null if this method is called by ContentChild.
-  //
-  // aImmediateDispatch is for use by (main-thread) IPC code so that PContent
-  // ordering can be maintained.  Without this, the event would be enqueued and
-  // run in a future turn of the event loop, potentially allowing other PContent
-  // Recv* methods to trigger script that wants to assume our localstorage
-  // changes have already been applied.  This is the case for message manager
-  // messages which are used by ContentTask testing logic and webextensions.
-  static void
-  DispatchStorageEvent(const nsAString& aDocumentURI,
-                       const nsAString& aKey,
-                       const nsAString& aOldValue,
-                       const nsAString& aNewValue,
-                       nsIPrincipal* aPrincipal,
-                       bool aIsPrivate,
-                       Storage* aStorage,
-                       bool aImmediateDispatch);
-
   void
   ApplyEvent(StorageEvent* aStorageEvent);
 
 private:
   ~LocalStorage();
 
   friend class LocalStorageManager;
   friend class LocalStorageCache;
@@ -110,17 +98,17 @@ private:
 
   // Principal this Storage (i.e. localStorage or sessionStorage) has
   // been created for
   nsCOMPtr<nsIPrincipal> mPrincipal;
 
   // Whether this storage is running in private-browsing window.
   bool mIsPrivate : 1;
 
-  void BroadcastChangeNotification(const nsAString& aKey,
-                                   const nsAString& aOldValue,
-                                   const nsAString& aNewValue);
+  void OnChange(const nsAString& aKey,
+                const nsAString& aOldValue,
+                const nsAString& aNewValue);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_LocalStorage_h
--- a/dom/storage/LocalStorageCache.cpp
+++ b/dom/storage/LocalStorageCache.cpp
@@ -70,37 +70,53 @@ NS_IMETHODIMP_(void) LocalStorageCacheBr
     /* NS_ASSERT_OWNINGTHREAD(_class); */
     delete (this);
   }
 }
 
 // LocalStorageCache
 
 LocalStorageCache::LocalStorageCache(const nsACString* aOriginNoSuffix)
-  : mOriginNoSuffix(*aOriginNoSuffix)
+  : mActor(nullptr)
+  , mOriginNoSuffix(*aOriginNoSuffix)
   , mMonitor("LocalStorageCache")
   , mLoaded(false)
   , mLoadResult(NS_OK)
   , mInitialized(false)
   , mPersistent(false)
   , mSessionOnlyDataSetActive(false)
   , mPreloadTelemetryRecorded(false)
 {
   MOZ_COUNT_CTOR(LocalStorageCache);
 }
 
 LocalStorageCache::~LocalStorageCache()
 {
+  if (mActor) {
+    mActor->SendDeleteMeInternal();
+    MOZ_ASSERT(!mActor, "SendDeleteMeInternal should have cleared!");
+  }
+
   if (mManager) {
     mManager->DropCache(this);
   }
 
   MOZ_COUNT_DTOR(LocalStorageCache);
 }
 
+void
+LocalStorageCache::SetActor(LocalStorageCacheChild* aActor)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aActor);
+  MOZ_ASSERT(!mActor);
+
+  mActor = aActor;
+}
+
 NS_IMETHODIMP_(void)
 LocalStorageCache::Release(void)
 {
   // We must actually release on the main thread since the cache removes it
   // self from the manager's hash table.  And we don't want to lock access to
   // that hash table.
   if (NS_IsMainThread()) {
     LocalStorageCacheBridge::Release();
@@ -147,16 +163,38 @@ LocalStorageCache::Init(LocalStorageMana
   // this storage cache is bound to.
   MOZ_ASSERT(StringBeginsWith(mQuotaOriginScope, mOriginSuffix));
   MOZ_ASSERT(mOriginSuffix.IsEmpty() != StringBeginsWith(mQuotaOriginScope,
                                                          NS_LITERAL_CSTRING("^")));
 
   mUsage = aManager->GetOriginUsage(mQuotaOriginScope);
 }
 
+void
+LocalStorageCache::NotifyObservers(const LocalStorage* aStorage,
+                                   const nsString& aKey,
+                                   const nsString& aOldValue,
+                                   const nsString& aNewValue)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aStorage);
+
+  if (!mActor) {
+    return;
+  }
+
+  // We want to send a message to the parent in order to broadcast the
+  // StorageEvent correctly to any child process.
+
+  Unused << mActor->SendNotify(aStorage->DocumentURI(),
+                               aKey,
+                               aOldValue,
+                               aNewValue);
+}
+
 inline bool
 LocalStorageCache::Persist(const LocalStorage* aStorage) const
 {
   return mPersistent &&
          !aStorage->IsSessionOnly() &&
          !aStorage->IsPrivate();
 }
 
@@ -396,17 +434,23 @@ LocalStorageCache::SetItem(const LocalSt
   }
 
   if (aValue == aOld && DOMStringIsNull(aValue) == DOMStringIsNull(aOld)) {
     return NS_SUCCESS_DOM_NO_OPERATION;
   }
 
   data.mKeys.Put(aKey, aValue);
 
-  if (aSource == ContentMutation && Persist(aStorage)) {
+  if (aSource != ContentMutation) {
+    return NS_OK;
+  }
+
+  NotifyObservers(aStorage, nsString(aKey), aOld, aValue);
+
+  if (Persist(aStorage)) {
     StorageDBChild* storageChild = StorageDBChild::Get();
     if (!storageChild) {
       NS_ERROR("Writing to localStorage after the database has been shut down"
                ", data lose!");
       return NS_ERROR_NOT_INITIALIZED;
     }
 
     if (DOMStringIsNull(aOld)) {
@@ -438,17 +482,23 @@ LocalStorageCache::RemoveItem(const Loca
   }
 
   // Recalculate the cached data size
   const int64_t delta = -(static_cast<int64_t>(aOld.Length()) +
                           static_cast<int64_t>(aKey.Length()));
   Unused << ProcessUsageDelta(aStorage, delta, aSource);
   data.mKeys.Remove(aKey);
 
-  if (aSource == ContentMutation && Persist(aStorage)) {
+  if (aSource != ContentMutation) {
+    return NS_OK;
+  }
+
+  NotifyObservers(aStorage, nsString(aKey), aOld, VoidString());
+
+  if (Persist(aStorage)) {
     StorageDBChild* storageChild = StorageDBChild::Get();
     if (!storageChild) {
       NS_ERROR("Writing to localStorage after the database has been shut down"
                ", data lose!");
       return NS_ERROR_NOT_INITIALIZED;
     }
 
     return storageChild->AsyncRemoveItem(this, aKey);
@@ -480,17 +530,25 @@ LocalStorageCache::Clear(const LocalStor
   Data& data = DataSet(aStorage);
   bool hadData = !!data.mKeys.Count();
 
   if (hadData) {
     Unused << ProcessUsageDelta(aStorage, -data.mOriginQuotaUsage, aSource);
     data.mKeys.Clear();
   }
 
-  if (aSource == ContentMutation && Persist(aStorage) && (refresh || hadData)) {
+  if (aSource != ContentMutation) {
+    return hadData ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
+  }
+
+  if (hadData) {
+    NotifyObservers(aStorage, VoidString(), VoidString(), VoidString());
+  }
+
+  if (Persist(aStorage) && (refresh || hadData)) {
     StorageDBChild* storageChild = StorageDBChild::Get();
     if (!storageChild) {
       NS_ERROR("Writing to localStorage after the database has been shut down"
                ", data lose!");
       return NS_ERROR_NOT_INITIALIZED;
     }
 
     return storageChild->AsyncClear(this);
--- a/dom/storage/LocalStorageCache.h
+++ b/dom/storage/LocalStorageCache.h
@@ -16,16 +16,17 @@
 #include "mozilla/Monitor.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/Atomics.h"
 
 namespace mozilla {
 namespace dom {
 
 class LocalStorage;
+class LocalStorageCacheChild;
 class LocalStorageManager;
 class StorageUsage;
 class StorageDBBridge;
 
 // Interface class on which only the database or IPC may call.
 // Used to populate the cache with DB data.
 class LocalStorageCacheBridge
 {
@@ -71,16 +72,33 @@ protected:
 
 // Implementation of scope cache that is responsible for preloading data
 // for persistent storage (localStorage) and hold data for non-private,
 // private and session-only cookie modes.  It is also responsible for
 // persisting data changes using the database, works as a write-back cache.
 class LocalStorageCache : public LocalStorageCacheBridge
 {
 public:
+  void
+  AssertIsOnOwningThread() const
+  {
+    NS_ASSERT_OWNINGTHREAD(LocalStorage);
+  }
+
+  void
+  SetActor(LocalStorageCacheChild* aActor);
+
+  void
+  ClearActor()
+  {
+    AssertIsOnOwningThread();
+
+    mActor = nullptr;
+  }
+
   NS_IMETHOD_(void) Release(void) override;
 
   enum MutationSource {
     // The mutation is a result of an explicit JS mutation in this process.
     // The mutation should be sent to the sDatabase. Quota will be checked and
     // QuotaExceededError may be returned without the mutation being applied.
     ContentMutation,
     // The mutation initially was triggered in a different process and is being
@@ -176,16 +194,23 @@ private:
 
 private:
   // Synchronously blocks until the cache is fully loaded from the database
   void WaitForPreload(mozilla::Telemetry::HistogramID aTelemetryID);
 
   // Helper to get one of the 3 data sets (regular, private, session)
   Data& DataSet(const LocalStorage* aStorage);
 
+  // Used for firing storage events and synchronization of caches in other
+  // content processes.
+  void NotifyObservers(const LocalStorage* aStorage,
+                       const nsString& aKey,
+                       const nsString& aOldValue,
+                       const nsString& aNewValue);
+
   // Whether the storage change is about to persist
   bool Persist(const LocalStorage* aStorage) const;
 
   // Changes the quota usage on the given data set if it fits the quota.
   // If not, then false is returned and no change to the set must be done.
   // A special case is if aSource==E10sPropagated, then we will return true even
   // if the change would put us over quota.  This is done to ensure coherency of
   // caches between processes in the face of races.  It does allow an attacker
@@ -205,16 +230,24 @@ private:
   // hash table is handled in the destructor by call to the manager.  Cache
   // could potentially overlive the manager, hence the hard ref.
   RefPtr<LocalStorageManager> mManager;
 
   // Reference to the usage counter object we check on for eTLD+1 quota limit.
   // Obtained from the manager during initialization (Init method).
   RefPtr<StorageUsage> mUsage;
 
+  // The LocalStorageCacheChild is created at the same time of this class.
+  // In normal operation, the actor will be synchronously cleared in our
+  // destructor when we tell it to delete itself.  In a shutdown-related edge
+  // case in the parent process for JSM's, it is possible for the actor to be
+  // destroyed while this class remains alive, in which case it will be nulled
+  // out.
+  LocalStorageCacheChild* mActor;
+
   // The origin this cache belongs to in the "DB format", i.e. reversed
   nsCString mOriginNoSuffix;
 
   // The origin attributes suffix
   nsCString mOriginSuffix;
 
   // The eTLD+1 scope used to count quota usage.  It is in the reversed format
   // and contains the origin attributes suffix.
--- a/dom/storage/LocalStorageManager.cpp
+++ b/dom/storage/LocalStorageManager.cpp
@@ -241,19 +241,48 @@ LocalStorageManager::GetStorageInternal(
         }
       } else {
         if (originKey.EqualsLiteral("knalb.:about")) {
           return NS_OK;
         }
       }
     }
 
+    PBackgroundChild* backgroundActor =
+      BackgroundChild::GetOrCreateForCurrentThread();
+    if (NS_WARN_IF(!backgroundActor)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    PrincipalInfo principalInfo;
+    rv = mozilla::ipc::PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    uint32_t privateBrowsingId;
+    rv = aPrincipal->GetPrivateBrowsingId(&privateBrowsingId);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
     // There is always a single instance of a cache per scope
     // in a single instance of a DOM storage manager.
     cache = PutCache(originAttrSuffix, originKey, aPrincipal);
+
+    LocalStorageCacheChild* actor = new LocalStorageCacheChild(cache);
+
+    MOZ_ALWAYS_TRUE(
+      backgroundActor->SendPBackgroundLocalStorageCacheConstructor(
+                                                            actor,
+                                                            principalInfo,
+                                                            originKey,
+                                                            privateBrowsingId));
+
+    cache->SetActor(actor);
   }
 
   if (aRetval) {
     nsCOMPtr<nsPIDOMWindowInner> inner = nsPIDOMWindowInner::From(aWindow);
 
     nsCOMPtr<nsIDOMStorage> storage = new LocalStorage(
       inner, this, cache, aDocumentURI, aPrincipal, aPrivate);
     storage.forget(aRetval);
new file mode 100644
--- /dev/null
+++ b/dom/storage/PBackgroundLocalStorageCache.ipdl
@@ -0,0 +1,41 @@
+/* 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 PBackground;
+
+include PBackgroundSharedTypes;
+
+namespace mozilla {
+namespace dom {
+
+async protocol PBackgroundLocalStorageCache
+{
+  manager PBackground;
+
+parent:
+  async DeleteMe();
+
+  async Notify(nsString documentURI,
+               nsString key,
+               nsString oldValue,
+               nsString newValue);
+
+child:
+  // The principalInfo and privateBrowsingId could instead be retained by the
+  // LocalStorageCacheChild/LocalStorageCache instead of being re-transmitted.
+  // However, these changes are a temporary optimization intended for uplift,
+  // and this constant factor overhead is very small compared to the upside of
+  // filtering.
+  async Observe(PrincipalInfo principalInfo,
+                uint32_t privateBrowsingId,
+                nsString documentURI,
+                nsString key,
+                nsString oldValue,
+                nsString newValue);
+
+  async __delete__();
+};
+
+} // namespace dom
+} // namespace mozilla
--- a/dom/storage/Storage.h
+++ b/dom/storage/Storage.h
@@ -106,16 +106,25 @@ public:
     aFound = !aRv.ErrorCodeIs(NS_SUCCESS_DOM_NO_OPERATION);
   }
 
   virtual void
   Clear(nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) = 0;
 
   bool IsSessionOnly() const { return mIsSessionOnly; }
 
+  // aStorage can be null if this method is called by LocalStorageCacheChild.
+  //
+  // aImmediateDispatch is for use by child IPC code (LocalStorageCacheChild)
+  // so that PBackground ordering can be maintained.  Without this, the event
+  // would be/ enqueued and run in a future turn of the event loop, potentially
+  // allowing other PBackground Recv* methods to trigger script that wants to
+  // assume our localstorage changes have already been applied.  This is the
+  // case for message manager messages which are used by ContentTask testing
+  // logic and webextensions.
   static void
   NotifyChange(Storage* aStorage, nsIPrincipal* aPrincipal,
                const nsAString& aKey, const nsAString& aOldValue,
                const nsAString& aNewValue, const char16_t* aStorageType,
                const nsAString& aDocumentURI, bool aIsPrivate,
                bool aImmediateDispatch);
 
 protected:
--- a/dom/storage/StorageIPC.cpp
+++ b/dom/storage/StorageIPC.cpp
@@ -18,23 +18,99 @@
 #include "nsIDiskSpaceWatcher.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 namespace {
 
+typedef nsClassHashtable<nsCStringHashKey, nsTArray<LocalStorageCacheParent*>>
+  LocalStorageCacheParentHashtable;
+
+StaticAutoPtr<LocalStorageCacheParentHashtable> gLocalStorageCacheParents;
+
 StorageDBChild* sStorageChild = nullptr;
 
 // False until we shut the storage child down.
 bool sStorageChildDown = false;
 
 }
 
+LocalStorageCacheChild::LocalStorageCacheChild(LocalStorageCache* aCache)
+  : mCache(aCache)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aCache);
+  aCache->AssertIsOnOwningThread();
+
+  MOZ_COUNT_CTOR(LocalStorageCacheChild);
+}
+
+LocalStorageCacheChild::~LocalStorageCacheChild()
+{
+  AssertIsOnOwningThread();
+
+  MOZ_COUNT_DTOR(LocalStorageCacheChild);
+}
+
+void
+LocalStorageCacheChild::SendDeleteMeInternal()
+{
+  AssertIsOnOwningThread();
+
+  if (mCache) {
+    mCache->ClearActor();
+    mCache = nullptr;
+
+    MOZ_ALWAYS_TRUE(PBackgroundLocalStorageCacheChild::SendDeleteMe());
+  }
+}
+
+void
+LocalStorageCacheChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+  AssertIsOnOwningThread();
+
+  if (mCache) {
+    mCache->ClearActor();
+    mCache = nullptr;
+  }
+}
+
+mozilla::ipc::IPCResult
+LocalStorageCacheChild::RecvObserve(const PrincipalInfo& aPrincipalInfo,
+                                    const uint32_t& aPrivateBrowsingId,
+                                    const nsString& aDocumentURI,
+                                    const nsString& aKey,
+                                    const nsString& aOldValue,
+                                    const nsString& aNewValue)
+{
+  AssertIsOnOwningThread();
+
+  nsresult rv;
+  nsCOMPtr<nsIPrincipal> principal =
+    PrincipalInfoToPrincipal(aPrincipalInfo, &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+
+  Storage::NotifyChange(/* aStorage */ nullptr,
+                        principal,
+                        aKey,
+                        aOldValue,
+                        aNewValue,
+                        /* aStorageType */ u"localStorage",
+                        aDocumentURI,
+                        /* aIsPrivate */ !!aPrivateBrowsingId,
+                        /* aImmediateDispatch */ true);
+
+  return IPC_OK();
+}
+
 // ----------------------------------------------------------------------------
 // Child
 // ----------------------------------------------------------------------------
 
 class StorageDBChild::ShutdownObserver final
   : public nsIObserver
 {
 public:
@@ -391,16 +467,98 @@ ShutdownObserver::Observe(nsISupports* a
 
     NS_RELEASE(sStorageChild);
     sStorageChild = nullptr;
   }
 
   return NS_OK;
 }
 
+LocalStorageCacheParent::LocalStorageCacheParent(
+                                            const PrincipalInfo& aPrincipalInfo,
+                                            const nsACString& aOriginKey,
+                                            uint32_t aPrivateBrowsingId)
+  : mPrincipalInfo(aPrincipalInfo)
+  , mOriginKey(aOriginKey)
+  , mPrivateBrowsingId(aPrivateBrowsingId)
+  , mActorDestroyed(false)
+{
+  AssertIsOnBackgroundThread();
+}
+
+LocalStorageCacheParent::~LocalStorageCacheParent()
+{
+  MOZ_ASSERT(mActorDestroyed);
+}
+
+void
+LocalStorageCacheParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!mActorDestroyed);
+
+  mActorDestroyed = true;
+
+  MOZ_ASSERT(gLocalStorageCacheParents);
+
+  nsTArray<LocalStorageCacheParent*>* array;
+  gLocalStorageCacheParents->Get(mOriginKey, &array);
+  MOZ_ASSERT(array);
+
+  array->RemoveElement(this);
+
+  if (array->IsEmpty()) {
+    gLocalStorageCacheParents->Remove(mOriginKey);
+  }
+
+  if (!gLocalStorageCacheParents->Count()) {
+    gLocalStorageCacheParents = nullptr;
+  }
+}
+
+mozilla::ipc::IPCResult
+LocalStorageCacheParent::RecvDeleteMe()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!mActorDestroyed);
+
+  IProtocol* mgr = Manager();
+  if (!PBackgroundLocalStorageCacheParent::Send__delete__(this)) {
+    return IPC_FAIL_NO_REASON(mgr);
+  }
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+LocalStorageCacheParent::RecvNotify(const nsString& aDocumentURI,
+                                    const nsString& aKey,
+                                    const nsString& aOldValue,
+                                    const nsString& aNewValue)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(gLocalStorageCacheParents);
+
+  nsTArray<LocalStorageCacheParent*>* array;
+  gLocalStorageCacheParents->Get(mOriginKey, &array);
+  MOZ_ASSERT(array);
+
+  for (LocalStorageCacheParent* localStorageCacheParent : *array) {
+    if (localStorageCacheParent != this) {
+      Unused << localStorageCacheParent->SendObserve(mPrincipalInfo,
+                                                     mPrivateBrowsingId,
+                                                     aDocumentURI,
+                                                     aKey,
+                                                     aOldValue,
+                                                     aNewValue);
+    }
+  }
+
+  return IPC_OK();
+}
+
 // ----------------------------------------------------------------------------
 // Parent
 // ----------------------------------------------------------------------------
 
 class StorageDBParent::ObserverSink
   : public StorageObserverSink
 {
   nsCOMPtr<nsIEventTarget> mOwningEventTarget;
@@ -1222,16 +1380,76 @@ ObserverSink::Observe(const char* aTopic
 
   return NS_OK;
 }
 
 /*******************************************************************************
  * Exported functions
  ******************************************************************************/
 
+PBackgroundLocalStorageCacheParent*
+AllocPBackgroundLocalStorageCacheParent(
+                              const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+                              const nsCString& aOriginKey,
+                              const uint32_t& aPrivateBrowsingId)
+{
+  AssertIsOnBackgroundThread();
+
+  RefPtr<LocalStorageCacheParent> actor =
+    new LocalStorageCacheParent(aPrincipalInfo, aOriginKey, aPrivateBrowsingId);
+
+  // Transfer ownership to IPDL.
+  return actor.forget().take();
+}
+
+mozilla::ipc::IPCResult
+RecvPBackgroundLocalStorageCacheConstructor(
+                              mozilla::ipc::PBackgroundParent* aBackgroundActor,
+                              PBackgroundLocalStorageCacheParent* aActor,
+                              const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+                              const nsCString& aOriginKey,
+                              const uint32_t& aPrivateBrowsingId)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+
+  auto* actor = static_cast<LocalStorageCacheParent*>(aActor);
+
+  if (!gLocalStorageCacheParents) {
+    gLocalStorageCacheParents = new LocalStorageCacheParentHashtable();
+  }
+
+  nsTArray<LocalStorageCacheParent*>* array;
+  if (!gLocalStorageCacheParents->Get(aOriginKey, &array)) {
+    array = new nsTArray<LocalStorageCacheParent*>();
+    gLocalStorageCacheParents->Put(aOriginKey, array);
+  }
+  array->AppendElement(actor);
+
+  // We are currently trusting the content process not to lie to us.  It is
+  // future work to consult the ClientManager to determine whether this is a
+  // legitimate origin for the content process.
+
+  return IPC_OK();
+}
+
+bool
+DeallocPBackgroundLocalStorageCacheParent(
+                                     PBackgroundLocalStorageCacheParent* aActor)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+
+  // Transfer ownership back from IPDL.
+  RefPtr<LocalStorageCacheParent> actor =
+    dont_AddRef(static_cast<LocalStorageCacheParent*>(aActor));
+
+  return true;
+}
+
 PBackgroundStorageParent*
 AllocPBackgroundStorageParent(const nsString& aProfilePath)
 {
   AssertIsOnBackgroundThread();
 
   return new StorageDBParent(aProfilePath);
 }
 
--- a/dom/storage/StorageIPC.h
+++ b/dom/storage/StorageIPC.h
@@ -2,33 +2,89 @@
 /* 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_StorageIPC_h
 #define mozilla_dom_StorageIPC_h
 
+#include "mozilla/dom/PBackgroundLocalStorageCacheChild.h"
+#include "mozilla/dom/PBackgroundLocalStorageCacheParent.h"
 #include "mozilla/dom/PBackgroundStorageChild.h"
 #include "mozilla/dom/PBackgroundStorageParent.h"
 #include "StorageDBThread.h"
 #include "LocalStorageCache.h"
 #include "StorageObserver.h"
 #include "mozilla/Mutex.h"
 #include "nsAutoPtr.h"
 
 namespace mozilla {
 
 class OriginAttributesPattern;
 
+namespace ipc {
+
+class BackgroundChildImpl;
+class PrincipalInfo;
+
+} // namespace ipc
+
 namespace dom {
 
 class LocalStorageManager;
 class PBackgroundStorageParent;
 
+class LocalStorageCacheChild final
+  : public PBackgroundLocalStorageCacheChild
+{
+  friend class mozilla::ipc::BackgroundChildImpl;
+  friend class LocalStorageCache;
+  friend class LocalStorageManager;
+
+  // LocalStorageCache effectively owns this instance, although IPC handles its
+  // allocation/deallocation.  When the LocalStorageCache destructor runs, it
+  // will invoke SendDeleteMeInternal() which will trigger both instances to
+  // drop their mutual references and cause IPC to destroy the actor after the
+  // DeleteMe round-trip.
+  LocalStorageCache* MOZ_NON_OWNING_REF mCache;
+
+  NS_DECL_OWNINGTHREAD
+
+public:
+  void
+  AssertIsOnOwningThread() const
+  {
+    NS_ASSERT_OWNINGTHREAD(LocalStorageCacheChild);
+  }
+
+private:
+  // Only created by LocalStorageManager.
+  explicit LocalStorageCacheChild(LocalStorageCache* aCache);
+
+  // Only destroyed by mozilla::ipc::BackgroundChildImpl.
+  ~LocalStorageCacheChild();
+
+  // Only called by LocalStorageCache.
+  void
+  SendDeleteMeInternal();
+
+  // IPDL methods are only called by IPDL.
+  void
+  ActorDestroy(ActorDestroyReason aWhy) override;
+
+  mozilla::ipc::IPCResult
+  RecvObserve(const PrincipalInfo& aPrincipalInfo,
+              const uint32_t& aPrivateBrowsingId,
+              const nsString& aDocumentURI,
+              const nsString& aKey,
+              const nsString& aOldValue,
+              const nsString& aNewValue) override;
+};
+
 // Child side of the IPC protocol, exposes as DB interface but
 // is responsible to send all requests to the parent process
 // and expects asynchronous answers. Those are then transparently
 // forwarded back to consumers on the child process.
 class StorageDBChild final
   : public PBackgroundStorageChild
 {
   class ShutdownObserver;
@@ -121,16 +177,49 @@ private:
   nsTHashtable<nsRefPtrHashKey<LocalStorageCacheBridge>> mLoadingCaches;
 
   // Status of the remote database
   nsresult mStatus;
 
   bool mIPCOpen;
 };
 
+class LocalStorageCacheParent final
+  : public PBackgroundLocalStorageCacheParent
+{
+  const PrincipalInfo mPrincipalInfo;
+  const nsCString mOriginKey;
+  uint32_t mPrivateBrowsingId;
+  bool mActorDestroyed;
+
+public:
+  // Created in AllocPBackgroundLocalStorageCacheParent.
+  LocalStorageCacheParent(const PrincipalInfo& aPrincipalInfo,
+                          const nsACString& aOriginKey,
+                          uint32_t aPrivateBrowsingId);
+
+  NS_INLINE_DECL_REFCOUNTING(mozilla::dom::LocalStorageCacheParent)
+
+private:
+  // Reference counted.
+  ~LocalStorageCacheParent();
+
+  // IPDL methods are only called by IPDL.
+  void
+  ActorDestroy(ActorDestroyReason aWhy) override;
+
+  mozilla::ipc::IPCResult
+  RecvDeleteMe() override;
+
+  mozilla::ipc::IPCResult
+  RecvNotify(const nsString& aDocumentURI,
+             const nsString& aKey,
+             const nsString& aOldValue,
+             const nsString& aNewValue) override;
+};
 
 // Receives async requests from child processes and is responsible
 // to send back responses from the DB thread.  Exposes as a fake
 // LocalStorageCache consumer.
 // Also responsible for forwardning all chrome operation notifications
 // such as cookie cleaning etc to the child process.
 class StorageDBParent final : public PBackgroundStorageParent
 {
@@ -278,16 +367,34 @@ private:
 
   ThreadSafeAutoRefCnt mRefCnt;
   NS_DECL_OWNINGTHREAD
 
   // True when IPC channel is open and Send*() methods are OK to use.
   bool mIPCOpen;
 };
 
+PBackgroundLocalStorageCacheParent*
+AllocPBackgroundLocalStorageCacheParent(
+                              const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+                              const nsCString& aOriginKey,
+                              const uint32_t& aPrivateBrowsingId);
+
+mozilla::ipc::IPCResult
+RecvPBackgroundLocalStorageCacheConstructor(
+                              mozilla::ipc::PBackgroundParent* aBackgroundActor,
+                              PBackgroundLocalStorageCacheParent* aActor,
+                              const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+                              const nsCString& aOriginKey,
+                              const uint32_t& aPrivateBrowsingId);
+
+bool
+DeallocPBackgroundLocalStorageCacheParent(
+                                    PBackgroundLocalStorageCacheParent* aActor);
+
 PBackgroundStorageParent*
 AllocPBackgroundStorageParent(const nsString& aProfilePath);
 
 mozilla::ipc::IPCResult
 RecvPBackgroundStorageConstructor(PBackgroundStorageParent* aActor,
                                   const nsString& aProfilePath);
 
 bool
--- a/dom/storage/moz.build
+++ b/dom/storage/moz.build
@@ -31,16 +31,17 @@ UNIFIED_SOURCES += [
     'StorageDBUpdater.cpp',
     'StorageIPC.cpp',
     'StorageNotifierService.cpp',
     'StorageObserver.cpp',
     'StorageUtils.cpp',
 ]
 
 IPDL_SOURCES += [
+    'PBackgroundLocalStorageCache.ipdl',
     'PBackgroundStorage.ipdl',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/dom/base',
--- a/ipc/glue/BackgroundChildImpl.cpp
+++ b/ipc/glue/BackgroundChildImpl.cpp
@@ -210,16 +210,36 @@ BackgroundChildImpl::DeallocPBackgroundI
                                          PBackgroundIndexedDBUtilsChild* aActor)
 {
   MOZ_ASSERT(aActor);
 
   delete aActor;
   return true;
 }
 
+BackgroundChildImpl::PBackgroundLocalStorageCacheChild*
+BackgroundChildImpl::AllocPBackgroundLocalStorageCacheChild(
+                                            const PrincipalInfo& aPrincipalInfo,
+                                            const nsCString& aOriginKey,
+                                            const uint32_t& aPrivateBrowsingId)
+{
+  MOZ_CRASH("PBackgroundLocalStorageChild actors should be manually "
+            "constructed!");
+}
+
+bool
+BackgroundChildImpl::DeallocPBackgroundLocalStorageCacheChild(
+                                      PBackgroundLocalStorageCacheChild* aActor)
+{
+  MOZ_ASSERT(aActor);
+
+  delete aActor;
+  return true;
+}
+
 BackgroundChildImpl::PBackgroundStorageChild*
 BackgroundChildImpl::AllocPBackgroundStorageChild(const nsString& aProfilePath)
 {
   MOZ_CRASH("PBackgroundStorageChild actors should be manually constructed!");
 }
 
 bool
 BackgroundChildImpl::DeallocPBackgroundStorageChild(
@@ -670,42 +690,16 @@ BackgroundChildImpl::DeallocPHttpBackgro
   // The reference is increased in BackgroundChannelCreateCallback::ActorCreated
   // of HttpBackgroundChannelChild.cpp. We should decrease it after IPC
   // destroyed.
   RefPtr<net::HttpBackgroundChannelChild> child =
     dont_AddRef(static_cast<net::HttpBackgroundChannelChild*>(aActor));
   return true;
 }
 
-mozilla::ipc::IPCResult
-BackgroundChildImpl::RecvDispatchLocalStorageChange(
-                                            const nsString& aDocumentURI,
-                                            const nsString& aKey,
-                                            const nsString& aOldValue,
-                                            const nsString& aNewValue,
-                                            const PrincipalInfo& aPrincipalInfo,
-                                            const bool& aIsPrivate)
-{
-  if (!NS_IsMainThread()) {
-    return IPC_OK();
-  }
-
-  nsresult rv;
-  nsCOMPtr<nsIPrincipal> principal =
-    PrincipalInfoToPrincipal(aPrincipalInfo, &rv);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return IPC_FAIL_NO_REASON(this);
-  }
-
-  LocalStorage::DispatchStorageEvent(aDocumentURI, aKey, aOldValue, aNewValue,
-                                     principal, aIsPrivate, nullptr, true);
-
-  return IPC_OK();
-}
-
 bool
 BackgroundChildImpl::GetMessageSchedulerGroups(const Message& aMsg, SchedulerGroupSet& aGroups)
 {
   if (aMsg.type() == layout::PVsync::MessageType::Msg_Notify__ID) {
     MOZ_ASSERT(NS_IsMainThread());
     aGroups.Clear();
     if (dom::TabChild::HasVisibleTabs()) {
       for (auto iter = dom::TabChild::GetVisibleTabs().ConstIter();
--- a/ipc/glue/BackgroundChildImpl.h
+++ b/ipc/glue/BackgroundChildImpl.h
@@ -65,16 +65,27 @@ protected:
 
   virtual PBackgroundIndexedDBUtilsChild*
   AllocPBackgroundIndexedDBUtilsChild() override;
 
   virtual bool
   DeallocPBackgroundIndexedDBUtilsChild(PBackgroundIndexedDBUtilsChild* aActor)
                                         override;
 
+  virtual PBackgroundLocalStorageCacheChild*
+  AllocPBackgroundLocalStorageCacheChild(const PrincipalInfo& aPrincipalInfo,
+                                         const nsCString& aOriginKey,
+                                         const uint32_t& aPrivateBrowsingId)
+                                         override;
+
+  virtual bool
+  DeallocPBackgroundLocalStorageCacheChild(
+                                       PBackgroundLocalStorageCacheChild* aActor)
+                                       override;
+
   virtual PBackgroundStorageChild*
   AllocPBackgroundStorageChild(const nsString& aProfilePath) override;
 
   virtual bool
   DeallocPBackgroundStorageChild(PBackgroundStorageChild* aActor) override;
 
   virtual PPendingIPCBlobChild*
   AllocPPendingIPCBlobChild(const IPCBlob& aBlob) override;
@@ -222,24 +233,16 @@ protected:
   DeallocPWebAuthnTransactionChild(PWebAuthnTransactionChild* aActor) override;
 
   virtual PHttpBackgroundChannelChild*
   AllocPHttpBackgroundChannelChild(const uint64_t& aChannelId) override;
 
   virtual bool
   DeallocPHttpBackgroundChannelChild(PHttpBackgroundChannelChild* aActor) override;
 
-  virtual mozilla::ipc::IPCResult
-  RecvDispatchLocalStorageChange(const nsString& aDocumentURI,
-                                 const nsString& aKey,
-                                 const nsString& aOldValue,
-                                 const nsString& aNewValue,
-                                 const PrincipalInfo& aPrincipalInfo,
-                                 const bool& aIsPrivate) override;
-
   bool
   GetMessageSchedulerGroups(const Message& aMsg, SchedulerGroupSet& aGroups) override;
 
   virtual PMIDIPortChild* AllocPMIDIPortChild(const MIDIPortInfo& aPortInfo,
                                               const bool& aSysexEnabled) override;
   virtual bool DeallocPMIDIPortChild(PMIDIPortChild*) override;
 
   virtual PMIDIManagerChild* AllocPMIDIManagerChild() override;
--- a/ipc/glue/BackgroundParentImpl.cpp
+++ b/ipc/glue/BackgroundParentImpl.cpp
@@ -243,16 +243,62 @@ BackgroundParentImpl::RecvFlushPendingFi
   AssertIsOnBackgroundThread();
 
   if (!mozilla::dom::indexedDB::RecvFlushPendingFileDeletions()) {
     return IPC_FAIL_NO_REASON(this);
   }
   return IPC_OK();
 }
 
+BackgroundParentImpl::PBackgroundLocalStorageCacheParent*
+BackgroundParentImpl::AllocPBackgroundLocalStorageCacheParent(
+                                            const PrincipalInfo& aPrincipalInfo,
+                                            const nsCString& aOriginKey,
+                                            const uint32_t& aPrivateBrowsingId)
+{
+  AssertIsInMainProcess();
+  AssertIsOnBackgroundThread();
+
+  return
+    mozilla::dom::AllocPBackgroundLocalStorageCacheParent(aPrincipalInfo,
+                                                          aOriginKey,
+                                                          aPrivateBrowsingId);
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPBackgroundLocalStorageCacheConstructor(
+                                     PBackgroundLocalStorageCacheParent* aActor,
+                                     const PrincipalInfo& aPrincipalInfo,
+                                     const nsCString& aOriginKey,
+                                     const uint32_t& aPrivateBrowsingId)
+{
+  AssertIsInMainProcess();
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+
+  return
+    mozilla::dom::RecvPBackgroundLocalStorageCacheConstructor(
+                                                            this,
+                                                            aActor,
+                                                            aPrincipalInfo,
+                                                            aOriginKey,
+                                                            aPrivateBrowsingId);
+}
+
+bool
+BackgroundParentImpl::DeallocPBackgroundLocalStorageCacheParent(
+                                     PBackgroundLocalStorageCacheParent* aActor)
+{
+  AssertIsInMainProcess();
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+
+  return mozilla::dom::DeallocPBackgroundLocalStorageCacheParent(aActor);
+}
+
 auto
 BackgroundParentImpl::AllocPBackgroundStorageParent(const nsString& aProfilePath)
   -> PBackgroundStorageParent*
 {
   AssertIsInMainProcess();
   AssertIsOnBackgroundThread();
 
   return mozilla::dom::AllocPBackgroundStorageParent(aProfilePath);
@@ -276,44 +322,16 @@ BackgroundParentImpl::DeallocPBackground
 {
   AssertIsInMainProcess();
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aActor);
 
   return mozilla::dom::DeallocPBackgroundStorageParent(aActor);
 }
 
-mozilla::ipc::IPCResult
-BackgroundParentImpl::RecvBroadcastLocalStorageChange(
-                                            const nsString& aDocumentURI,
-                                            const nsString& aKey,
-                                            const nsString& aOldValue,
-                                            const nsString& aNewValue,
-                                            const PrincipalInfo& aPrincipalInfo,
-                                            const bool& aIsPrivate)
-{
-  // Let's inform the StorageActivityService about this change.
-  dom::StorageActivityService::SendActivity(aPrincipalInfo);
-
-  nsTArray<PBackgroundParent*> liveActorArray;
-  if (NS_WARN_IF(!BackgroundParent::GetLiveActorArray(this, liveActorArray))) {
-    return IPC_FAIL_NO_REASON(this);
-  }
-
-  for (auto* liveActor : liveActorArray) {
-    if (liveActor != this) {
-      Unused << liveActor->SendDispatchLocalStorageChange(
-        nsString(aDocumentURI), nsString(aKey), nsString(aOldValue),
-        nsString(aNewValue), aPrincipalInfo, aIsPrivate);
-    }
-  }
-
-  return IPC_OK();
-}
-
 PPendingIPCBlobParent*
 BackgroundParentImpl::AllocPPendingIPCBlobParent(const IPCBlob& aBlob)
 {
   MOZ_CRASH("PPendingIPCBlobParent actors should be manually constructed!");
 }
 
 bool
 BackgroundParentImpl::DeallocPPendingIPCBlobParent(PPendingIPCBlobParent* aActor)
--- a/ipc/glue/BackgroundParentImpl.h
+++ b/ipc/glue/BackgroundParentImpl.h
@@ -58,34 +58,45 @@ protected:
   virtual bool
   DeallocPBackgroundIndexedDBUtilsParent(
                                         PBackgroundIndexedDBUtilsParent* aActor)
                                         override;
 
   virtual mozilla::ipc::IPCResult
   RecvFlushPendingFileDeletions() override;
 
+  virtual PBackgroundLocalStorageCacheParent*
+  AllocPBackgroundLocalStorageCacheParent(const PrincipalInfo& aPrincipalInfo,
+                                          const nsCString& aOriginKey,
+                                          const uint32_t& aPrivateBrowsingId)
+                                          override;
+
+  virtual mozilla::ipc::IPCResult
+  RecvPBackgroundLocalStorageCacheConstructor(
+                                     PBackgroundLocalStorageCacheParent* aActor,
+                                     const PrincipalInfo& aPrincipalInfo,
+                                     const nsCString& aOriginKey,
+                                     const uint32_t& aPrivateBrowsingId)
+                                     override;
+
+  virtual bool
+  DeallocPBackgroundLocalStorageCacheParent(
+                                     PBackgroundLocalStorageCacheParent* aActor)
+                                     override;
+
   virtual PBackgroundStorageParent*
   AllocPBackgroundStorageParent(const nsString& aProfilePath) override;
 
   virtual mozilla::ipc::IPCResult
   RecvPBackgroundStorageConstructor(PBackgroundStorageParent* aActor,
                                     const nsString& aProfilePath) override;
 
   virtual bool
   DeallocPBackgroundStorageParent(PBackgroundStorageParent* aActor) override;
 
-  virtual mozilla::ipc::IPCResult
-  RecvBroadcastLocalStorageChange(const nsString& aDocumentURI,
-                                  const nsString& aKey,
-                                  const nsString& aOldValue,
-                                  const nsString& aNewValue,
-                                  const PrincipalInfo& aPrincipalInfo,
-                                  const bool& aIsPrivate) override;
-
   virtual PPendingIPCBlobParent*
   AllocPPendingIPCBlobParent(const IPCBlob& aBlob) override;
 
   virtual bool
   DeallocPPendingIPCBlobParent(PPendingIPCBlobParent* aActor) override;
 
   virtual PIPCBlobInputStreamParent*
   AllocPIPCBlobInputStreamParent(const nsID& aID,
--- a/ipc/glue/PBackground.ipdl
+++ b/ipc/glue/PBackground.ipdl
@@ -1,15 +1,16 @@
 /* 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 PAsmJSCacheEntry;
 include protocol PBackgroundIDBFactory;
 include protocol PBackgroundIndexedDBUtils;
+include protocol PBackgroundLocalStorageCache;
 include protocol PBackgroundStorage;
 include protocol PBackgroundTest;
 include protocol PBroadcastChannel;
 include protocol PCache;
 include protocol PCacheStorage;
 include protocol PCacheStreamControl;
 include protocol PClientManager;
 include protocol PFileDescriptorSet;
@@ -54,16 +55,17 @@ using mozilla::dom::asmjscache::WritePar
 namespace mozilla {
 namespace ipc {
 
 sync protocol PBackground
 {
   manages PAsmJSCacheEntry;
   manages PBackgroundIDBFactory;
   manages PBackgroundIndexedDBUtils;
+  manages PBackgroundLocalStorageCache;
   manages PBackgroundStorage;
   manages PBackgroundTest;
   manages PBroadcastChannel;
   manages PCache;
   manages PCacheStorage;
   manages PCacheStreamControl;
   manages PClientManager;
   manages PFileDescriptorSet;
@@ -92,24 +94,21 @@ parent:
 
   async PBackgroundIDBFactory(LoggingInfo loggingInfo);
 
   async PBackgroundIndexedDBUtils();
 
   // Use only for testing!
   async FlushPendingFileDeletions();
 
-  async PBackgroundStorage(nsString profilePath);
+  async PBackgroundLocalStorageCache(PrincipalInfo principalInfo,
+                                     nsCString originKey,
+                                     uint32_t privateBrowsingId);
 
-  async BroadcastLocalStorageChange(nsString documentURI,
-                                    nsString key,
-                                    nsString oldValue,
-                                    nsString newValue,
-                                    PrincipalInfo principalInfo,
-                                    bool isPrivate);
+  async PBackgroundStorage(nsString profilePath);
 
   async PVsync();
 
   async PCameras();
 
   async PUDPSocket(OptionalPrincipalInfo pInfo, nsCString filter);
   async PBroadcastChannel(PrincipalInfo pInfo, nsCString origin, nsString channel);
 
@@ -155,23 +154,16 @@ parent:
 child:
   async PCache();
   async PCacheStreamControl();
 
   async PParentToChildStream();
 
   async PPendingIPCBlob(IPCBlob blob);
 
-  async DispatchLocalStorageChange(nsString documentURI,
-                                   nsString key,
-                                   nsString oldValue,
-                                   nsString newValue,
-                                   PrincipalInfo principalInfo,
-                                   bool isPrivate);
-
 both:
   // PIPCBlobInputStream is created on the parent side only if the child starts
   // a migration.
   async PIPCBlobInputStream(nsID aID, uint64_t aSize);
 
   async PFileDescriptorSet(FileDescriptor fd);
 };