Bug 1286798 - Part 36: Allow snapshot initialization to a specific load state; r=asuth
authorJan Varga <jan.varga@gmail.com>
Thu, 29 Nov 2018 21:49:10 +0100
changeset 508034 d5f866efde44d0e6d375dc7bcb24b86f21023103
parent 508033 6214aafc061f09377c40eca490cc12621169c892
child 508035 52e5be69837d8effb5f58181ac0e9166bd440fd7
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth
bugs1286798
milestone65.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 1286798 - Part 36: Allow snapshot initialization to a specific load state; r=asuth Before this patch, it was only possible to initialize a snapshot to the Partial state or AllOrderedItems state. Now there's a third state AllOrderedKeys. This improves performance by eliminating sync calls to parent process when we know nothing about a key in content process (in that case we have to use a sync call to the parent process to see if there's a value for it). With this patch we always try to send all keys to content when a snapshot is being initialized. For this to work efficiently, we cache the size of all keys. Having cached size of all keys also allows us to just iterate the mValues hashtable when the size of keys is bigger than snapshot prefill threshold (instead of iterating over the mKeys array and joining with mValues for each particular key). There's some additional cleanup in snapshot info construction and Datastore::SetItem/RemoveItem/Clear methods.
dom/localstorage/ActorsParent.cpp
dom/localstorage/LSSnapshot.cpp
dom/localstorage/LSSnapshot.h
dom/localstorage/PBackgroundLSDatabase.ipdl
dom/localstorage/SerializationHelpers.h
dom/localstorage/moz.build
--- a/dom/localstorage/ActorsParent.cpp
+++ b/dom/localstorage/ActorsParent.cpp
@@ -1300,26 +1300,28 @@ class Datastore final
   nsDataHashtable<nsStringHashKey, uint32_t> mUpdateBatchRemovals;
   nsTArray<nsString> mUpdateBatchAppends;
   nsTArray<nsString> mKeys;
   nsTArray<int64_t> mPendingUsageDeltas;
   const nsCString mOrigin;
   const uint32_t mPrivateBrowsingId;
   int64_t mUsage;
   int64_t mUpdateBatchUsage;
+  int64_t mSizeOfKeys;
   bool mClosed;
 #ifdef DEBUG
   bool mInUpdateBatch;
 #endif
 
 public:
   // Created by PrepareDatastoreOp.
   Datastore(const nsACString& aOrigin,
             uint32_t aPrivateBrowsingId,
             int64_t aUsage,
+            int64_t aSizeOfKeys,
             already_AddRefed<DirectoryLock>&& aDirectoryLock,
             already_AddRefed<Connection>&& aConnection,
             already_AddRefed<QuotaObject>&& aQuotaObject,
             nsDataHashtable<nsStringHashKey, nsString>& aValues,
             nsTArray<nsString>& aKeys);
 
   const nsCString&
   Origin() const
@@ -1334,23 +1336,16 @@ public:
   }
 
   bool
   IsPersistent() const
   {
     return mPrivateBrowsingId == 0;
   }
 
