Bug 1546723 - Part 4: Use a write optimizer in LSSnapshot; r=asuth draft
authorJan Varga <jan.varga@gmail.com>
Wed, 15 May 2019 06:11:11 +0200
changeset 2008606 c35f2332126b8b7a6ff99fcb560423dc4ce9d04e
parent 2008605 12ef1daf509831e5d4a0e372a09cbdb1840780d8
child 2008607 2f6ae2152532743a874910d9dc3eccb49aced9ca
push id363926
push userjvarga@mozilla.com
push dateSat, 18 May 2019 08:19:45 +0000
treeherdertry@e89baae86dd1 [default view] [failures only]
reviewersasuth
bugs1546723
milestone68.0a1
Bug 1546723 - Part 4: Use a write optimizer in LSSnapshot; r=asuth This patch adds a write optimizer to LSSnapshot. The optimizer is only used when there are no observers for other content processes. Differential Revision: https://phabricator.services.mozilla.com/D31199
dom/localstorage/ActorsParent.cpp
dom/localstorage/LSSnapshot.cpp
dom/localstorage/LSSnapshot.h
dom/localstorage/PBackgroundLSDatabase.ipdl
dom/localstorage/PBackgroundLSSnapshot.ipdl
modules/libpref/init/all.js
--- a/dom/localstorage/ActorsParent.cpp
+++ b/dom/localstorage/ActorsParent.cpp
@@ -1763,19 +1763,23 @@ class Datastore final
   void BeginUpdateBatch(int64_t aSnapshotInitialUsage);
 
   int64_t EndUpdateBatch(int64_t aSnapshotPeakUsage);
 
   int64_t GetUsage() const { return mUsage; }
 
   int64_t RequestUpdateUsage(int64_t aRequestedSize, int64_t aMinSize);
 
-  void NotifyObservers(Database* aDatabase, const nsString& aDocumentURI,
-                       const nsString& aKey, const LSValue& aOldValue,
-                       const LSValue& aNewValue);
+  bool HasOtherProcessObservers(Database* aDatabase);
+
+  void NotifyOtherProcessObservers(Database* aDatabase,
+                                   const nsString& aDocumentURI,
+                                   const nsString& aKey,
+                                   const LSValue& aOldValue,
+                                   const LSValue& aNewValue);
 
   NS_INLINE_DECL_REFCOUNTING(Datastore)
 
  private:
   // Reference counted.
   ~Datastore();
 
   bool UpdateUsage(int64_t aDelta);
@@ -2119,16 +2123,19 @@ class Snapshot final : public PBackgroun
 
   void Finish();
 
   // IPDL methods are only called by IPDL.
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   mozilla::ipc::IPCResult RecvDeleteMe() override;
 
