Bug 1547454 - LSNG: Create a new C++ class for local storage values; r=asuth
☠☠ backed out by df3eadfa74a8 ☠ ☠
authorJan Varga <jan.varga@gmail.com>
Mon, 29 Apr 2019 06:05:56 +0200
changeset 531466 ade08d6dc3614c3ec3a3adee85a058cf666831f8
parent 531465 b622431054b74071311a6613942d93ae278f5c72
child 531467 df97dfbb526adfe2fdf14a94dcc6bf83b5a2cab5
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth
bugs1547454
milestone68.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 1547454 - LSNG: Create a new C++ class for local storage values; r=asuth Differential Revision: https://phabricator.services.mozilla.com/D29138
dom/localstorage/ActorsChild.cpp
dom/localstorage/ActorsChild.h
dom/localstorage/ActorsParent.cpp
dom/localstorage/LSSnapshot.cpp
dom/localstorage/LSSnapshot.h
dom/localstorage/LSValue.cpp
dom/localstorage/LSValue.h
dom/localstorage/PBackgroundLSObserver.ipdl
dom/localstorage/PBackgroundLSSharedTypes.ipdlh
dom/localstorage/PBackgroundLSSnapshot.ipdl
dom/localstorage/SerializationHelpers.h
dom/localstorage/moz.build
--- a/dom/localstorage/ActorsChild.cpp
+++ b/dom/localstorage/ActorsChild.cpp
@@ -121,17 +121,17 @@ void LSObserverChild::ActorDestroy(Actor
     mObserver = nullptr;
 #endif
   }
 }
 
 mozilla::ipc::IPCResult LSObserverChild::RecvObserve(
     const PrincipalInfo& aPrincipalInfo, const uint32_t& aPrivateBrowsingId,
     const nsString& aDocumentURI, const nsString& aKey,
-    const nsString& aOldValue, const nsString& aNewValue) {
+    const LSValue& aOldValue, const LSValue& aNewValue) {
   AssertIsOnOwningThread();
 
   if (!mObserver) {
     return IPC_OK();
   }
 
   nsresult rv;
   nsCOMPtr<nsIPrincipal> principal =
--- a/dom/localstorage/ActorsChild.h
+++ b/dom/localstorage/ActorsChild.h
@@ -119,18 +119,18 @@ class LSObserverChild final : public PBa
 
   // IPDL methods are only called by IPDL.
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   mozilla::ipc::IPCResult RecvObserve(const PrincipalInfo& aPrinciplaInfo,
                                       const uint32_t& aPrivateBrowsingId,
                                       const nsString& aDocumentURI,
                                       const nsString& aKey,
-                                      const nsString& aOldValue,
-                                      const nsString& aNewValue) override;
+                                      const LSValue& aOldValue,
+                                      const LSValue& aNewValue) override;
 };
 
 /**
  * Minimal glue IPC-managed (new/delete) actor that is used by LSObject and its
  * RequestHelper to perform synchronous requests on top of an asynchronous
  * protocol.
  *
  * Takes an `LSReuestChildCallback` to be invoked when a response is received
--- a/dom/localstorage/ActorsParent.cpp
+++ b/dom/localstorage/ActorsParent.cpp
@@ -1222,20 +1222,19 @@ class WriteOptimizer final {
     AssertIsOnBackgroundThread();
     MOZ_ASSERT(&aWriteOptimizer != this);
 
     mWriteInfos.SwapElements(aWriteOptimizer.mWriteInfos);
     mTotalDelta = aWriteOptimizer.mTotalDelta;
     aWriteOptimizer.mTotalDelta = 0;
   }
 
-  void AddItem(const nsString& aKey, const nsString& aValue,
-               int64_t aDelta = 0);
-
-  void UpdateItem(const nsString& aKey, const nsString& aValue,
+  void AddItem(const nsString& aKey, const LSValue& aValue, int64_t aDelta = 0);
+
+  void UpdateItem(const nsString& aKey, const LSValue& aValue,
                   int64_t aDelta = 0);
 
   void RemoveItem(const nsString& aKey, int64_t aDelta = 0);
 
   void Clear(int64_t aDelta = 0);
 
   bool HasWrites() const {
     AssertIsOnBackgroundThread();
@@ -1266,38 +1265,38 @@ class WriteOptimizer::WriteInfo {
   virtual ~WriteInfo() = default;
 };
 
 /**
  * SetItem mutation where the key did not previously exist.
  */
 class WriteOptimizer::AddItemInfo : public WriteInfo {
   nsString mKey;
-  nsString mValue;
+  LSValue mValue;
 
  public:
-  AddItemInfo(const nsAString& aKey, const nsAString& aValue)
+  AddItemInfo(const nsAString& aKey, const LSValue& aValue)
       : mKey(aKey), mValue(aValue) {}
 
   const nsAString& GetKey() const { return mKey; }
 
-  const nsAString& GetValue() const { return mValue; }
+  const LSValue& GetValue() const { return mValue; }
 
  private:
   Type GetType() override { return AddItem; }
 
   nsresult Perform(Connection* aConnection, bool aShadowWrites) override;
 };
 
 /**
  * SetItem mutation where the key already existed.
  */
 class WriteOptimizer::UpdateItemInfo final : public AddItemInfo {
  public:
-  UpdateItemInfo(const nsAString& aKey, const nsAString& aValue)
+  UpdateItemInfo(const nsAString& aKey, const LSValue& aValue)
       : AddItemInfo(aKey, aValue) {}
 
  private:
   Type GetType() override { return UpdateItem; }
 };
 
 class WriteOptimizer::RemoveItemInfo final : public WriteInfo {
   nsString mKey;
@@ -1477,19 +1476,19 @@ class Connection final {
   // This method is used to asynchronously execute a connection datastore
   // operation on the connection thread.
   void Dispatch(ConnectionDatastoreOperationBase* aOp);
 
   // This method is used to asynchronously close the storage connection on the
   // connection thread.
   void Close(nsIRunnable* aCallback);
 
-  void AddItem(const nsString& aKey, const nsString& aValue, int64_t aDelta);
-
-  void UpdateItem(const nsString& aKey, const nsString& aValue, int64_t aDelta);
+  void AddItem(const nsString& aKey, const LSValue& aValue, int64_t aDelta);
+
+  void UpdateItem(const nsString& aKey, const LSValue& aValue, int64_t aDelta);
 
   void RemoveItem(const nsString& aKey, int64_t aDelta);
 
   void Clear(int64_t aDelta);
 
   void BeginUpdateBatch();
 
   void EndUpdateBatch();
@@ -1642,17 +1641,17 @@ class Datastore final
    * are any active databases final deltas can't be calculated and
    * `UpdateUsage()` can't be invoked.
    */
   nsTHashtable<nsPtrHashKey<Database>> mActiveDatabases;
   /**
    * Non-authoritative hashtable representation of mOrderedItems for efficient
    * lookup.
    */
-  nsDataHashtable<nsStringHashKey, nsString> mValues;
+  nsDataHashtable<nsStringHashKey, LSValue> mValues;
   /**
    * The authoritative ordered state of the Datastore; mValue also exists as an
    * unordered hashtable for efficient lookup.
    */
   nsTArray<LSItemInfo> mOrderedItems;
   nsTArray<int64_t> mPendingUsageDeltas;
   WriteOptimizer mWriteOptimizer;
   const nsCString mOrigin;
@@ -1668,17 +1667,17 @@ class Datastore final
 
  public:
   // Created by PrepareDatastoreOp.
   Datastore(const nsACString& aOrigin, uint32_t aPrivateBrowsingId,
             int64_t aUsage, int64_t aSizeOfKeys, int64_t aSizeOfItems,
             already_AddRefed<DirectoryLock>&& aDirectoryLock,
             already_AddRefed<Connection>&& aConnection,
             already_AddRefed<QuotaObject>&& aQuotaObject,
-            nsDataHashtable<nsStringHashKey, nsString>& aValues,
+            nsDataHashtable<nsStringHashKey, LSValue>& aValues,
             nsTArray<LSItemInfo>& aOrderedItems);
 
   const nsCString& Origin() const { return mOrigin; }
 
   uint32_t PrivateBrowsingId() const { return mPrivateBrowsingId; }
 
   bool IsPersistent() const {
     // Private-browsing is forbidden from touching disk, but
@@ -1717,35 +1716,35 @@ class Datastore final
                            nsTHashtable<nsStringHashKey>& aLoadedItems,
                            nsTArray<LSItemInfo>& aItemInfos,
                            uint32_t& aNextLoadIndex, uint32_t& aTotalLength,
                            int64_t& aInitialUsage, int64_t& aPeakUsage,
                            LSSnapshot::LoadState& aLoadState);
 
   const nsTArray<LSItemInfo>& GetOrderedItems() const { return mOrderedItems; }
 
-  void GetItem(const nsString& aKey, nsString& aValue) const;
+  void GetItem(const nsString& aKey, LSValue& aValue) const;
 
   void GetKeys(nsTArray<nsString>& aKeys) const;
 
   //////////////////////////////////////////////////////////////////////////////
   // Mutation Methods
   //
   // These are only called during Snapshot::RecvCheckpoint
 
   /**
    * Used by Snapshot::RecvCheckpoint to set a key/value pair as part of a an
    * explicit batch.
    */
   void SetItem(Database* aDatabase, const nsString& aDocumentURI,
-               const nsString& aKey, const nsString& aOldValue,
-               const nsString& aValue);
+               const nsString& aKey, const LSValue& aOldValue,
+               const LSValue& aValue);
 
   void RemoveItem(Database* aDatabase, const nsString& aDocumentURI,
-                  const nsString& aKey, const nsString& aOldValue);
+                  const nsString& aKey, const LSValue& aOldValue);
 
   void Clear(Database* aDatabase, const nsString& aDocumentURI);
 
   void PrivateBrowsingClear();
 
   void BeginUpdateBatch(int64_t aSnapshotInitialUsage);
 
   int64_t EndUpdateBatch(int64_t aSnapshotPeakUsage);