-  int64_t
-  Usage() const
-  {
-    AssertIsOnBackgroundThread();
-    return mUsage;
-  }
-
   void
   Close();
 
   bool
   IsClosed() const
   {
     AssertIsOnBackgroundThread();
 
@@ -1389,22 +1384,24 @@ public:
 #endif
 
   void
   NoteActiveDatabase(Database* aDatabase);
 
   void
   NoteInactiveDatabase(Database* aDatabase);
 
-  uint32_t
-  GetLength() const;
-
   void
-  GetItemInfos(nsTHashtable<nsStringHashKey>& aLoadedItems,
-               nsTArray<LSItemInfo>& aItemInfos);
+  GetSnapshotInitInfo(int64_t aRequestedSize,
+                      nsTHashtable<nsStringHashKey>& aLoadedItems,
+                      nsTArray<LSItemInfo>& aItemInfos,
+                      uint32_t& aTotalLength,
+                      int64_t& aInitialUsage,
+                      int64_t& aPeakUsage,
+                      LSSnapshot::LoadState& aLoadState);
 
   void
   GetItem(const nsString& aKey, nsString& aValue) const;
 
   void
   GetKeys(nsTArray<nsString>& aKeys) const;
 
   void
@@ -1693,31 +1690,34 @@ public:
   Snapshot(Database* aDatabase,
            const nsAString& aDocumentURI);
 
   void
   Init(nsTHashtable<nsStringHashKey>& aLoadedItems,
        uint32_t aTotalLength,
        int64_t aInitialUsage,
        int64_t aPeakUsage,
-       bool aFullPrefill)
+       LSSnapshot::LoadState aLoadState)
   {
     AssertIsOnBackgroundThread();
     MOZ_ASSERT(aInitialUsage >= 0);
     MOZ_ASSERT(aPeakUsage >= aInitialUsage);
-    MOZ_ASSERT_IF(aFullPrefill, aLoadedItems.Count() == 0);
+    MOZ_ASSERT_IF(aLoadState == LSSnapshot::LoadState::AllOrderedItems,
+                  aLoadedItems.Count() == 0);
     MOZ_ASSERT(mTotalLength == 0);
     MOZ_ASSERT(mInitialUsage == -1);
     MOZ_ASSERT(mPeakUsage == -1);
 
     mLoadedItems.SwapElements(aLoadedItems);
     mTotalLength = aTotalLength;
     mInitialUsage = aInitialUsage;
     mPeakUsage = aPeakUsage;
-    if (aFullPrefill) {
+    if (aLoadState == LSSnapshot::LoadState::AllOrderedKeys) {
+      mLoadKeysReceived = true;
+    } else if (aLoadState == LSSnapshot::LoadState::AllOrderedItems) {
       mLoadedReceived = true;
       mLoadedAllItems = true;
       mLoadKeysReceived = true;
     }
   }
 
   void
   SaveItem(const nsAString& aKey,
@@ -1946,16 +1946,17 @@ class PrepareDatastoreOp
   const LSRequestPrepareDatastoreParams mParams;
   nsCString mSuffix;
   nsCString mGroup;
   nsCString mMainThreadOrigin;
   nsCString mOrigin;
   nsString mDatabaseFilePath;
   uint32_t mPrivateBrowsingId;
   int64_t mUsage;
+  int64_t mSizeOfKeys;
   NestedState mNestedState;
   bool mDatabaseNotAvailable;
   bool mRequestedDirectoryLock;
   bool mInvalidated;
 
 #ifdef DEBUG
   int64_t mDEBUGUsage;
 #endif
@@ -3532,28 +3533,30 @@ ConnectionThread::Shutdown()
 
 /*******************************************************************************
  * Datastore
  ******************************************************************************/
 
 Datastore::Datastore(const nsACString& aOrigin,
                      uint32_t aPrivateBrowsingId,
                      int64_t aUsage,
+                     int64_t aSizeOfKeys,
                      already_AddRefed<DirectoryLock>&& aDirectoryLock,
                      already_AddRefed<Connection>&& aConnection,
                      already_AddRefed<QuotaObject>&& aQuotaObject,
                      nsDataHashtable<nsStringHashKey, nsString>& aValues,
                      nsTArray<nsString>& aKeys)
   : mDirectoryLock(std::move(aDirectoryLock))
   , mConnection(std::move(aConnection))
   , mQuotaObject(std::move(aQuotaObject))
   , mOrigin(aOrigin)
   , mPrivateBrowsingId(aPrivateBrowsingId)
   , mUsage(aUsage)
   , mUpdateBatchUsage(-1)
+  , mSizeOfKeys(aSizeOfKeys)
   , mClosed(false)
 #ifdef DEBUG
   , mInUpdateBatch(false)
 #endif
 {
   AssertIsOnBackgroundThread();
 
   mValues.SwapElements(aValues);
@@ -3748,53 +3751,95 @@ Datastore::NoteInactiveDatabase(Database
       DebugOnly<bool> ok = UpdateUsage(finalDelta);
       MOZ_ASSERT(ok);
     }
 
     mPendingUsageDeltas.Clear();
   }
 }
 
-uint32_t
-Datastore::GetLength() const
-{
-  AssertIsOnBackgroundThread();
-  MOZ_ASSERT(!mClosed);
-
-  return mValues.Count();
-}
-
 void
-Datastore::GetItemInfos(nsTHashtable<nsStringHashKey>& aLoadedItems,
-                        nsTArray<LSItemInfo>& aItemInfos)
+Datastore::GetSnapshotInitInfo(int64_t aRequestedSize,
+                               nsTHashtable<nsStringHashKey>& aLoadedItems,
+                               nsTArray<LSItemInfo>& aItemInfos,
+                               uint32_t& aTotalLength,
+                               int64_t& aInitialUsage,
+                               int64_t& aPeakUsage,
+                               LSSnapshot::LoadState& aLoadState)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(!mClosed);
 
+#ifdef DEBUG
+  int64_t sizeOfKeys = 0;
+  for (auto key : mKeys) {
+    sizeOfKeys += static_cast<int64_t>(key.Length());
+  }
+  MOZ_ASSERT(mSizeOfKeys == sizeOfKeys);
+#endif
+
   int64_t size = 0;
-  for (auto key : mKeys) {
+  if (mSizeOfKeys <= gSnapshotPrefill) {
     nsString value;
-    DebugOnly<bool> hasValue = mValues.Get(key, &value);
-    MOZ_ASSERT(hasValue);
-
-    size += static_cast<int64_t>(key.Length()) +
-            static_cast<int64_t>(value.Length());
-
-    if (size > gSnapshotPrefill) {
-      return;
+    for (auto key : mKeys) {
+      if (!value.IsVoid()) {
+        DebugOnly<bool> hasValue = mValues.Get(key, &value);
+        MOZ_ASSERT(hasValue);
+
+        size += static_cast<int64_t>(key.Length()) +
+                static_cast<int64_t>(value.Length());
+
+        if (size > gSnapshotPrefill) {
+          value.SetIsVoid(true);
+        } else {
+          aLoadedItems.PutEntry(key);
+        }
+      }
+
+      LSItemInfo* itemInfo = aItemInfos.AppendElement();
+      itemInfo->key() = key;
+      itemInfo->value() = value;
+    }
+
+    if (value.IsVoid()) {
+      aLoadState = LSSnapshot::LoadState::AllOrderedKeys;
+    } else {
+      aLoadedItems.Clear();
+      aLoadState = LSSnapshot::LoadState::AllOrderedItems;
     }
-
-    aLoadedItems.PutEntry(key);
-
-    LSItemInfo* itemInfo = aItemInfos.AppendElement();
-    itemInfo->key() = key;
-    itemInfo->value() = value;
-  }
-
-  aLoadedItems.Clear();
+  } else {
+    for (auto iter = mValues.ConstIter(); !iter.Done(); iter.Next()) {
+      const nsAString& key = iter.Key();
+      const nsString& value = iter.Data();
+
+      size += static_cast<int64_t>(key.Length()) +
+              static_cast<int64_t>(value.Length());
+
+      if (size > gSnapshotPrefill) {
+        break;
+      }
+
+      aLoadedItems.PutEntry(key);
+
+      LSItemInfo* itemInfo = aItemInfos.AppendElement();
+      itemInfo->key() = iter.Key();
+      itemInfo->value() = iter.Data();
+    }
+
+    MOZ_ASSERT(aItemInfos.Length() < mKeys.Length());
+    aLoadState = LSSnapshot::LoadState::Partial;
+  }
+
+  aTotalLength = mValues.Count();
+
+  aInitialUsage = mUsage;
+  aPeakUsage = aInitialUsage;
+  if (aRequestedSize && UpdateUsage(aRequestedSize)) {
+    aPeakUsage += aRequestedSize;
+  }
 }
 
 void
 Datastore::GetItem(const nsString& aKey, nsString& aValue) const
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(!mClosed);
 
@@ -3823,37 +3868,34 @@ Datastore::SetItem(Database* aDatabase,
   MOZ_ASSERT(aDatabase);
   MOZ_ASSERT(!mClosed);
   MOZ_ASSERT(mInUpdateBatch);
 
   nsString oldValue;
   GetItem(aKey, oldValue);
 
   if (oldValue != aValue || oldValue.IsVoid() != aValue.IsVoid()) {
-    bool affectsOrder;
-
-    int64_t delta = static_cast<int64_t>(aValue.Length()) -
-                    static_cast<int64_t>(oldValue.Length());
-
-    if (oldValue.IsVoid()) {
-      affectsOrder = true;
-
-      delta += static_cast<int64_t>(aKey.Length());
-
-      mUpdateBatchAppends.AppendElement(aKey);
-    } else {
-      affectsOrder = false;
-    }
-
-    mUpdateBatchUsage += delta;
-
-    NotifySnapshots(aDatabase, aKey, oldValue, affectsOrder);
+    bool isNewItem = oldValue.IsVoid();
+
+    NotifySnapshots(aDatabase, aKey, oldValue, /* affectsOrder */ isNewItem);
 
     mValues.Put(aKey, aValue);
 
+    if (isNewItem) {
+      mUpdateBatchAppends.AppendElement(aKey);
+
+      mUpdateBatchUsage += static_cast<int64_t>(aKey.Length()) +
+                           static_cast<int64_t>(aValue.Length());
+
+      mSizeOfKeys += static_cast<int64_t>(aKey.Length());
+    } else {
+      mUpdateBatchUsage += static_cast<int64_t>(aValue.Length()) -
+                           static_cast<int64_t>(oldValue.Length());
+    }
+
     if (IsPersistent()) {
       mConnection->SetItem(aKey, aValue);
     }
   }
 
   NotifyObservers(aDatabase, aDocumentURI, aKey, aOldValue, aValue);
 }
 
@@ -3867,31 +3909,31 @@ Datastore::RemoveItem(Database* aDatabas
   MOZ_ASSERT(aDatabase);
   MOZ_ASSERT(!mClosed);
   MOZ_ASSERT(mInUpdateBatch);
 
   nsString oldValue;
   GetItem(aKey, oldValue);
 
   if (!oldValue.IsVoid()) {
+    NotifySnapshots(aDatabase, aKey, oldValue, /* aAffectsOrder */ true);
+
+    mValues.Remove(aKey);
+
     auto entry = mUpdateBatchRemovals.LookupForAdd(aKey);
     if (entry) {
       entry.Data()++;
     } else {
       entry.OrInsert([]() { return 1; });
     }
 
-    int64_t delta = -(static_cast<int64_t>(aKey.Length()) +
-                      static_cast<int64_t>(oldValue.Length()));
-
-    mUpdateBatchUsage += delta;
-
-    NotifySnapshots(aDatabase, aKey, oldValue, /* aAffectsOrder */ true);
-
-    mValues.Remove(aKey);
+    mUpdateBatchUsage -= (static_cast<int64_t>(aKey.Length()) +
+                          static_cast<int64_t>(oldValue.Length()));
+
+    mSizeOfKeys -= static_cast<int64_t>(aKey.Length());
 
     if (IsPersistent()) {
       mConnection->RemoveItem(aKey);
     }
   }
 
   NotifyObservers(aDatabase, aDocumentURI, aKey, aOldValue, VoidString());
 }
@@ -3901,34 +3943,38 @@ Datastore::Clear(Database* aDatabase,
                  const nsString& aDocumentURI)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aDatabase);
   MOZ_ASSERT(!mClosed);
   MOZ_ASSERT(mInUpdateBatch);
 
   if (mValues.Count()) {
-    mUpdateBatchRemovals.Clear();
-    mUpdateBatchAppends.Clear();
-
+    int64_t updateBatchUsage = mUpdateBatchUsage;
     for (auto iter = mValues.ConstIter(); !iter.Done(); iter.Next()) {
       const nsAString& key = iter.Key();
       const nsAString& value = iter.Data();
 
-      int64_t delta = -(static_cast<int64_t>(key.Length()) +
-                        static_cast<int64_t>(value.Length()));
-
-      mUpdateBatchUsage += delta;
+      updateBatchUsage -= (static_cast<int64_t>(key.Length()) +
+                           static_cast<int64_t>(value.Length()));
 
       NotifySnapshots(aDatabase, key, value, /* aAffectsOrder */ true);
     }
 
     mValues.Clear();
+
+    mUpdateBatchRemovals.Clear();
+    mUpdateBatchAppends.Clear();
+
     mKeys.Clear();
 
+    mUpdateBatchUsage = updateBatchUsage;
+
+    mSizeOfKeys = 0;
+
     if (IsPersistent()) {
       mConnection->Clear();
     }
   }
 
   NotifyObservers(aDatabase,
                   aDocumentURI,
                   VoidString(),
@@ -3943,17 +3989,20 @@ Datastore::PrivateBrowsingClear()
   MOZ_ASSERT(mPrivateBrowsingId);
   MOZ_ASSERT(!mClosed);
 
   if (mValues.Count()) {
     DebugOnly<bool> ok = UpdateUsage(-mUsage);
     MOZ_ASSERT(ok);
 
     mValues.Clear();
+
     mKeys.Clear();
+
+    mSizeOfKeys = 0;
   }
 }
 
 void
 Datastore::BeginUpdateBatch(int64_t aSnapshotInitialUsage)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aSnapshotInitialUsage >= 0);