+  mozilla::ipc::IPCResult RecvCheckpoint(
+      nsTArray<LSWriteInfo>&& aWriteInfos) override;
+
   mozilla::ipc::IPCResult RecvCheckpointAndNotify(
       nsTArray<LSWriteAndNotifyInfo>&& aWriteAndNotifyInfos) override;
 
   mozilla::ipc::IPCResult RecvFinish() override;
 
   mozilla::ipc::IPCResult RecvLoaded() override;
 
   mozilla::ipc::IPCResult RecvLoadValueAndMoreItems(
@@ -5318,20 +5325,47 @@ int64_t Datastore::RequestUpdateUsage(in
 
   if (UpdateUsage(aMinSize)) {
     return aMinSize;
   }
 
   return 0;
 }
 
-void Datastore::NotifyObservers(Database* aDatabase,
-                                const nsString& aDocumentURI,
-                                const nsString& aKey, const LSValue& aOldValue,
-                                const LSValue& aNewValue) {
+bool Datastore::HasOtherProcessObservers(Database* aDatabase) {
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aDatabase);
+
+  if (!gObservers) {
+    return false;
+  }
+
+  nsTArray<Observer*>* array;
+  if (!gObservers->Get(mOrigin, &array)) {
+    return false;
+  }
+
+  MOZ_ASSERT(array);
+
+  PBackgroundParent* databaseBackgroundActor = aDatabase->Manager();
+
+  for (Observer* observer : *array) {
+    if (observer->Manager() != databaseBackgroundActor) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+void Datastore::NotifyOtherProcessObservers(Database* aDatabase,
+                                            const nsString& aDocumentURI,
+                                            const nsString& aKey,
+                                            const LSValue& aOldValue,
+                                            const LSValue& aNewValue) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aDatabase);
 
   if (!gObservers) {
     return;
   }
 
   nsTArray<Observer*>* array;
@@ -5686,27 +5720,30 @@ mozilla::ipc::IPCResult Database::RecvPB
 
   int64_t peakUsage = initialUsage;
 
   if (aIncreasePeakUsage) {
     int64_t size = mDatastore->RequestUpdateUsage(aRequestedSize, aMinSize);
     peakUsage += size;
   }
 
+  bool hasOtherProcessObservers = mDatastore->HasOtherProcessObservers(this);
+
   snapshot->Init(loadedItems, unknownItems, nextLoadIndex, totalLength,
                  initialUsage, peakUsage, loadState);
 
   RegisterSnapshot(snapshot);
 
   aInitInfo->addKeyToUnknownItems() = addKeyToUnknownItems;
   aInitInfo->itemInfos() = std::move(itemInfos);
   aInitInfo->totalLength() = totalLength;
   aInitInfo->initialUsage() = initialUsage;
   aInitInfo->peakUsage() = peakUsage;
   aInitInfo->loadState() = loadState;
+  aInitInfo->hasOtherProcessObservers() = hasOtherProcessObservers;
 
   return IPC_OK();
 }
 
 bool Database::DeallocPBackgroundLSSnapshotParent(
     PBackgroundLSSnapshotParent* aActor) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aActor);
@@ -5806,16 +5843,65 @@ mozilla::ipc::IPCResult Snapshot::RecvDe
 
   IProtocol* mgr = Manager();
   if (!PBackgroundLSSnapshotParent::Send__delete__(this)) {
     return IPC_FAIL_NO_REASON(mgr);
   }
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult Snapshot::RecvCheckpoint(
+    nsTArray<LSWriteInfo>&& aWriteInfos) {
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mUsage >= 0);
+  MOZ_DIAGNOSTIC_ASSERT(mPeakUsage >= mUsage);
+
+  if (NS_WARN_IF(aWriteInfos.IsEmpty())) {
+    ASSERT_UNLESS_FUZZING();
+    return IPC_FAIL_NO_REASON(this);
+  }
+
+  mDatastore->BeginUpdateBatch(mUsage);
+
+  for (uint32_t index = 0; index < aWriteInfos.Length(); index++) {
+    const LSWriteInfo& writeInfo = aWriteInfos[index];
+
+    switch (writeInfo.type()) {
+      case LSWriteInfo::TLSSetItemInfo: {
+        const LSSetItemInfo& info = writeInfo.get_LSSetItemInfo();
+
+        mDatastore->SetItem(mDatabase, info.key(), info.value());
+
+        break;
+      }
+
+      case LSWriteInfo::TLSRemoveItemInfo: {
+        const LSRemoveItemInfo& info = writeInfo.get_LSRemoveItemInfo();
+
+        mDatastore->RemoveItem(mDatabase, info.key());
+
+        break;
+      }
+
+      case LSWriteInfo::TLSClearInfo: {
+        mDatastore->Clear(mDatabase);
+
+        break;
+      }
+
+      default:
+        MOZ_CRASH("Should never get here!");
+    }
+  }
+
+  mUsage = mDatastore->EndUpdateBatch(-1);
+
+  return IPC_OK();
+}
+
 mozilla::ipc::IPCResult Snapshot::RecvCheckpointAndNotify(
     nsTArray<LSWriteAndNotifyInfo>&& aWriteAndNotifyInfos) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(mUsage >= 0);
   MOZ_DIAGNOSTIC_ASSERT(mPeakUsage >= mUsage);
 
   if (NS_WARN_IF(aWriteAndNotifyInfos.IsEmpty())) {
     ASSERT_UNLESS_FUZZING();
@@ -5830,39 +5916,41 @@ mozilla::ipc::IPCResult Snapshot::RecvCh
 
     switch (writeAndNotifyInfo.type()) {
       case LSWriteAndNotifyInfo::TLSSetItemAndNotifyInfo: {
         const LSSetItemAndNotifyInfo& info =
             writeAndNotifyInfo.get_LSSetItemAndNotifyInfo();
 
         mDatastore->SetItem(mDatabase, info.key(), info.value());
 
-        mDatastore->NotifyObservers(mDatabase, mDocumentURI, info.key(),
-                                    info.oldValue(), info.value());
+        mDatastore->NotifyOtherProcessObservers(
+            mDatabase, mDocumentURI, info.key(), info.oldValue(), info.value());
 
         break;
       }
 
       case LSWriteAndNotifyInfo::TLSRemoveItemAndNotifyInfo: {
         const LSRemoveItemAndNotifyInfo& info =
             writeAndNotifyInfo.get_LSRemoveItemAndNotifyInfo();
 
         mDatastore->RemoveItem(mDatabase, info.key());
 
-        mDatastore->NotifyObservers(mDatabase, mDocumentURI, info.key(),
-                                    info.oldValue(), VoidLSValue());
+        mDatastore->NotifyOtherProcessObservers(mDatabase, mDocumentURI,
+                                                info.key(), info.oldValue(),
+                                                VoidLSValue());
 
         break;
       }
 
       case LSWriteAndNotifyInfo::TLSClearInfo: {
         mDatastore->Clear(mDatabase);
 
-        mDatastore->NotifyObservers(mDatabase, mDocumentURI, VoidString(),
-                                    VoidLSValue(), VoidLSValue());
+        mDatastore->NotifyOtherProcessObservers(mDatabase, mDocumentURI,
+                                                VoidString(), VoidLSValue(),
+                                                VoidLSValue());
 
         break;
       }
 
       default:
         MOZ_CRASH("Should never get here!");
     }
   }