@@ -1762,23 +1761,23 @@ class Datastore final
 
   void MaybeClose();
 
   void ConnectionClosedCallback();
 
   void CleanupMetadata();
 
   void NotifySnapshots(Database* aDatabase, const nsAString& aKey,
-                       const nsAString& aOldValue, bool aAffectsOrder);
+                       const LSValue& aOldValue, bool aAffectsOrder);
 
   void MarkSnapshotsDirty();
 
   void NotifyObservers(Database* aDatabase, const nsString& aDocumentURI,
-                       const nsString& aKey, const nsString& aOldValue,
-                       const nsString& aNewValue);
+                       const nsString& aKey, const LSValue& aOldValue,
+                       const LSValue& aNewValue);
 };
 
 class PreparedDatastore {
   RefPtr<Datastore> mDatastore;
   nsCOMPtr<nsITimer> mTimer;
   const Maybe<ContentParentId> mContentParentId;
   // Strings share buffers if possible, so it's not a problem to duplicate the
   // origin here.
@@ -1991,17 +1990,17 @@ class Snapshot final : public PBackgroun
    * notifications that are not yet known to the child LSSnapshot.
    *
    * The naive way to snapshot the state of mDatastore would be to duplicate its
    * internal mValues at the time of our creation, but that is wasteful if few
    * changes are made to the Datastore's state.  So we only track values that
    * are changed/evicted from the Datastore as they happen, as reported to us by
    * SaveItem notifications.
    */
-  nsDataHashtable<nsStringHashKey, nsString> mValues;
+  nsDataHashtable<nsStringHashKey, LSValue> mValues;
   /**
    * Latched state of mDatastore's keys during a SaveItem notification with
    * aAffectsOrder=true.  The ordered keys needed to be saved off so that a
    * consistent ordering could be presented to the child LSSnapshot when it asks
    * for them via RecvLoadKeys.
    */
   nsTArray<nsString> mKeys;
   nsString mDocumentURI;
@@ -2085,17 +2084,17 @@ class Snapshot final : public PBackgroun
     }
   }
 
   /**
    * Called via NotifySnapshots by Datastore whenever it is updating its
    * internal state so that snapshots can save off the state of a value at the
    * time of their creation.
    */