@@ -4387,47 +4436,46 @@ mozilla::ipc::IPCResult
 Database::RecvPBackgroundLSSnapshotConstructor(
                                             PBackgroundLSSnapshotParent* aActor,
                                             const nsString& aDocumentURI,
                                             const int64_t& aRequestedSize,
                                             LSSnapshotInitInfo* aInitInfo)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aRequestedSize >= 0);
+  MOZ_ASSERT(aInitInfo);
   MOZ_ASSERT(!mAllowedToClose);
 
   auto* snapshot = static_cast<Snapshot*>(aActor);
 
   // TODO: This can be optimized depending on which operation triggers snapshot
   //       creation. For example clear() doesn't need to receive items at all.
   nsTHashtable<nsStringHashKey> loadedItems;
   nsTArray<LSItemInfo> itemInfos;
-  mDatastore->GetItemInfos(loadedItems, itemInfos);
-
-  uint32_t totalLength = mDatastore->GetLength();
-
-  int64_t initialUsage = mDatastore->Usage();
-
-  int64_t peakUsage = initialUsage;
-  if (aRequestedSize && mDatastore->UpdateUsage(aRequestedSize)) {
-    peakUsage += aRequestedSize;
-  }
-
-  snapshot->Init(loadedItems,
-                 totalLength,
-                 initialUsage,
-                 peakUsage,
-                 /* aFullPrefill */ itemInfos.Length() == totalLength);
+  uint32_t totalLength;
+  int64_t initialUsage;
+  int64_t peakUsage;
+  LSSnapshot::LoadState loadState;
+  mDatastore->GetSnapshotInitInfo(aRequestedSize,
+                                  loadedItems,
+                                  itemInfos,
+                                  totalLength,
+                                  initialUsage,
+                                  peakUsage,
+                                  loadState);
+
+  snapshot->Init(loadedItems, totalLength, initialUsage, peakUsage, loadState);
 
   RegisterSnapshot(snapshot);
 
   aInitInfo->itemInfos() = std::move(itemInfos);
   aInitInfo->totalLength() = totalLength;
   aInitInfo->initialUsage() = initialUsage;
   aInitInfo->peakUsage() = peakUsage;