--- a/dom/localstorage/LSSnapshot.cpp
+++ b/dom/localstorage/LSSnapshot.cpp
@@ -12,24 +12,80 @@ namespace mozilla {
 namespace dom {
 
 namespace {
 
 const uint32_t kSnapshotTimeoutMs = 20000;
 
 }  // namespace
 
+/**
+ * Coalescing manipulation queue used by `LSSnapshot`.  Used by `LSSnapshot` to
+ * buffer and coalesce manipulations before they are sent to the parent process,
+ * when a Snapshot Checkpoints. (This can only be done when there are no
+ * observers for other content processes.)
+ */
+class SnapshotWriteOptimizer final
+    : public LSWriteOptimizer<nsAString, nsString> {
+ public:
+  void Enumerate(nsTArray<LSWriteInfo>& aWriteInfos);
+};
+
+void SnapshotWriteOptimizer::Enumerate(nsTArray<LSWriteInfo>& aWriteInfos) {
+  AssertIsOnOwningThread();
+
+  if (mTruncateInfo) {
+    LSClearInfo clearInfo;
+
+    aWriteInfos.AppendElement(std::move(clearInfo));
+  }
+
+  for (auto iter = mWriteInfos.ConstIter(); !iter.Done(); iter.Next()) {
+    WriteInfo* writeInfo = iter.Data();
+
+    switch (writeInfo->GetType()) {
+      case WriteInfo::InsertItem:
+      case WriteInfo::UpdateItem: {
+        auto insertItemInfo = static_cast<InsertItemInfo*>(writeInfo);
+
+        LSSetItemInfo setItemInfo;
+        setItemInfo.key() = insertItemInfo->GetKey();
+        setItemInfo.value() = LSValue(insertItemInfo->GetValue());
+
+        aWriteInfos.AppendElement(std::move(setItemInfo));
+
+        break;
+      }
+
+      case WriteInfo::DeleteItem: {
+        auto deleteItemInfo = static_cast<DeleteItemInfo*>(writeInfo);
+
+        LSRemoveItemInfo removeItemInfo;
+        removeItemInfo.key() = deleteItemInfo->GetKey();
+
+        aWriteInfos.AppendElement(std::move(removeItemInfo));
+
+        break;
+      }
+
+      default:
+        MOZ_CRASH("Bad type!");
+    }
+  }
+}
+
 LSSnapshot::LSSnapshot(LSDatabase* aDatabase)
     : mDatabase(aDatabase),
       mActor(nullptr),
       mInitLength(0),
       mLength(0),
       mExactUsage(0),
       mPeakUsage(0),
       mLoadState(LoadState::Initial),
+      mHasOtherProcessObservers(false),
       mExplicit(false),
       mHasPendingStableStateCallback(false),
       mHasPendingTimerCallback(false),
       mDirty(false)
 #ifdef DEBUG
       ,
       mInitialized(false),
       mSentFinish(false)
@@ -97,22 +153,30 @@ nsresult LSSnapshot::Init(const nsAStrin
     MOZ_ASSERT(loadState == LoadState::AllOrderedItems);
   }
 
   mExactUsage = aInitInfo.initialUsage();
   mPeakUsage = aInitInfo.peakUsage();
 
   mLoadState = aInitInfo.loadState();
 
+  mHasOtherProcessObservers = aInitInfo.hasOtherProcessObservers();
+
   mExplicit = aExplicit;
 
 #ifdef DEBUG
   mInitialized = true;
 #endif
 
+  if (mHasOtherProcessObservers) {
+    mWriteAndNotifyInfos = new nsTArray<LSWriteAndNotifyInfo>();
+  } else {
+    mWriteOptimizer = new SnapshotWriteOptimizer();
+  }
+
   if (!mExplicit) {
     mTimer = NS_NewTimer();
     MOZ_ASSERT(mTimer);
 
     ScheduleStableStateCallback();
   }
 
   return NS_OK;
@@ -236,22 +300,34 @@ nsresult LSSnapshot::SetItem(const nsASt
       }
       return rv;
     }
 
     if (oldValue.IsVoid() && mLoadState == LoadState::Partial) {
       mLength++;
     }
 
-    LSSetItemAndNotifyInfo setItemAndNotifyInfo;
-    setItemAndNotifyInfo.key() = aKey;
-    setItemAndNotifyInfo.oldValue() = LSValue(oldValue);
-    setItemAndNotifyInfo.value() = LSValue(aValue);
+    if (mHasOtherProcessObservers) {
+      MOZ_ASSERT(mWriteAndNotifyInfos);
+
+      LSSetItemAndNotifyInfo setItemAndNotifyInfo;
+      setItemAndNotifyInfo.key() = aKey;
+      setItemAndNotifyInfo.oldValue() = LSValue(oldValue);
+      setItemAndNotifyInfo.value() = LSValue(aValue);
 
-    mWriteAndNotifyInfos.AppendElement(std::move(setItemAndNotifyInfo));
+      mWriteAndNotifyInfos->AppendElement(std::move(setItemAndNotifyInfo));
+    } else {
+      MOZ_ASSERT(mWriteOptimizer);
+
+      if (oldValue.IsVoid()) {
+        mWriteOptimizer->InsertItem(aKey, aValue);
+      } else {
+        mWriteOptimizer->UpdateItem(aKey, aValue);
+      }
+    }
   }
 
   aNotifyInfo.changed() = changed;
   aNotifyInfo.oldValue() = oldValue;
 
   return NS_OK;
 }
 