-  void SaveItem(const nsAString& aKey, const nsAString& aOldValue,
+  void SaveItem(const nsAString& aKey, const LSValue& aOldValue,
                 bool aAffectsOrder);
 
   void MarkDirty();
 
   NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Snapshot)
 
  private:
   // Reference counted.
@@ -2111,17 +2110,17 @@ class Snapshot final : public PBackgroun
   mozilla::ipc::IPCResult RecvCheckpoint(
       nsTArray<LSWriteInfo>&& aWriteInfos) override;
 
   mozilla::ipc::IPCResult RecvFinish() override;
 
   mozilla::ipc::IPCResult RecvLoaded() override;
 
   mozilla::ipc::IPCResult RecvLoadValueAndMoreItems(
-      const nsString& aKey, nsString* aValue,
+      const nsString& aKey, LSValue* aValue,
       nsTArray<LSItemInfo>* aItemInfos) override;
 
   mozilla::ipc::IPCResult RecvLoadKeys(nsTArray<nsString>* aKeys) override;
 
   mozilla::ipc::IPCResult RecvIncreasePeakUsage(const int64_t& aRequestedSize,
                                                 const int64_t& aMinSize,
                                                 int64_t* aSize) override;
 
@@ -2134,18 +2133,18 @@ class Observer final : public PBackgroun
 
  public:
   // Created in AllocPBackgroundLSObserverParent.
   explicit Observer(const nsACString& aOrigin);
 
   const nsCString& Origin() const { return mOrigin; }
 
   void Observe(Database* aDatabase, const nsString& aDocumentURI,
-               const nsString& aKey, const nsString& aOldValue,
-               const nsString& aNewValue);
+               const nsString& aKey, const LSValue& aOldValue,
+               const LSValue& aNewValue);
 
   NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Observer)
 
  private:
   // Reference counted.
   ~Observer();
 
   // IPDL methods are only called by IPDL.
@@ -2291,17 +2290,17 @@ class PrepareDatastoreOp
   nsCOMPtr<nsIEventTarget> mMainEventTarget;
   RefPtr<PrepareDatastoreOp> mDelayedOp;
   RefPtr<DirectoryLock> mPendingDirectoryLock;
   RefPtr<DirectoryLock> mDirectoryLock;
   RefPtr<Connection> mConnection;
   RefPtr<Datastore> mDatastore;
   nsAutoPtr<ArchivedOriginScope> mArchivedOriginScope;
   LoadDataOp* mLoadDataOp;
-  nsDataHashtable<nsStringHashKey, nsString> mValues;
+  nsDataHashtable<nsStringHashKey, LSValue> mValues;
   nsTArray<LSItemInfo> mOrderedItems;
   const LSRequestCommonParams mParams;
   Maybe<ContentParentId> mContentParentId;
   nsCString mSuffix;
   nsCString mGroup;
   nsCString mMainThreadOrigin;
   nsCString mOrigin;
   nsString mDirectoryPath;
@@ -3573,34 +3572,34 @@ already_AddRefed<mozilla::dom::quota::Cl
 }
 
 }  // namespace localstorage
 
 /*******************************************************************************
  * WriteOptimizer
  ******************************************************************************/
 