+  aInitInfo->loadState() = loadState;
 
   return IPC_OK();
 }
 
 bool
 Database::DeallocPBackgroundLSSnapshotParent(
                                             PBackgroundLSSnapshotParent* aActor)
 {
@@ -5005,16 +5053,17 @@ LSRequestBase::RecvFinish()
 PrepareDatastoreOp::PrepareDatastoreOp(nsIEventTarget* aMainEventTarget,
                                        const LSRequestParams& aParams)
   : LSRequestBase(aMainEventTarget)
   , mMainEventTarget(aMainEventTarget)
   , mLoadDataOp(nullptr)
   , mParams(aParams.get_LSRequestPrepareDatastoreParams())
   , mPrivateBrowsingId(0)
   , mUsage(0)
+  , mSizeOfKeys(0)
   , mNestedState(NestedState::BeforeNesting)
   , mDatabaseNotAvailable(false)
   , mRequestedDirectoryLock(false)
   , mInvalidated(false)
 #ifdef DEBUG
   , mDEBUGUsage(0)
 #endif
 {
@@ -5815,16 +5864,17 @@ PrepareDatastoreOp::GetResponse(LSReques
     if (mPrivateBrowsingId == 0) {
       quotaObject = GetQuotaObject();
       MOZ_ASSERT(quotaObject);
     }
 
     mDatastore = new Datastore(mOrigin,
                                mPrivateBrowsingId,
                                mUsage,
+                               mSizeOfKeys,
                                mDirectoryLock.forget(),
                                mConnection.forget(),
                                quotaObject.forget(),
                                mValues,
                                mKeys);
 
     mDatastore->NoteLivePrepareDatastoreOp(this);
 
@@ -6030,16 +6080,17 @@ LoadDataOp::DoDatastoreWork()
     nsString value;
     rv = stmt->GetString(1, value);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     mPrepareDatastoreOp->mValues.Put(key, value);
     mPrepareDatastoreOp->mKeys.AppendElement(key);
+    mPrepareDatastoreOp->mSizeOfKeys += key.Length();
 #ifdef DEBUG
     mPrepareDatastoreOp->mDEBUGUsage += key.Length() + value.Length();
 #endif
   }
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
--- a/dom/localstorage/LSSnapshot.cpp
+++ b/dom/localstorage/LSSnapshot.cpp
@@ -55,42 +55,53 @@ LSSnapshot::Init(const LSSnapshotInitInf
                  bool aExplicit)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(mLoadState == LoadState::Initial);
   MOZ_ASSERT(!mInitialized);
   MOZ_ASSERT(!mSentFinish);
 
+  if (aExplicit) {
+    mSelfRef = this;
+  } else {
+    nsCOMPtr<nsIRunnable> runnable = this;
+    nsContentUtils::RunInStableState(runnable.forget());
+  }
+
+  LoadState loadState = aInitInfo.loadState();
+
   const nsTArray<LSItemInfo>& itemInfos = aInitInfo.itemInfos();
   for (uint32_t i = 0; i < itemInfos.Length(); i++) {
     const LSItemInfo& itemInfo = itemInfos[i];
-    mLoadedItems.PutEntry(itemInfo.key());
-    mValues.Put(itemInfo.key(), itemInfo.value());
+
+    const nsString& value = itemInfo.value();
+
+    if (loadState != LoadState::AllOrderedItems && !value.IsVoid()) {
+      mLoadedItems.PutEntry(itemInfo.key());
+    }
+
+    mValues.Put(itemInfo.key(), value);
   }
 
-  if (itemInfos.Length() == aInitInfo.totalLength()) {
-    mLoadState = LoadState::AllOrderedItems;
-  } else {
-    mLoadState = LoadState::Partial;
+  if (loadState == LoadState::Partial) {
     mInitLength = aInitInfo.totalLength();
     mLength = mInitLength;
+  } else if (loadState == LoadState::AllOrderedKeys) {
+    mInitLength = aInitInfo.totalLength();
+  } else {
+    MOZ_ASSERT(loadState == LoadState::AllOrderedItems);
   }
 
   mExactUsage = aInitInfo.initialUsage();
   mPeakUsage = aInitInfo.peakUsage();
 
-  mExplicit = aExplicit;
+  mLoadState = aInitInfo.loadState();
 
-  if (mExplicit) {
-    mSelfRef = this;
-  } else {
-    nsCOMPtr<nsIRunnable> runnable = this;
-    nsContentUtils::RunInStableState(runnable.forget());
-  }
+  mExplicit = aExplicit;
 
 #ifdef DEBUG
   mInitialized = true;
 #endif
 
   return NS_OK;
 }
 
--- a/dom/localstorage/LSSnapshot.h
+++ b/dom/localstorage/LSSnapshot.h
@@ -5,30 +5,37 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_localstorage_LSSnapshot_h
 #define mozilla_dom_localstorage_LSSnapshot_h
 
 namespace mozilla {
 namespace dom {
 
+class LSDatabase;
+class LSNotifyInfo;
 class LSSnapshotChild;
+class LSSnapshotInitInfo;
+class LSWriteInfo;
 
 class LSSnapshot final
   : public nsIRunnable
 {
+public:
   enum class LoadState
   {
     Initial,
     Partial,
     AllOrderedKeys,
     AllUnorderedItems,
-    AllOrderedItems
+    AllOrderedItems,
+    EndGuard
   };
 
+private:
   RefPtr<LSSnapshot> mSelfRef;
 
   RefPtr<LSDatabase> mDatabase;
 
   LSSnapshotChild* mActor;
 
   nsTHashtable<nsStringHashKey> mLoadedItems;
   nsTHashtable<nsStringHashKey> mUnknownItems;
@@ -36,16 +43,17 @@ class LSSnapshot final
   nsTArray<LSWriteInfo> mWriteInfos;
 
   uint32_t mInitLength;
   uint32_t mLength;
   int64_t mExactUsage;
   int64_t mPeakUsage;
 
   LoadState mLoadState;
+
   bool mExplicit;
 
 #ifdef DEBUG
   bool mInitialized;
   bool mSentFinish;
 #endif
 
 public:
--- a/dom/localstorage/PBackgroundLSDatabase.ipdl
+++ b/dom/localstorage/PBackgroundLSDatabase.ipdl
@@ -1,30 +1,36 @@
 /* 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 protocol PBackgroundLSSnapshot;
 
+include "mozilla/dom/localstorage/SerializationHelpers.h";
+
+using mozilla::dom::LSSnapshot::LoadState
+  from "mozilla/dom/LSSnapshot.h";
+
 namespace mozilla {
 namespace dom {
 
 struct LSItemInfo
 {
   nsString key;
   nsString value;
 };
 
 struct LSSnapshotInitInfo
 {
   LSItemInfo[] itemInfos;
   uint32_t totalLength;
   int64_t initialUsage;
   int64_t peakUsage;
+  LoadState loadState;
 };
 
 sync protocol PBackgroundLSDatabase
 {
   manager PBackground;
   manages PBackgroundLSSnapshot;
 
 parent:
new file mode 100644
--- /dev/null
+++ b/dom/localstorage/SerializationHelpers.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_localstorage_SerializationHelpers_h
+#define mozilla_dom_localstorage_SerializationHelpers_h
+
+#include "ipc/IPCMessageUtils.h"
+
+#include "mozilla/dom/LSSnapshot.h"
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::dom::LSSnapshot::LoadState> :
+  public ContiguousEnumSerializer<mozilla::dom::LSSnapshot::LoadState,
+                                  mozilla::dom::LSSnapshot::LoadState::Initial,
+                                  mozilla::dom::LSSnapshot::LoadState::EndGuard>
+{ };
+
+} // namespace IPC
+
+#endif // mozilla_dom_localstorage_SerializationHelpers_h
--- a/dom/localstorage/moz.build
+++ b/dom/localstorage/moz.build
@@ -11,23 +11,25 @@ XPCSHELL_TESTS_MANIFESTS += [
 XPIDL_SOURCES += [
     'nsILocalStorageManager.idl',
 ]
 
 XPIDL_MODULE = 'dom_localstorage'
 
 EXPORTS.mozilla.dom.localstorage += [
     'ActorsParent.h',
+    'SerializationHelpers.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'LocalStorageCommon.h',
     'LocalStorageManager2.h',
     'LSObject.h',
     'LSObserver.h',
+    'LSSnapshot.h',
 ]
 
 UNIFIED_SOURCES += [
     'ActorsChild.cpp',
     'ActorsParent.cpp',
     'LocalStorageCommon.cpp',
     'LocalStorageManager2.cpp',
     'LSDatabase.cpp',