@@ -282,21 +358,29 @@ nsresult LSSnapshot::RemoveItem(const ns
 
     DebugOnly<nsresult> rv = UpdateUsage(delta);
     MOZ_ASSERT(NS_SUCCEEDED(rv));
 
     if (mLoadState == LoadState::Partial) {
       mLength--;
     }
 
-    LSRemoveItemAndNotifyInfo removeItemAndNotifyInfo;
-    removeItemAndNotifyInfo.key() = aKey;
-    removeItemAndNotifyInfo.oldValue() = LSValue(oldValue);
+    if (mHasOtherProcessObservers) {
+      MOZ_ASSERT(mWriteAndNotifyInfos);
+
+      LSRemoveItemAndNotifyInfo removeItemAndNotifyInfo;
+      removeItemAndNotifyInfo.key() = aKey;
+      removeItemAndNotifyInfo.oldValue() = LSValue(oldValue);
 
-    mWriteAndNotifyInfos.AppendElement(std::move(removeItemAndNotifyInfo));
+      mWriteAndNotifyInfos->AppendElement(std::move(removeItemAndNotifyInfo));
+    } else {
+      MOZ_ASSERT(mWriteOptimizer);
+
+      mWriteOptimizer->DeleteItem(aKey);
+    }
   }
 
   aNotifyInfo.changed() = changed;
   aNotifyInfo.oldValue() = oldValue;
 
   return NS_OK;
 }
 