-void WriteOptimizer::AddItem(const nsString& aKey, const nsString& aValue,
+void WriteOptimizer::AddItem(const nsString& aKey, const LSValue& aValue,
                              int64_t aDelta) {
   AssertIsOnBackgroundThread();
 
   WriteInfo* existingWriteInfo;
   nsAutoPtr<WriteInfo> newWriteInfo;
   if (mWriteInfos.Get(aKey, &existingWriteInfo) &&
       existingWriteInfo->GetType() == WriteInfo::RemoveItem) {
     newWriteInfo = new UpdateItemInfo(aKey, aValue);
   } else {
     newWriteInfo = new AddItemInfo(aKey, aValue);
   }
   mWriteInfos.Put(aKey, newWriteInfo.forget());
 
   mTotalDelta += aDelta;
 }
 
-void WriteOptimizer::UpdateItem(const nsString& aKey, const nsString& aValue,
+void WriteOptimizer::UpdateItem(const nsString& aKey, const LSValue& aValue,
                                 int64_t aDelta) {
   AssertIsOnBackgroundThread();
 
   WriteInfo* existingWriteInfo;
   nsAutoPtr<WriteInfo> newWriteInfo;
   if (mWriteInfos.Get(aKey, &existingWriteInfo) &&
       existingWriteInfo->GetType() == WriteInfo::AddItem) {
     newWriteInfo = new AddItemInfo(aKey, aValue);
@@ -4073,25 +4072,25 @@ void Connection::Close(nsIRunnable* aCal
     mFlushTimer = nullptr;
   }
 
   RefPtr<CloseOp> op = new CloseOp(this, aCallback);
 
   Dispatch(op);
 }
 
-void Connection::AddItem(const nsString& aKey, const nsString& aValue,
+void Connection::AddItem(const nsString& aKey, const LSValue& aValue,
                          int64_t aDelta) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mInUpdateBatch);
 
   mWriteOptimizer.AddItem(aKey, aValue, aDelta);
 }
 
-void Connection::UpdateItem(const nsString& aKey, const nsString& aValue,
+void Connection::UpdateItem(const nsString& aKey, const LSValue& aValue,
                             int64_t aDelta) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mInUpdateBatch);
 
   mWriteOptimizer.UpdateItem(aKey, aValue, aDelta);
 }
 
 void Connection::RemoveItem(const nsString& aKey, int64_t aDelta) {
@@ -4469,17 +4468,17 @@ void ConnectionThread::Shutdown() {
  * Datastore
  ******************************************************************************/
 
 Datastore::Datastore(const nsACString& aOrigin, uint32_t aPrivateBrowsingId,
                      int64_t aUsage, int64_t aSizeOfKeys, int64_t aSizeOfItems,
                      already_AddRefed<DirectoryLock>&& aDirectoryLock,
                      already_AddRefed<Connection>&& aConnection,
                      already_AddRefed<QuotaObject>&& aQuotaObject,
-                     nsDataHashtable<nsStringHashKey, nsString>& aValues,
+                     nsDataHashtable<nsStringHashKey, LSValue>& aValues,
                      nsTArray<LSItemInfo>& aOrderedItems)
     : mDirectoryLock(std::move(aDirectoryLock)),
       mConnection(std::move(aConnection)),
       mQuotaObject(std::move(aQuotaObject)),
       mOrigin(aOrigin),
       mPrivateBrowsingId(aPrivateBrowsingId),
       mUsage(aUsage),
       mUpdateBatchUsage(-1),
@@ -4689,17 +4688,17 @@ void Datastore::GetSnapshotInitInfo(cons
       return LSSnapshot::LoadState::AllOrderedKeys;
     }
 
     return LSSnapshot::LoadState::Partial;
   };
 
   // Value for given aKey if aKey is not void (can be void too if value doesn't
   // exist for given aKey).
-  nsString value;
+  LSValue value;
   // If aKey and value are not void, checkKey will be set to true. Once we find
   // an item for given aKey in one of the loops below, checkKey is set to false
   // to prevent additional comparison of strings (string implementation compares
   // string lengths first to avoid char by char comparison if possible).
   bool checkKey = false;
 
   // Avoid additional hash lookup if all ordered items fit into initial prefill
   // already.
@@ -4758,17 +4757,17 @@ void Datastore::GetSnapshotInitInfo(cons
 
       int64_t size = mSizeOfKeys;
       bool setVoidValue = false;
       bool doneSendingValues = false;
       for (uint32_t index = 0; index < mOrderedItems.Length(); index++) {
         const LSItemInfo& item = mOrderedItems[index];
 
         const nsString& key = item.key();
-        const nsString& value = item.value();
+        const LSValue& value = item.value();
 
         if (checkKey && key == aKey) {
           checkKey = false;
           setVoidValue = false;
         } else if (!setVoidValue) {
           if (doneSendingValues) {
             setVoidValue = true;
           } else {
@@ -4802,17 +4801,17 @@ void Datastore::GetSnapshotInitInfo(cons
     }
 
     case LSSnapshot::LoadState::Partial: {
       int64_t size = 0;
       for (uint32_t index = 0; index < mOrderedItems.Length(); index++) {
         const LSItemInfo& item = mOrderedItems[index];
 
         const nsString& key = item.key();
-        const nsString& value = item.value();
+        const LSValue& value = item.value();
 
         if (checkKey && key == aKey) {
           checkKey = false;
         } else {
           size += static_cast<int64_t>(key.Length()) +
                   static_cast<int64_t>(value.Length());
 
           if (size > gSnapshotPrefill) {
@@ -4854,17 +4853,17 @@ void Datastore::GetSnapshotInitInfo(cons
   aTotalLength = mValues.Count();
 
   aInitialUsage = mUsage;
   aPeakUsage = aInitialUsage;
 
   aLoadState = loadState;
 }
 
-void Datastore::GetItem(const nsString& aKey, nsString& aValue) const {
+void Datastore::GetItem(const nsString& aKey, LSValue& aValue) const {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(!mClosed);
 
   if (!mValues.Get(aKey, &aValue)) {
     aValue.SetIsVoid(true);
   }
 }
 
@@ -4873,27 +4872,27 @@ void Datastore::GetKeys(nsTArray<nsStrin
   MOZ_ASSERT(!mClosed);
 
   for (auto item : mOrderedItems) {
     aKeys.AppendElement(item.key());
   }
 }
 
 void Datastore::SetItem(Database* aDatabase, const nsString& aDocumentURI,
-                        const nsString& aKey, const nsString& aOldValue,
-                        const nsString& aValue) {
+                        const nsString& aKey, const LSValue& aOldValue,
+                        const LSValue& aValue) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aDatabase);
   MOZ_ASSERT(!mClosed);
   MOZ_ASSERT(mInUpdateBatch);
 
-  nsString oldValue;
+  LSValue oldValue;
   GetItem(aKey, oldValue);
 
-  if (oldValue != aValue || oldValue.IsVoid() != aValue.IsVoid()) {
+  if (oldValue != aValue) {
     bool isNewItem = oldValue.IsVoid();
 
     NotifySnapshots(aDatabase, aKey, oldValue, /* affectsOrder */ isNewItem);
 
     mValues.Put(aKey, aValue);
 
     int64_t sizeOfItem;
 
@@ -4926,23 +4925,23 @@ void Datastore::SetItem(Database* aDatab
       }
     }
   }
 
   NotifyObservers(aDatabase, aDocumentURI, aKey, aOldValue, aValue);
 }
 
 void Datastore::RemoveItem(Database* aDatabase, const nsString& aDocumentURI,
-                           const nsString& aKey, const nsString& aOldValue) {
+                           const nsString& aKey, const LSValue& aOldValue) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aDatabase);
   MOZ_ASSERT(!mClosed);
   MOZ_ASSERT(mInUpdateBatch);
 
-  nsString oldValue;
+  LSValue oldValue;
   GetItem(aKey, oldValue);
 
   if (!oldValue.IsVoid()) {
     NotifySnapshots(aDatabase, aKey, oldValue, /* aAffectsOrder */ true);
 
     mValues.Remove(aKey);
 
     mWriteOptimizer.RemoveItem(aKey);
@@ -4955,30 +4954,30 @@ void Datastore::RemoveItem(Database* aDa
     mSizeOfKeys -= sizeOfKey;
     mSizeOfItems -= sizeOfItem;
 
     if (IsPersistent()) {
       mConnection->RemoveItem(aKey, -sizeOfItem);
     }
   }
 
-  NotifyObservers(aDatabase, aDocumentURI, aKey, aOldValue, VoidString());
+  NotifyObservers(aDatabase, aDocumentURI, aKey, aOldValue, VoidLSValue());
 }
 
 void Datastore::Clear(Database* aDatabase, const nsString& aDocumentURI) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aDatabase);
   MOZ_ASSERT(!mClosed);
   MOZ_ASSERT(mInUpdateBatch);
 
   if (mValues.Count()) {
     int64_t sizeOfItems = 0;
     for (auto iter = mValues.ConstIter(); !iter.Done(); iter.Next()) {
       const nsAString& key = iter.Key();
-      const nsAString& value = iter.Data();
+      const LSValue& value = iter.Data();
 
       sizeOfItems += (static_cast<int64_t>(key.Length()) +
                       static_cast<int64_t>(value.Length()));
 
       NotifySnapshots(aDatabase, key, value, /* aAffectsOrder */ true);
     }
 
     mValues.Clear();
@@ -4990,18 +4989,18 @@ void Datastore::Clear(Database* aDatabas
     mSizeOfKeys = 0;
     mSizeOfItems = 0;
 
     if (IsPersistent()) {
       mConnection->Clear(-sizeOfItems);
     }
   }
 
-  NotifyObservers(aDatabase, aDocumentURI, VoidString(), VoidString(),
-                  VoidString());
+  NotifyObservers(aDatabase, aDocumentURI, VoidString(), VoidLSValue(),
+                  VoidLSValue());
 }
 
 void Datastore::PrivateBrowsingClear() {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(mPrivateBrowsingId);
   MOZ_ASSERT(!mClosed);
   MOZ_ASSERT(!mInUpdateBatch);
 
@@ -5162,18 +5161,17 @@ void Datastore::CleanupMetadata() {
   gDatastores->Remove(mOrigin);
 
   if (!gDatastores->Count()) {
     gDatastores = nullptr;
   }
 }
 
 void Datastore::NotifySnapshots(Database* aDatabase, const nsAString& aKey,
-                                const nsAString& aOldValue,
-                                bool aAffectsOrder) {
+                                const LSValue& aOldValue, bool aAffectsOrder) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aDatabase);
 
   for (auto iter = mDatabases.ConstIter(); !iter.Done(); iter.Next()) {
     Database* database = iter.Get()->GetKey();
     if (database == aDatabase) {
       continue;
     }
@@ -5195,18 +5193,18 @@ void Datastore::MarkSnapshotsDirty() {
     if (snapshot) {
       snapshot->MarkDirty();
     }
   }
 }
 
 void Datastore::NotifyObservers(Database* aDatabase,
                                 const nsString& aDocumentURI,
-                                const nsString& aKey, const nsString& aOldValue,
-                                const nsString& aNewValue) {
+                                const nsString& aKey, const LSValue& aOldValue,
+                                const LSValue& aNewValue) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aDatabase);
 
   if (!gObservers) {
     return;
   }
 
   nsTArray<Observer*>* array;
@@ -5510,28 +5508,28 @@ Snapshot::Snapshot(Database* aDatabase, 
   MOZ_ASSERT(aDatabase);
 }
 
 Snapshot::~Snapshot() {
   MOZ_ASSERT(mActorDestroyed);
   MOZ_ASSERT(mFinishReceived);
 }
 
-void Snapshot::SaveItem(const nsAString& aKey, const nsAString& aOldValue,
+void Snapshot::SaveItem(const nsAString& aKey, const LSValue& aOldValue,
                         bool aAffectsOrder) {
   AssertIsOnBackgroundThread();
 
   MarkDirty();
 
   if (mLoadedAllItems) {
     return;
   }
 
   if (!mLoadedItems.GetEntry(aKey) && !mUnknownItems.GetEntry(aKey)) {
-    nsString oldValue(aOldValue);
+    LSValue oldValue(aOldValue);
     mValues.LookupForAdd(aKey).OrInsert([oldValue]() { return oldValue; });
   }
 
   if (aAffectsOrder && !mSavedKeys) {
     mDatastore->GetKeys(mKeys);
     mSavedKeys = true;
   }
 }
@@ -5676,17 +5674,17 @@ mozilla::ipc::IPCResult Snapshot::RecvLo
   mKeys.Clear();
   mLoadedAllItems = true;
   mLoadKeysReceived = true;
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult Snapshot::RecvLoadValueAndMoreItems(
-    const nsString& aKey, nsString* aValue, nsTArray<LSItemInfo>* aItemInfos) {
+    const nsString& aKey, LSValue* aValue, nsTArray<LSItemInfo>* aItemInfos) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aValue);
   MOZ_ASSERT(aItemInfos);
   MOZ_ASSERT(mDatastore);
 
   if (NS_WARN_IF(mFinishReceived)) {
     ASSERT_UNLESS_FUZZING();
     return IPC_FAIL_NO_REASON(this);
@@ -5765,17 +5763,17 @@ mozilla::ipc::IPCResult Snapshot::RecvLo
       if (countBeforePut != mLoadedItems.Count()) {
         // Check mValues first since that contains values as they existed when
         // our snapshot was created, but have since been changed/removed in the
         // datastore. If it's not there, then the datastore has the
         // still-current value. However, if the datastore's key ordering has
         // changed, we need to do a hash lookup rather than being able to do an
         // optimized direct access to the index.
 
-        nsString value;
+        LSValue value;
         auto valueEntry = mValues.Lookup(key);
         if (valueEntry) {
           value = valueEntry.Data();
         } else if (mSavedKeys) {
           mDatastore->GetItem(nsString(key), value);
         } else {
           value = orderedItems[mNextLoadIndex].value();
         }
@@ -5898,18 +5896,18 @@ mozilla::ipc::IPCResult Snapshot::RecvPi
 Observer::Observer(const nsACString& aOrigin)
     : mOrigin(aOrigin), mActorDestroyed(false) {
   AssertIsOnBackgroundThread();
 }
 
 Observer::~Observer() { MOZ_ASSERT(mActorDestroyed); }
 
 void Observer::Observe(Database* aDatabase, const nsString& aDocumentURI,
-                       const nsString& aKey, const nsString& aOldValue,
-                       const nsString& aNewValue) {
+                       const nsString& aKey, const LSValue& aOldValue,
+                       const LSValue& aNewValue) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aDatabase);
 
   Unused << SendObserve(aDatabase->GetPrincipalInfo(),
                         aDatabase->PrivateBrowsingId(), aDocumentURI, aKey,
                         aOldValue, aNewValue);
 }
 
@@ -7297,22 +7295,24 @@ nsresult PrepareDatastoreOp::LoadDataOp:
   bool hasResult;
   while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&hasResult)) && hasResult) {
     nsString key;
     rv = stmt->GetString(0, key);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    nsString value;
-    rv = stmt->GetString(1, value);
+    nsString buffer;
+    rv = stmt->GetString(1, buffer);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
+    LSValue value(buffer);
+
     mPrepareDatastoreOp->mValues.Put(key, value);
     auto item = mPrepareDatastoreOp->mOrderedItems.AppendElement();
     item->key() = key;
     item->value() = value;
     mPrepareDatastoreOp->mSizeOfKeys += key.Length();
     mPrepareDatastoreOp->mSizeOfItems += key.Length() + value.Length();
 #ifdef DEBUG
     mPrepareDatastoreOp->mDEBUGUsage += key.Length() + value.Length();
--- a/dom/localstorage/LSSnapshot.cpp
+++ b/dom/localstorage/LSSnapshot.cpp
@@ -71,17 +71,17 @@ nsresult LSSnapshot::Init(const nsAStrin
   mSelfRef = this;
 
   LoadState loadState = aInitInfo.loadState();
 
   const nsTArray<LSItemInfo>& itemInfos = aInitInfo.itemInfos();
   for (uint32_t i = 0; i < itemInfos.Length(); i++) {
     const LSItemInfo& itemInfo = itemInfos[i];
 
-    const nsString& value = itemInfo.value();
+    const LSValue& value = itemInfo.value();
 
     if (loadState != LoadState::AllOrderedItems && !value.IsVoid()) {
       mLoadedItems.PutEntry(itemInfo.key());
     }
 
     mValues.Put(itemInfo.key(), value);
   }
 
@@ -238,18 +238,18 @@ nsresult LSSnapshot::SetItem(const nsASt
     }
 
     if (oldValue.IsVoid() && mLoadState == LoadState::Partial) {
       mLength++;
     }
 
     LSSetItemInfo setItemInfo;
     setItemInfo.key() = aKey;
-    setItemInfo.oldValue() = oldValue;
-    setItemInfo.value() = aValue;
+    setItemInfo.oldValue() = LSValue(oldValue);
+    setItemInfo.value() = LSValue(aValue);
 
     mWriteInfos.AppendElement(std::move(setItemInfo));
   }
 
   aNotifyInfo.changed() = changed;
   aNotifyInfo.oldValue() = oldValue;
 
   return NS_OK;
@@ -284,17 +284,17 @@ nsresult LSSnapshot::RemoveItem(const ns
     MOZ_ASSERT(NS_SUCCEEDED(rv));
 
     if (mLoadState == LoadState::Partial) {
       mLength--;
     }
 
     LSRemoveItemInfo removeItemInfo;
     removeItemInfo.key() = aKey;
-    removeItemInfo.oldValue() = oldValue;
+    removeItemInfo.oldValue() = LSValue(oldValue);
 
     mWriteInfos.AppendElement(std::move(removeItemInfo));
   }
 
   aNotifyInfo.changed() = changed;
   aNotifyInfo.oldValue() = oldValue;
 
   return NS_OK;
@@ -431,22 +431,25 @@ nsresult LSSnapshot::GetItemInternal(con
 
   switch (mLoadState) {
     case LoadState::Partial: {
       if (mValues.Get(aKey, &result)) {
         MOZ_ASSERT(!result.IsVoid());
       } else if (mLoadedItems.GetEntry(aKey) || mUnknownItems.GetEntry(aKey)) {
         result.SetIsVoid(true);
       } else {
+        LSValue value;
         nsTArray<LSItemInfo> itemInfos;
         if (NS_WARN_IF(!mActor->SendLoadValueAndMoreItems(
-                nsString(aKey), &result, &itemInfos))) {
+                nsString(aKey), &value, &itemInfos))) {
           return NS_ERROR_FAILURE;
         }
 
+        result = value;
+
         if (result.IsVoid()) {
           mUnknownItems.PutEntry(aKey);
         } else {
           mLoadedItems.PutEntry(aKey);
           mValues.Put(aKey, result);
 
           // mLoadedItems.Count()==mInitLength is checked below.
         }
@@ -476,22 +479,25 @@ nsresult LSSnapshot::GetItemInternal(con
       }
 
       break;
     }
 
     case LoadState::AllOrderedKeys: {
       if (mValues.Get(aKey, &result)) {
         if (result.IsVoid()) {
+          LSValue value;
           nsTArray<LSItemInfo> itemInfos;
           if (NS_WARN_IF(!mActor->SendLoadValueAndMoreItems(
-                  nsString(aKey), &result, &itemInfos))) {
+                  nsString(aKey), &value, &itemInfos))) {
             return NS_ERROR_FAILURE;
           }
 
+          result = value;
+
           MOZ_ASSERT(!result.IsVoid());
 
           mLoadedItems.PutEntry(aKey);
           mValues.Put(aKey, result);
 
           // mLoadedItems.Count()==mInitLength is checked below.
 
           for (uint32_t i = 0; i < itemInfos.Length(); i++) {
--- a/dom/localstorage/LSSnapshot.h
+++ b/dom/localstorage/LSSnapshot.h
@@ -2,16 +2,18 @@
 /* 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_LSSnapshot_h
 #define mozilla_dom_localstorage_LSSnapshot_h
 
+#include "LSValue.h"
+
 namespace mozilla {
 namespace dom {
 
 class LSDatabase;
 class LSNotifyInfo;
 class LSSnapshotChild;
 class LSSnapshotInitInfo;
 class LSWriteInfo;
new file mode 100644
--- /dev/null
+++ b/dom/localstorage/LSValue.cpp
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "LSValue.h"
+
+namespace mozilla {
+namespace dom {
+
+const LSValue& VoidLSValue() {
+  static const LSValue sVoidLSValue(VoidString());
+
+  return sVoidLSValue;
+}
+
+}  // namespace dom
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/localstorage/LSValue.h
@@ -0,0 +1,53 @@
+/* -*- 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_LSValue_h
+#define mozilla_dom_localstorage_LSValue_h
+
+namespace mozilla {
+namespace dom {
+
+/**
+ * Represents a LocalStorage value. From content's perspective, values (if
+ * present) are always DOMStrings. This is also true from a quota-tracking
+ * perspective. However, for memory and disk efficiency it's preferable to store
+ * the value in alternate representations. The LSValue type exists to support
+ * these alternate representations in future.
+ */
+class LSValue final {
+  friend struct IPC::ParamTraits<LSValue>;
+
+  nsString mBuffer;
+
+ public:
+  LSValue() {}
+
+  explicit LSValue(const nsAString& aBuffer) : mBuffer(aBuffer) {}
+
+  bool IsVoid() const { return mBuffer.IsVoid(); }
+
+  void SetIsVoid(bool aVal) { mBuffer.SetIsVoid(aVal); }
+
+  uint32_t Length() const { return mBuffer.Length(); }
+
+  bool Equals(const LSValue& aOther) const {
+    return mBuffer == aOther.mBuffer &&
+           mBuffer.IsVoid() == aOther.mBuffer.IsVoid();
+  }
+
+  bool operator==(const LSValue& aOther) const { return Equals(aOther); }
+
+  bool operator!=(const LSValue& aOther) const { return !Equals(aOther); }
+
+  operator const nsString&() const { return mBuffer; }
+};
+
+const LSValue& VoidLSValue();
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif  // mozilla_dom_localstorage_LSValue_h
--- a/dom/localstorage/PBackgroundLSObserver.ipdl
+++ b/dom/localstorage/PBackgroundLSObserver.ipdl
@@ -1,16 +1,21 @@
 /* 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;
 
+include "mozilla/dom/localstorage/SerializationHelpers.h";
+
+using mozilla::dom::LSValue
+  from "mozilla/dom/LSValue.h";
+
 namespace mozilla {
 namespace dom {
 
 /**
  * The observer protocol sends "storage" event notifications for changes to
  * LocalStorage that take place in other processes as their Snapshots are
  * Checkpointed to the canonical Datastore in the parent process.  Same-process
  * notifications are generated as mutations happen.
@@ -44,14 +49,14 @@ child:
    * Checkpointed, applying their mutations.  The child actor currently directly
    * shunts these to Storage::NotifyChange to generate "storage" events for
    * immediate dispatch.
    */
   async Observe(PrincipalInfo principalInfo,
                 uint32_t privateBrowsingId,
                 nsString documentURI,
                 nsString key,
-                nsString oldValue,
-                nsString newValue);
+                LSValue oldValue,
+                LSValue newValue);
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/localstorage/PBackgroundLSSharedTypes.ipdlh
+++ b/dom/localstorage/PBackgroundLSSharedTypes.ipdlh
@@ -1,15 +1,20 @@
 /* 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 PBackgroundSharedTypes;
 include ProtocolTypes;
 
+include "mozilla/dom/localstorage/SerializationHelpers.h";
+
+using mozilla::dom::LSValue
+  from "mozilla/dom/LSValue.h";
+
 namespace mozilla {
 namespace dom {
 
 struct LSRequestCommonParams
 {
   PrincipalInfo principalInfo;
   nsCString originKey;
 };
@@ -52,13 +57,13 @@ union LSSimpleRequestParams
  * LocalStorage key/value pair wire representations.  `value` may be void in
  * cases where there is a value but it is not being sent for memory/bandwidth
  * conservation purposes.  (It's not possible to have a null/undefined `value`
  * as Storage is defined explicitly as a String store.)
  */
 struct LSItemInfo
 {
   nsString key;
-  nsString value;
+  LSValue value;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/localstorage/PBackgroundLSSnapshot.ipdl
+++ b/dom/localstorage/PBackgroundLSSnapshot.ipdl
@@ -2,30 +2,35 @@
  * 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 PBackgroundLSDatabase;
 
 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;
-  nsString oldValue;
-  nsString value;
+  LSValue oldValue;
+  LSValue value;
 };
 
 struct LSRemoveItemInfo
 {
   nsString key;
-  nsString oldValue;
+  LSValue oldValue;
 };
 
 struct LSClearInfo
 {
 };
 
 /**
  * Union of LocalStorage mutation types.
@@ -56,17 +61,17 @@ parent:
    * the need to use this synchronous message again.
    *
    * This needs to be synchronous because LocalStorage's semantics are
    * synchronous.  Note that the Snapshot in the PBackground parent already
    * has the answers to this request immediately available without needing to
    * consult any other threads or perform any I/O.
    */
   sync LoadValueAndMoreItems(nsString key)
-    returns (nsString value, LSItemInfo[] itemInfos);
+    returns (LSValue value, LSItemInfo[] itemInfos);
 
   /**
    * Invoked on demand to load all keys in in their canonical order if they
    * didn't fit into the initial snapshot prefill.
    *
    * This needs to be synchronous because LocalStorage's semantics are
    * synchronous.  Note that the Snapshot in the PBackground parent already
    * has the answers to this request immediately available without needing to
--- a/dom/localstorage/SerializationHelpers.h
+++ b/dom/localstorage/SerializationHelpers.h
@@ -5,21 +5,40 @@
  * 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"
+#include "mozilla/dom/LSValue.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> {};
 
+template <>
+struct ParamTraits<mozilla::dom::LSValue> {
+  typedef mozilla::dom::LSValue paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam) {
+    WriteParam(aMsg, aParam.mBuffer);
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter,
+                   paramType* aResult) {
+    return ReadParam(aMsg, aIter, &aResult->mBuffer);
+  }
+
+  static void Log(const paramType& aParam, std::wstring* aLog) {
+    LogParam(aParam.mBuffer, aLog);
+  }
+};
+
 }  // namespace IPC
 
 #endif  // mozilla_dom_localstorage_SerializationHelpers_h
--- a/dom/localstorage/moz.build
+++ b/dom/localstorage/moz.build
@@ -29,27 +29,29 @@ EXPORTS.mozilla.dom.localstorage += [
 ]
 
 EXPORTS.mozilla.dom += [
     'LocalStorageCommon.h',
     'LocalStorageManager2.h',
     'LSObject.h',
     'LSObserver.h',
     'LSSnapshot.h',
+    'LSValue.h',
 ]
 
 UNIFIED_SOURCES += [
     'ActorsChild.cpp',
     'ActorsParent.cpp',
     'LocalStorageCommon.cpp',
     'LocalStorageManager2.cpp',
     'LSDatabase.cpp',
     'LSObject.cpp',
     'LSObserver.cpp',
     'LSSnapshot.cpp',
+    'LSValue.cpp',
     'ReportInternalError.cpp',
 ]
 
 IPDL_SOURCES += [
     'PBackgroundLSDatabase.ipdl',
     'PBackgroundLSObserver.ipdl',
     'PBackgroundLSRequest.ipdl',
     'PBackgroundLSSharedTypes.ipdlh',