@@ -329,19 +413,27 @@ nsresult LSSnapshot::Clear(LSNotifyInfo&
   } else {
     changed = true;
 
     DebugOnly<nsresult> rv = UpdateUsage(-mExactUsage);
     MOZ_ASSERT(NS_SUCCEEDED(rv));
 
     mValues.Clear();
 
-    LSClearInfo clearInfo;
+    if (mHasOtherProcessObservers) {
+      MOZ_ASSERT(mWriteAndNotifyInfos);
+
+      LSClearInfo clearInfo;
 
-    mWriteAndNotifyInfos.AppendElement(std::move(clearInfo));
+      mWriteAndNotifyInfos->AppendElement(std::move(clearInfo));
+    } else {
+      MOZ_ASSERT(mWriteOptimizer);
+
+      mWriteOptimizer->Truncate();
+    }
   }
 
   aNotifyInfo.changed() = changed;
 
   return NS_OK;
 }
 
 void LSSnapshot::MarkDirty() {
@@ -588,38 +680,76 @@ nsresult LSSnapshot::EnsureAllKeys() {
   }
 
   nsDataHashtable<nsStringHashKey, nsString> newValues;
 
   for (auto key : keys) {
     newValues.Put(key, VoidString());
   }
 
-  for (uint32_t index = 0; index < mWriteAndNotifyInfos.Length(); index++) {
-    const LSWriteAndNotifyInfo& writeAndNotifyInfo =
-        mWriteAndNotifyInfos[index];
+  if (mHasOtherProcessObservers) {
+    MOZ_ASSERT(mWriteAndNotifyInfos);
+
+    if (!mWriteAndNotifyInfos->IsEmpty()) {
+      for (uint32_t index = 0; index < mWriteAndNotifyInfos->Length();
+           index++) {
+        const LSWriteAndNotifyInfo& writeAndNotifyInfo =
+            mWriteAndNotifyInfos->ElementAt(index);
 
-    switch (writeAndNotifyInfo.type()) {
-      case LSWriteAndNotifyInfo::TLSSetItemAndNotifyInfo: {
-        newValues.Put(writeAndNotifyInfo.get_LSSetItemAndNotifyInfo().key(),
-                      VoidString());
-        break;
+        switch (writeAndNotifyInfo.type()) {
+          case LSWriteAndNotifyInfo::TLSSetItemAndNotifyInfo: {
+            newValues.Put(writeAndNotifyInfo.get_LSSetItemAndNotifyInfo().key(),
+                          VoidString());
+            break;
+          }
+          case LSWriteAndNotifyInfo::TLSRemoveItemAndNotifyInfo: {
+            newValues.Remove(
+                writeAndNotifyInfo.get_LSRemoveItemAndNotifyInfo().key());
+            break;
+          }
+          case LSWriteAndNotifyInfo::TLSClearInfo: {
+            newValues.Clear();
+            break;
+          }
+
+          default:
+            MOZ_CRASH("Should never get here!");
+        }
       }
-      case LSWriteAndNotifyInfo::TLSRemoveItemAndNotifyInfo: {
-        newValues.Remove(
-            writeAndNotifyInfo.get_LSRemoveItemAndNotifyInfo().key());
-        break;
+    }
+  } else {
+    MOZ_ASSERT(mWriteOptimizer);
+
+    if (mWriteOptimizer->HasWrites()) {
+      nsTArray<LSWriteInfo> writeInfos;
+      mWriteOptimizer->Enumerate(writeInfos);
+
+      MOZ_ASSERT(!writeInfos.IsEmpty());
+
+      for (uint32_t index = 0; index < writeInfos.Length(); index++) {
+        const LSWriteInfo& writeInfo = writeInfos[index];
+
+        switch (writeInfo.type()) {
+          case LSWriteInfo::TLSSetItemInfo: {
+            newValues.Put(writeInfo.get_LSSetItemInfo().key(), VoidString());
+            break;
+          }
+          case LSWriteInfo::TLSRemoveItemInfo: {
+            newValues.Remove(writeInfo.get_LSRemoveItemInfo().key());
+            break;
+          }
+          case LSWriteInfo::TLSClearInfo: {
+            newValues.Clear();
+            break;
+          }
+
+          default:
+            MOZ_CRASH("Should never get here!");
+        }
       }
-      case LSWriteAndNotifyInfo::TLSClearInfo: {
-        newValues.Clear();
-        break;
-      }
-
-      default:
-        MOZ_CRASH("Should never get here!");
     }
   }
 
   MOZ_ASSERT_IF(mLoadState == LoadState::AllUnorderedItems,
                 newValues.Count() == mValues.Count());
 
   for (auto iter = newValues.Iter(); !iter.Done(); iter.Next()) {
     nsString value;
@@ -677,20 +807,37 @@ nsresult LSSnapshot::UpdateUsage(int64_t
 }
 
 nsresult LSSnapshot::Checkpoint() {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(mInitialized);
   MOZ_ASSERT(!mSentFinish);
 
-  if (!mWriteAndNotifyInfos.IsEmpty()) {
-    MOZ_ALWAYS_TRUE(mActor->SendCheckpointAndNotify(mWriteAndNotifyInfos));
+  if (mHasOtherProcessObservers) {
+    MOZ_ASSERT(mWriteAndNotifyInfos);
+
+    if (!mWriteAndNotifyInfos->IsEmpty()) {
+      MOZ_ALWAYS_TRUE(mActor->SendCheckpointAndNotify(*mWriteAndNotifyInfos));
+
+      mWriteAndNotifyInfos->Clear();
+    }
+  } else {
+    MOZ_ASSERT(mWriteOptimizer);
 
-    mWriteAndNotifyInfos.Clear();
+    if (mWriteOptimizer->HasWrites()) {
+      nsTArray<LSWriteInfo> writeInfos;
+      mWriteOptimizer->Enumerate(writeInfos);
+
+      MOZ_ASSERT(!writeInfos.IsEmpty());
+
+      MOZ_ALWAYS_TRUE(mActor->SendCheckpoint(writeInfos));
+
+      mWriteOptimizer->Reset();
+    }
   }
 
   return NS_OK;
 }
 
 nsresult LSSnapshot::Finish() {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mDatabase);
--- a/dom/localstorage/LSSnapshot.h
+++ b/dom/localstorage/LSSnapshot.h
@@ -12,16 +12,17 @@
 namespace mozilla {
 namespace dom {
 
 class LSDatabase;
 class LSNotifyInfo;
 class LSSnapshotChild;
 class LSSnapshotInitInfo;
 class LSWriteAndNotifyInfo;
+class SnapshotWriteOptimizer;
 
 class LSSnapshot final : public nsIRunnable {
  public:
   /**
    * The LoadState expresses what subset of information a snapshot has from the
    * authoritative Datastore in the parent process.  The initial snapshot is
    * populated heuristically based on the size of the keys and size of the items
    * (inclusive of the key value; item is key+value, not just value) of the
@@ -74,25 +75,27 @@ class LSSnapshot final : public nsIRunna
 
   nsCOMPtr<nsITimer> mTimer;
 
   LSSnapshotChild* mActor;
 
   nsTHashtable<nsStringHashKey> mLoadedItems;
   nsTHashtable<nsStringHashKey> mUnknownItems;
   nsDataHashtable<nsStringHashKey, nsString> mValues;
-  nsTArray<LSWriteAndNotifyInfo> mWriteAndNotifyInfos;
+  nsAutoPtr<SnapshotWriteOptimizer> mWriteOptimizer;
+  nsAutoPtr<nsTArray<LSWriteAndNotifyInfo>> mWriteAndNotifyInfos;
 
   uint32_t mInitLength;
   uint32_t mLength;
   int64_t mExactUsage;
   int64_t mPeakUsage;
 
   LoadState mLoadState;
 
+  bool mHasOtherProcessObservers;
   bool mExplicit;
   bool mHasPendingStableStateCallback;
   bool mHasPendingTimerCallback;
   bool mDirty;
 
 #ifdef DEBUG
   bool mInitialized;
   bool mSentFinish;
--- a/dom/localstorage/PBackgroundLSDatabase.ipdl
+++ b/dom/localstorage/PBackgroundLSDatabase.ipdl
@@ -54,16 +54,21 @@ struct LSSnapshotInitInfo
    * 0 or more bytes of space.  If space was available, the increase will be the
    * `requestedSize` from the PBackgroundLSSnapshot constructor.  If the
    * LocalStorage usage was already close to the limit, then the fallback is the
    * `minSize` requested, or 0 if there wasn't space for that.
    */
   int64_t peakUsage;
   // See `LSSnapshot::LoadState` in `LSSnapshot.h`
   LoadState loadState;
+  /**
+   * Boolean indicating whether there where cross-process observers registered
+   * for this origin at the time the snapshot was created.
+   */
+  bool hasOtherProcessObservers;
 };
 
 /**
  * This protocol is asynchronously created via constructor on PBackground but
  * has synchronous semantics from the perspective of content on the main thread.
  * The construction potentially involves waiting for disk I/O to load the
  * LocalStorage data from disk as well as related QuotaManager checks, so async
  * calls to PBackground are the only viable mechanism because blocking
--- a/dom/localstorage/PBackgroundLSSnapshot.ipdl
+++ b/dom/localstorage/PBackgroundLSSnapshot.ipdl
@@ -10,20 +10,41 @@ include PBackgroundLSSharedTypes;
 include "mozilla/dom/localstorage/SerializationHelpers.h";
 
 using mozilla::dom::LSValue
   from "mozilla/dom/LSValue.h";
 
 namespace mozilla {
 namespace dom {
 
+struct LSSetItemInfo
+{
+  nsString key;
+  LSValue value;
+};
+
+struct LSRemoveItemInfo
+{
+  nsString key;
+};
+
 struct LSClearInfo
 {
 };
 
+/**
+ * Union of LocalStorage mutation types.
+ */
+union LSWriteInfo
+{
+  LSSetItemInfo;
+  LSRemoveItemInfo;
+  LSClearInfo;
+};
+
 struct LSSetItemAndNotifyInfo
 {
   nsString key;
   LSValue oldValue;
   LSValue value;
 };
 
 struct LSRemoveItemAndNotifyInfo
@@ -44,16 +65,18 @@ union LSWriteAndNotifyInfo
 
 sync protocol PBackgroundLSSnapshot
 {
   manager PBackgroundLSDatabase;
 
 parent:
   async DeleteMe();
 
+  async Checkpoint(LSWriteInfo[] writeInfos);
+
   async CheckpointAndNotify(LSWriteAndNotifyInfo[] writeAndNotifyInfos);
 
   async Finish();
 
   async Loaded();
 
   /**
    * Invoked on demand to load an item that didn't fit into the initial
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1283,17 +1283,17 @@ pref("dom.storage.enabled", true);
 pref("dom.storage.next_gen", true);
 #else
 pref("dom.storage.next_gen", false);
 #endif
 pref("dom.storage.default_quota",      5120);
 pref("dom.storage.shadow_writes", true);
 pref("dom.storage.snapshot_prefill", 16384);
 pref("dom.storage.snapshot_gradual_prefill", 4096);
-pref("dom.storage.snapshot_reusing", true);
+pref("dom.storage.snapshot_reusing", false);
 pref("dom.storage.testing", false);
 pref("dom.storage.client_validation", true);
 
 pref("dom.send_after_paint_to_content", false);
 
 // Timeout clamp in ms for timeouts we clamp
 pref("dom.min_timeout_value", 4);
 // And for background windows