Merge inbound to mozilla-central. a=merge
authorOana Pop Rus <opoprus@mozilla.com>
Tue, 07 May 2019 00:45:24 +0300
changeset 531559 3c70f36ad62c9c714db3199fc00e60800ee82bde
parent 531544 e9f5f01d8b8ef297493a94e84186de210a4afccd (current diff)
parent 531558 6f45208b606582d016b044d48f512bc13ad9a5d5 (diff)
child 531611 16545e6e556eb031b325e6012cf27419f5c56091
child 531653 a52b0d5b0f9403317c9fcc3a4bd19e7c6142152b
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)
reviewersmerge
milestone68.0a1
first release with
nightly linux32
3c70f36ad62c / 68.0a1 / 20190506214617 / files
nightly linux64
3c70f36ad62c / 68.0a1 / 20190506214617 / files
nightly mac
3c70f36ad62c / 68.0a1 / 20190506214617 / files
nightly win32
3c70f36ad62c / 68.0a1 / 20190506214617 / files
nightly win64
3c70f36ad62c / 68.0a1 / 20190506214617 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
security/nss/coreconf/check_cc_clang.py
--- a/devtools/client/inspector/markup/test/browser_markup_display_node_01.js
+++ b/devtools/client/inspector/markup/test/browser_markup_display_node_01.js
@@ -8,17 +8,17 @@
 
 const TEST_URI = `
   <style type="text/css">
     #grid {
       display: grid;
     }
     #subgrid {
       display: grid;
-      grid: subgrid;
+      grid: subgrid / subgrid;
     }
     #flex {
       display: flex;
     }
     #block {
       display: block;
     }
   </style>
--- a/devtools/client/inspector/markup/test/doc_markup_subgrid.html
+++ b/devtools/client/inspector/markup/test/doc_markup_subgrid.html
@@ -18,17 +18,17 @@
 
     header, footer {
       grid-column: span 3;
     }
 
     main {
       grid-column: span 3;
       display: grid;
-      grid: subgrid;
+      grid: subgrid / subgrid;
     }
 
     .aside1 {
       grid-column: 1;
     }
 
     .aside2 {
       grid-column: 3;
new file mode 100644
--- /dev/null
+++ b/dom/base/SerializedStackHolder.cpp
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SerializedStackHolder.h"
+
+#include "js/SavedFrameAPI.h"
+#include "mozilla/dom/WorkerPrivate.h"
+
+namespace mozilla {
+namespace dom {
+
+SerializedStackHolder::SerializedStackHolder()
+  : mHolder(StructuredCloneHolder::CloningSupported,
+            StructuredCloneHolder::TransferringNotSupported,
+            StructuredCloneHolder::StructuredCloneScope::SameProcessDifferentThread) {}
+
+void SerializedStackHolder::WriteStack(JSContext* aCx,
+                                       JS::HandleObject aStack) {
+  JS::RootedValue stackValue(aCx, JS::ObjectValue(*aStack));
+  mHolder.Write(aCx, stackValue, IgnoreErrors());
+
+  // StructuredCloneHolder::Write can leave a pending exception on the context.
+  JS_ClearPendingException(aCx);
+}
+
+void SerializedStackHolder::SerializeMainThreadStack(JSContext* aCx,
+                                                     JS::HandleObject aStack) {
+  MOZ_ASSERT(NS_IsMainThread());
+  WriteStack(aCx, aStack);
+}
+
+void SerializedStackHolder::SerializeWorkerStack(JSContext* aCx,
+                                                 WorkerPrivate* aWorkerPrivate,
+                                                 JS::HandleObject aStack) {
+  MOZ_ASSERT(aWorkerPrivate->IsOnCurrentThread());
+
+  RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
+      aWorkerPrivate, "WorkerErrorReport");
+  if (workerRef) {
+    mWorkerRef = new ThreadSafeWorkerRef(workerRef);
+  } else {
+    // Don't write the stack if we can't create a ref to the worker.
+    return;
+  }
+
+  WriteStack(aCx, aStack);
+}
+
+void SerializedStackHolder::SerializeCurrentStack(JSContext* aCx) {
+  JS::RootedObject stack(aCx);
+  if (JS::CurrentGlobalOrNull(aCx) && !JS::CaptureCurrentStack(aCx, &stack)) {
+    JS_ClearPendingException(aCx);
+    return;
+  }
+
+  if (stack) {
+    if (NS_IsMainThread()) {
+      SerializeMainThreadStack(aCx, stack);
+    } else {
+      WorkerPrivate* currentWorker = GetCurrentThreadWorkerPrivate();
+      SerializeWorkerStack(aCx, currentWorker, stack);
+    }
+  }
+}
+
+JSObject* SerializedStackHolder::ReadStack(JSContext* aCx) {
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!mHolder.HasData()) {
+    return nullptr;
+  }
+
+  Maybe<nsJSPrincipals::AutoSetActiveWorkerPrincipal> set;
+  if (mWorkerRef) {
+    set.emplace(mWorkerRef->Private()->GetPrincipal());
+  }
+
+  JS::RootedValue stackValue(aCx);
+  mHolder.Read(xpc::CurrentNativeGlobal(aCx), aCx, &stackValue,
+               IgnoreErrors());
+  return stackValue.isObject() ? &stackValue.toObject() : nullptr;
+}
+
+UniquePtr<SerializedStackHolder> GetCurrentStackForNetMonitor(JSContext* aCx) {
+  UniquePtr<SerializedStackHolder> stack = MakeUnique<SerializedStackHolder>();
+  stack->SerializeCurrentStack(aCx);
+  return stack;
+}
+
+void NotifyNetworkMonitorAlternateStack(nsISupports* aChannel,
+                                        UniquePtr<SerializedStackHolder> aStackHolder) {
+  if (!aStackHolder) {
+    return;
+  }
+
+  nsString stackString;
+  ConvertSerializedStackToJSON(std::move(aStackHolder), stackString);
+
+  if (!stackString.IsEmpty()) {
+    NotifyNetworkMonitorAlternateStack(aChannel, stackString);
+  }
+}
+
+void ConvertSerializedStackToJSON(UniquePtr<SerializedStackHolder> aStackHolder,
+                                  nsAString& aStackString) {
+  // We need a JSContext to be able to stringify the SavedFrame stack.
+  // This will not run any scripts. A privileged scope is needed to fully
+  // inspect all stack frames we find.
+  AutoJSAPI jsapi;
+  DebugOnly<bool> ok = jsapi.Init(xpc::PrivilegedJunkScope());
+  JSContext* cx = jsapi.cx();
+
+  JS::RootedObject savedFrame(cx, aStackHolder->ReadStack(cx));
+  if (!savedFrame) {
+    return;
+  }
+
+  JS::RootedObject converted(cx);
+  converted = JS::ConvertSavedFrameToPlainObject(cx, savedFrame,
+                                                 JS::SavedFrameSelfHosted::Exclude);
+  if (!converted) {
+    JS_ClearPendingException(cx);
+    return;
+  }
+
+  JS::RootedValue convertedValue(cx, JS::ObjectValue(*converted));
+  if (!nsContentUtils::StringifyJSON(cx, &convertedValue, aStackString)) {
+    JS_ClearPendingException(cx);
+    return;
+  }
+}
+
+void NotifyNetworkMonitorAlternateStack(nsISupports* aChannel,
+                                        const nsAString& aStackJSON) {
+  nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+  if (!obsService) {
+    return;
+  }
+
+  obsService->NotifyObservers(aChannel, "network-monitor-alternate-stack",
+                              PromiseFlatString(aStackJSON).get());
+}
+
+}  // namespace dom
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/base/SerializedStackHolder.h
@@ -0,0 +1,80 @@
+/* -*- 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_SerializedStackHolder_h
+#define mozilla_dom_SerializedStackHolder_h
+
+#include "mozilla/dom/StructuredCloneHolder.h"
+#include "mozilla/dom/WorkerRef.h"
+
+namespace mozilla {
+namespace dom {
+
+// Information about a main or worker thread stack trace that can be accessed
+// from either kind of thread. When a worker thread stack is serialized, the
+// worker is held alive until this holder is destroyed.
+class SerializedStackHolder {
+  // Holds any encoded stack data.
+  StructuredCloneHolder mHolder;
+
+  // The worker associated with this stack, or null if this is a main thread
+  // stack.
+  RefPtr<ThreadSafeWorkerRef> mWorkerRef;
+
+  // Write aStack's data into mHolder.
+  void WriteStack(JSContext* aCx, JS::HandleObject aStack);
+
+ public:
+  SerializedStackHolder();
+
+  // Fill this holder with a main thread stack.
+  void SerializeMainThreadStack(JSContext* aCx, JS::HandleObject aStack);
+
+  // Fill this holder with a worker thread stack.
+  void SerializeWorkerStack(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+                            JS::HandleObject aStack);
+
+  // Fill this holder with the current thread's current stack.
+  void SerializeCurrentStack(JSContext* aCx);
+
+  // Read back a saved frame stack. This must be called on the main thread.
+  // This returns null on failure, and does not leave an exception on aCx.
+  JSObject* ReadStack(JSContext* aCx);
+};
+
+// Construct a stack for the current thread, which may be consumed by the net
+// monitor later on. This may be called on either the main or a worker thread.
+//
+// This always creates a stack, even if the net monitor isn't active for the
+// associated window. Ideally we would only create the stack if the net monitor
+// was active, but there doesn't seem to be an easy way to determine this.
+// The operations this is used with should be rare enough and/or have enough
+// other associated costs that the perf impact is low. See bug 1546736.
+UniquePtr<SerializedStackHolder> GetCurrentStackForNetMonitor(JSContext* aCx);
+
+// If aStackHolder is non-null, this notifies the net monitor that aStackHolder
+// is the stack from which aChannel originates. This must be called on the main
+// thread. This call is synchronous, and aChannel and aStackHolder will not be
+// used afterward. aChannel is an nsISupports object because this can be used
+// with either nsIChannel or nsIWebSocketChannel.
+void NotifyNetworkMonitorAlternateStack(nsISupports* aChannel,
+                                        UniquePtr<SerializedStackHolder> aStackHolder);
+
+// Read back the saved frame stack and store it in a string as JSON.
+// This must be called on the main thread.
+void ConvertSerializedStackToJSON(UniquePtr<SerializedStackHolder> aStackHolder,
+                                  nsAString& aStackString);
+
+// As above, notify the net monitor for a stack that has already been converted
+// to JSON. This can be used with ConvertSerializedStackToJSON when multiple
+// notifications might be needed for a single stack.
+void NotifyNetworkMonitorAlternateStack(nsISupports* aChannel,
+                                        const nsAString& aStackJSON);
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif  // mozilla_dom_SerializedStackHolder_h
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -218,16 +218,17 @@ EXPORTS.mozilla.dom += [
     'ProcessMessageManager.h',
     'ResizeObserver.h',
     'ResizeObserverController.h',
     'ResponsiveImageSelector.h',
     'SameProcessMessageQueue.h',
     'ScreenLuminance.h',
     'ScreenOrientation.h',
     'Selection.h',
+    'SerializedStackHolder.h',
     'ShadowIncludingTreeIterator.h',
     'ShadowRoot.h',
     'StructuredCloneBlob.h',
     'StructuredCloneHolder.h',
     'StructuredCloneTags.h',
     'StructuredCloneTester.h',
     'StyleSheetList.h',
     'SubtleCrypto.h',
@@ -387,16 +388,17 @@ UNIFIED_SOURCES += [
     'ResizeObserverController.cpp',
     'ResponsiveImageSelector.cpp',
     'SameProcessMessageQueue.cpp',
     'ScreenLuminance.cpp',
     'ScreenOrientation.cpp',
     'ScriptableContentIterator.cpp',
     'Selection.cpp',
     'SelectionChangeEventDispatcher.cpp',
+    'SerializedStackHolder.cpp',
     'ShadowRoot.cpp',
     'StorageAccessPermissionRequest.cpp',
     'StructuredCloneBlob.cpp',
     'StructuredCloneHolder.cpp',
     'StructuredCloneTester.cpp',
     'StyleSheetList.cpp',
     'SubtleCrypto.cpp',
     'TabGroup.cpp',
--- a/dom/base/nsContentPolicy.cpp
+++ b/dom/base/nsContentPolicy.cpp
@@ -94,17 +94,17 @@ inline nsresult nsContentPolicy::CheckPo
 #endif
 
   /*
    * There might not be a requestinglocation. This can happen for
    * iframes with an image as src. Get the uri from the dom node.
    * See bug 254510
    */
   if (!requestingLocation) {
-    nsCOMPtr<Document> doc;
+    nsCOMPtr<mozilla::dom::Document> doc;
     nsCOMPtr<nsIContent> node = do_QueryInterface(requestingContext);
     if (node) {
       doc = node->OwnerDoc();
     }
     if (!doc) {
       doc = do_QueryInterface(requestingContext);
     }
     if (doc) {
--- a/dom/base/nsFrameLoaderOwner.cpp
+++ b/dom/base/nsFrameLoaderOwner.cpp
@@ -7,16 +7,17 @@
 #include "nsFrameLoaderOwner.h"
 #include "nsFrameLoader.h"
 #include "nsFocusManager.h"
 #include "nsSubDocumentFrame.h"
 #include "nsQueryObject.h"
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/dom/BrowsingContext.h"
 #include "mozilla/dom/FrameLoaderBinding.h"
+#include "mozilla/dom/MozFrameLoaderOwnerBinding.h"
 
 already_AddRefed<nsFrameLoader> nsFrameLoaderOwner::GetFrameLoader() {
   return do_AddRef(mFrameLoader);
 }
 
 void nsFrameLoaderOwner::SetFrameLoader(nsFrameLoader* aNewFrameLoader) {
   mFrameLoader = aNewFrameLoader;
 }
@@ -64,12 +65,14 @@ void nsFrameLoaderOwner::ChangeRemotenes
       fm->ActivateRemoteFrameIfNeeded(*owner);
     }
   }
 
   // Assuming this element is a XULFrameElement, once we've reset our
   // FrameLoader, fire an event to act like we've recreated ourselves, similar
   // to what XULFrameElement does after rebinding to the tree.
   // ChromeOnlyDispatch is turns on to make sure this isn't fired into content.
-  (new AsyncEventDispatcher(owner, NS_LITERAL_STRING("XULFrameLoaderCreated"),
-                            CanBubble::eYes, ChromeOnlyDispatch::eYes))
+  (new mozilla::AsyncEventDispatcher(owner,
+                                     NS_LITERAL_STRING("XULFrameLoaderCreated"),
+                                     mozilla::CanBubble::eYes,
+                                     mozilla::ChromeOnlyDispatch::eYes))
       ->RunDOMEventWhenSafe();
 }
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -1551,17 +1551,17 @@ DOMInterfaces = {
     'headerFile': 'mozilla/dom/WorkerScope.h',
     'implicitJSContext': [
         'dump', 'global', 'reportError', 'setConsoleEventHandler',
     ],
 },
 
 'WorkerGlobalScope': {
     'headerFile': 'mozilla/dom/WorkerScope.h',
-    'implicitJSContext': [ 'createImageBitmap' ],
+    'implicitJSContext': [ 'createImageBitmap', 'importScripts' ],
     'concrete': False,
     # Rename a few things so we don't have both classes and methods
     # with the same name
     'binaryNames': {
         'performance': 'getPerformance',
     },
 },
 
--- a/dom/localstorage/ActorsChild.cpp
+++ b/dom/localstorage/ActorsChild.cpp
@@ -65,19 +65,19 @@ mozilla::ipc::IPCResult LSDatabaseChild:
     //       datastore right here, but asynchronously.
     //       However, we probably shouldn't do that if we are shutting down.
   }
 
   return IPC_OK();
 }
 
 PBackgroundLSSnapshotChild* LSDatabaseChild::AllocPBackgroundLSSnapshotChild(
-    const nsString& aDocumentURI, const bool& aIncreasePeakUsage,
-    const int64_t& aRequestedSize, const int64_t& aMinSize,
-    LSSnapshotInitInfo* aInitInfo) {
+    const nsString& aDocumentURI, const nsString& aKey,
+    const bool& aIncreasePeakUsage, const int64_t& aRequestedSize,
+    const int64_t& aMinSize, LSSnapshotInitInfo* aInitInfo) {
   MOZ_CRASH("PBackgroundLSSnapshotChild actor should be manually constructed!");
 }
 
 bool LSDatabaseChild::DeallocPBackgroundLSSnapshotChild(
     PBackgroundLSSnapshotChild* aActor) {
   MOZ_ASSERT(aActor);
 
   delete aActor;
@@ -121,32 +121,32 @@ 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 =
       PrincipalInfoToPrincipal(aPrincipalInfo, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return IPC_FAIL_NO_REASON(this);
   }
 
-  Storage::NotifyChange(/* aStorage */ nullptr, principal, aKey, aOldValue,
-                        aNewValue,
+  Storage::NotifyChange(/* aStorage */ nullptr, principal, aKey,
+                        aOldValue.AsString(), aNewValue.AsString(),
                         /* aStorageType */ kLocalStorageType, aDocumentURI,
                         /* aIsPrivate */ !!aPrivateBrowsingId,
                         /* aImmediateDispatch */ true);
 
   return IPC_OK();
 }
 
 /*******************************************************************************
--- a/dom/localstorage/ActorsChild.h
+++ b/dom/localstorage/ActorsChild.h
@@ -73,19 +73,19 @@ class LSDatabaseChild final : public PBa
   void SendDeleteMeInternal();
 
   // IPDL methods are only called by IPDL.
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   mozilla::ipc::IPCResult RecvRequestAllowToClose() override;
 
   PBackgroundLSSnapshotChild* AllocPBackgroundLSSnapshotChild(
-      const nsString& aDocumentURI, const bool& aIncreasePeakUsage,
-      const int64_t& aRequestedSize, const int64_t& aMinSize,
-      LSSnapshotInitInfo* aInitInfo) override;
+      const nsString& aDocumentURI, const nsString& aKey,
+      const bool& aIncreasePeakUsage, const int64_t& aRequestedSize,
+      const int64_t& aMinSize, LSSnapshotInitInfo* aInitInfo) override;
 
   bool DeallocPBackgroundLSSnapshotChild(
       PBackgroundLSSnapshotChild* aActor) override;
 };
 
 /**
  * Minimal IPC-managed (new/delete) actor that exists to receive and relay
  * "storage" events from changes to LocalStorage that take place in other
@@ -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
@@ -88,17 +88,17 @@ class Snapshot;
 typedef nsClassHashtable<nsCStringHashKey, ArchivedOriginInfo>
     ArchivedOriginHashtable;
 
 /*******************************************************************************
  * Constants
  ******************************************************************************/
 
 // Major schema version. Bump for almost everything.
-const uint32_t kMajorSchemaVersion = 2;
+const uint32_t kMajorSchemaVersion = 4;
 
 // Minor schema version. Should almost always be 0 (maybe bump on release
 // branches if we have to).
 const uint32_t kMinorSchemaVersion = 0;
 
 // The schema version we store in the SQLite database is a (signed) 32-bit
 // integer. The major version is left-shifted 4 bits so the max value is
 // 0xFFFFFFF. The minor version occupies the lower 4 bits and its max is 0xF.
@@ -316,16 +316,17 @@ nsresult CreateTables(mozIStorageConnect
     return rv;
   }
 
   // Table `data`
   rv = aConnection->ExecuteSimpleSQL(
       NS_LITERAL_CSTRING("CREATE TABLE data"
                          "( key TEXT PRIMARY KEY"
                          ", value TEXT NOT NULL"
+                         ", utf16Length INTEGER NOT NULL DEFAULT 0"
                          ", compressed INTEGER NOT NULL DEFAULT 0"
                          ", lastAccessTime INTEGER NOT NULL DEFAULT 0"
                          ");"));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = aConnection->SetSchemaVersion(kSQLiteSchemaVersion);
@@ -357,16 +358,52 @@ nsresult UpgradeSchemaFrom1_0To2_0(mozIS
   rv = aConnection->SetSchemaVersion(MakeSchemaVersion(2, 0));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
+nsresult UpgradeSchemaFrom2_0To3_0(mozIStorageConnection* aConnection) {
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aConnection);
+
+  nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+      "ALTER TABLE data ADD COLUMN utf16Length INTEGER NOT NULL DEFAULT 0;"));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(
+      NS_LITERAL_CSTRING("UPDATE data SET utf16Length = utf16Length(value);"));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->SetSchemaVersion(MakeSchemaVersion(3, 0));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult UpgradeSchemaFrom3_0To4_0(mozIStorageConnection* aConnection) {
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aConnection);
+
+  nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(4, 0));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
 nsresult SetDefaultPragmas(mozIStorageConnection* aConnection) {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aConnection);
 
   nsresult rv = aConnection->ExecuteSimpleSQL(
       NS_LITERAL_CSTRING("PRAGMA synchronous = FULL;"));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -506,22 +543,26 @@ nsresult CreateStorageConnection(nsIFile
       }
 
       rv = stmt->Execute();
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     } else {
       // This logic needs to change next time we change the schema!
-      static_assert(kSQLiteSchemaVersion == int32_t((2 << 4) + 0),
+      static_assert(kSQLiteSchemaVersion == int32_t((4 << 4) + 0),
                     "Upgrade function needed due to schema version increase.");
 
       while (schemaVersion != kSQLiteSchemaVersion) {
         if (schemaVersion == MakeSchemaVersion(1, 0)) {
           rv = UpgradeSchemaFrom1_0To2_0(connection);
+        } else if (schemaVersion == MakeSchemaVersion(2, 0)) {
+          rv = UpgradeSchemaFrom2_0To3_0(connection);
+        } else if (schemaVersion == MakeSchemaVersion(3, 0)) {
+          rv = UpgradeSchemaFrom3_0To4_0(connection);
         } else {
           LS_WARNING(
               "Unable to open LocalStorage database, no upgrade path is "
               "available!");
           return NS_ERROR_FAILURE;
         }
 
         if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -1222,20 +1263,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 +1306,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 +1517,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 +1682,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 +1708,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
@@ -1708,43 +1748,44 @@ class Datastore final
   void NoteLiveDatabase(Database* aDatabase);
 
   void NoteFinishedDatabase(Database* aDatabase);
 
   void NoteActiveDatabase(Database* aDatabase);
 
   void NoteInactiveDatabase(Database* aDatabase);
 
-  void GetSnapshotInitInfo(nsTHashtable<nsStringHashKey>& aLoadedItems,
+  void GetSnapshotInitInfo(const nsString& aKey, bool& aAddKeyToUnknownItems,
+                           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);
@@ -1761,23 +1802,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.
@@ -1924,24 +1965,25 @@ class Database final
   // IPDL methods are only called by IPDL.
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   mozilla::ipc::IPCResult RecvDeleteMe() override;
 
   mozilla::ipc::IPCResult RecvAllowToClose() override;
 
   PBackgroundLSSnapshotParent* AllocPBackgroundLSSnapshotParent(
-      const nsString& aDocumentURI, const bool& aIncreasePeakUsage,
-      const int64_t& aRequestedSize, const int64_t& aMinSize,
-      LSSnapshotInitInfo* aInitInfo) override;
+      const nsString& aDocumentURI, const nsString& aKey,
+      const bool& aIncreasePeakUsage, const int64_t& aRequestedSize,
+      const int64_t& aMinSize, LSSnapshotInitInfo* aInitInfo) override;
 
   mozilla::ipc::IPCResult RecvPBackgroundLSSnapshotConstructor(
       PBackgroundLSSnapshotParent* aActor, const nsString& aDocumentURI,
-      const bool& aIncreasePeakUsage, const int64_t& aRequestedSize,
-      const int64_t& aMinSize, LSSnapshotInitInfo* aInitInfo) override;
+      const nsString& aKey, const bool& aIncreasePeakUsage,
+      const int64_t& aRequestedSize, const int64_t& aMinSize,
+      LSSnapshotInitInfo* aInitInfo) override;
 
   bool DeallocPBackgroundLSSnapshotParent(
       PBackgroundLSSnapshotParent* aActor) override;
 };
 
 /**
  * Attempts to capture the state of the underlying Datastore at the time of its
  * creation so run-to-completion semantics can be honored.
@@ -1989,17 +2031,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;
@@ -2046,50 +2088,54 @@ class Snapshot final : public PBackgroun
   bool mLoadKeysReceived;
   bool mSentMarkDirty;
 
  public:
   // Created in AllocPBackgroundLSSnapshotParent.
   Snapshot(Database* aDatabase, const nsAString& aDocumentURI);
 
   void Init(nsTHashtable<nsStringHashKey>& aLoadedItems,
+            nsTHashtable<nsStringHashKey>& aUnknownItems,
             uint32_t aNextLoadIndex, uint32_t aTotalLength,
             int64_t aInitialUsage, int64_t aPeakUsage,
             LSSnapshot::LoadState aLoadState) {
     AssertIsOnBackgroundThread();
     MOZ_ASSERT(aInitialUsage >= 0);
     MOZ_ASSERT(aPeakUsage >= aInitialUsage);
     MOZ_ASSERT_IF(aLoadState != LSSnapshot::LoadState::AllOrderedItems,
                   aNextLoadIndex < aTotalLength);
     MOZ_ASSERT(mTotalLength == 0);
     MOZ_ASSERT(mUsage == -1);
     MOZ_ASSERT(mPeakUsage == -1);
 
     mLoadedItems.SwapElements(aLoadedItems);
+    mUnknownItems.SwapElements(aUnknownItems);
     mNextLoadIndex = aNextLoadIndex;
     mTotalLength = aTotalLength;
     mUsage = aInitialUsage;
     mPeakUsage = aPeakUsage;
     if (aLoadState == LSSnapshot::LoadState::AllOrderedKeys) {
+      MOZ_ASSERT(mUnknownItems.Count() == 0);
       mLoadKeysReceived = true;
     } else if (aLoadState == LSSnapshot::LoadState::AllOrderedItems) {
       MOZ_ASSERT(mLoadedItems.Count() == 0);
+      MOZ_ASSERT(mUnknownItems.Count() == 0);
       MOZ_ASSERT(mNextLoadIndex == mTotalLength);
       mLoadedReceived = true;
       mLoadedAllItems = true;
       mLoadKeysReceived = true;
     }
   }
 
   /**
    * 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.
@@ -2105,17 +2151,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;
 
@@ -2128,18 +2174,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.
@@ -2285,17 +2331,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;
@@ -3567,34 +3613,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);
@@ -3753,29 +3799,42 @@ nsresult WriteOptimizer::PerformWrites(C
 
 nsresult WriteOptimizer::AddItemInfo::Perform(Connection* aConnection,
                                               bool aShadowWrites) {
   AssertIsOnConnectionThread();
   MOZ_ASSERT(aConnection);
 
   Connection::CachedStatement stmt;
   nsresult rv = aConnection->GetCachedStatement(
-      NS_LITERAL_CSTRING("INSERT OR REPLACE INTO data (key, value) "
-                         "VALUES(:key, :value)"),
+      NS_LITERAL_CSTRING(
+          "INSERT OR REPLACE INTO data (key, value, utf16Length, compressed) "
+          "VALUES(:key, :value, :utf16Length, :compressed)"),
       &stmt);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"), mKey);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  rv = stmt->BindStringByName(NS_LITERAL_CSTRING("value"), mValue);
+  rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("value"), mValue);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("utf16Length"),
+                             mValue.UTF16Length());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("compressed"),
+                             mValue.IsCompressed());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = stmt->Execute();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -3810,17 +3869,25 @@ nsresult WriteOptimizer::AddItemInfo::Pe
     return rv;
   }
 
   rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"), mKey);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  rv = stmt->BindStringByName(NS_LITERAL_CSTRING("value"), mValue);
+  if (mValue.IsCompressed()) {
+    nsCString value;
+    if (NS_WARN_IF(!SnappyUncompress(mValue, value))) {
+      return NS_ERROR_FAILURE;
+    }
+    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("value"), value);
+  } else {
+    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("value"), mValue);
+  }
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = stmt->Execute();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -4067,25 +4134,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) {
@@ -4463,17 +4530,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),
@@ -4641,17 +4708,19 @@ void Datastore::NoteInactiveDatabase(Dat
       DebugOnly<bool> ok = UpdateUsage(finalDelta);
       MOZ_ASSERT(ok);
     }
 
     mPendingUsageDeltas.Clear();
   }
 }
 
-void Datastore::GetSnapshotInitInfo(nsTHashtable<nsStringHashKey>& aLoadedItems,
+void Datastore::GetSnapshotInitInfo(const nsString& aKey,
+                                    bool& aAddKeyToUnknownItems,
+                                    nsTHashtable<nsStringHashKey>& aLoadedItems,
                                     nsTArray<LSItemInfo>& aItemInfos,
                                     uint32_t& aNextLoadIndex,
                                     uint32_t& aTotalLength,
                                     int64_t& aInitialUsage, int64_t& aPeakUsage,
                                     LSSnapshot::LoadState& aLoadState) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(!mClosed);
   MOZ_ASSERT(!mInUpdateBatch);
@@ -4663,90 +4732,200 @@ void Datastore::GetSnapshotInitInfo(nsTH
     int64_t sizeOfKey = static_cast<int64_t>(item.key().Length());
     sizeOfKeys += sizeOfKey;
     sizeOfItems += sizeOfKey + static_cast<int64_t>(item.value().Length());
   }
   MOZ_ASSERT(mSizeOfKeys == sizeOfKeys);
   MOZ_ASSERT(mSizeOfItems == sizeOfItems);
 #endif
 
-  if (mSizeOfKeys <= gSnapshotPrefill) {
-    if (mSizeOfItems <= gSnapshotPrefill) {
+  // Computes load state optimized for current size of keys and items.
+  // Zero key length and value can be passed to do a quick initial estimation.
+  // If computed load state is already AllOrderedItems then excluded key length
+  // and value length can't make it any better.
+  auto GetLoadState = [&](auto aKeyLength, auto aValueLength) {
+    if (mSizeOfKeys - aKeyLength <= gSnapshotPrefill) {
+      if (mSizeOfItems - aKeyLength - aValueLength <= gSnapshotPrefill) {
+        return LSSnapshot::LoadState::AllOrderedItems;
+      }
+
+      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).
+  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.
+  LSSnapshot::LoadState loadState = GetLoadState(/* aKeyLength */ 0,
+                                                 /* aValueLength */ 0);
+  if (loadState != LSSnapshot::LoadState::AllOrderedItems && !aKey.IsVoid()) {
+    GetItem(aKey, value);
+    if (!value.IsVoid()) {
+      // Ok, we have a non void aKey and value.
+
+      // We have to watch for aKey during one of the loops below to exclude it
+      // from the size computation. The super fast mode (AllOrderedItems)
+      // doesn't have to do that though.
+      checkKey = true;
+
+      // We have to compute load state again because aKey length and value
+      // length is excluded from the size in this case.
+      loadState = GetLoadState(aKey.Length(), value.Length());
+    }
+  }
+
+  switch (loadState) {
+    case LSSnapshot::LoadState::AllOrderedItems: {
+      // We're sending all ordered items, we don't need to check keys because
+      // mOrderedItems must contain a value for aKey if checkKey is true.
+
       aItemInfos.AppendElements(mOrderedItems);
 
       MOZ_ASSERT(aItemInfos.Length() == mValues.Count());
       aNextLoadIndex = mValues.Count();
 
-      aLoadState = LSSnapshot::LoadState::AllOrderedItems;
-    } else {
+      aAddKeyToUnknownItems = false;
+
+      break;
+    }
+
+    case LSSnapshot::LoadState::AllOrderedKeys: {
+      // We don't have enough snapshot budget to send all items, but we do have
+      // enough to send all of the keys and to make a best effort to populate as
+      // many values as possible. We send void string values once we run out of
+      // budget. A complicating factor is that we want to make sure that we send
+      // the value for aKey which is a localStorage read that's triggering this
+      // request. Since that key can happen anywhere in the list of items, we
+      // need to handle it specially.
+      //
+      // The loop is effectively doing 2 things in parallel:
+      //
+      //   1. Looking for the `aKey` to send. This is tracked by `checkKey`
+      //      which is true if there was an `aKey` specified and until we
+      //      populate its value, and false thereafter.
+      //   2. Sending values until we run out of `size` budget and switch to
+      //      sending void values. `doneSendingValues` tracks when we've run out
+      //      of size budget, with `setVoidValue` tracking whether a value
+      //      should be sent for each turn of the event loop but can be
+      //      overridden when `aKey` is found.
+
       int64_t size = mSizeOfKeys;
-      nsString value;
+      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();
-
-        if (!value.IsVoid()) {
-          value = item.value();
-
-          size += static_cast<int64_t>(value.Length());
-
-          if (size <= gSnapshotPrefill) {
-            aLoadedItems.PutEntry(key);
+        const LSValue& value = item.value();
+
+        if (checkKey && key == aKey) {
+          checkKey = false;
+          setVoidValue = false;
+        } else if (!setVoidValue) {
+          if (doneSendingValues) {
+            setVoidValue = true;
           } else {
-            value.SetIsVoid(true);
-
-            // We set value to void so that will guard against entering the
-            // parent branch during next iterations. So aNextLoadIndex is set
-            // only once.
-            aNextLoadIndex = index;
+            size += static_cast<int64_t>(value.Length());
+
+            if (size > gSnapshotPrefill) {
+              setVoidValue = true;
+              doneSendingValues = true;
+
+              // We set doneSendingValues to true and that will guard against
+              // entering this branch during next iterations. So aNextLoadIndex
+              // is set only once.
+              aNextLoadIndex = index;
+            }
           }
         }
 
         LSItemInfo* itemInfo = aItemInfos.AppendElement();
         itemInfo->key() = key;
+        if (setVoidValue) {
+          itemInfo->value().SetIsVoid(true);
+        } else {
+          aLoadedItems.PutEntry(key);
+          itemInfo->value() = value;
+        }
+      }
+
+      aAddKeyToUnknownItems = false;
+
+      break;
+    }
+
+    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 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) {
+            aNextLoadIndex = index;
+            break;
+          }
+        }
+
+        aLoadedItems.PutEntry(key);
+
+        LSItemInfo* itemInfo = aItemInfos.AppendElement();
+        itemInfo->key() = key;
         itemInfo->value() = value;
       }
 
-      aLoadState = LSSnapshot::LoadState::AllOrderedKeys;
-    }
-  } else {
-    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();
-
-      size += static_cast<int64_t>(key.Length()) +
-              static_cast<int64_t>(value.Length());
-
-      if (size > gSnapshotPrefill) {
-        aNextLoadIndex = index;
-        break;
+      aAddKeyToUnknownItems = false;
+
+      if (!aKey.IsVoid()) {
+        if (value.IsVoid()) {
+          aAddKeyToUnknownItems = true;
+        } else if (checkKey) {
+          // The item wasn't added in the loop above, add it here.
+
+          LSItemInfo* itemInfo = aItemInfos.AppendElement();
+          itemInfo->key() = aKey;
+          itemInfo->value() = value;
+        }
       }
 
-      aLoadedItems.PutEntry(key);
-
-      LSItemInfo* itemInfo = aItemInfos.AppendElement();
-      itemInfo->key() = key;
-      itemInfo->value() = value;
-    }
-
-    MOZ_ASSERT(aItemInfos.Length() < mOrderedItems.Length());
-    aLoadState = LSSnapshot::LoadState::Partial;
+      MOZ_ASSERT(aItemInfos.Length() < mOrderedItems.Length());
+
+      break;
+    }
+
+    default:
+      MOZ_CRASH("Bad load state value!");
   }
 
   aTotalLength = mValues.Count();
 
   aInitialUsage = mUsage;
   aPeakUsage = aInitialUsage;
-}
-
-void Datastore::GetItem(const nsString& aKey, nsString& aValue) const {
+
+  aLoadState = loadState;
+}
+
+void Datastore::GetItem(const nsString& aKey, LSValue& aValue) const {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(!mClosed);
 
   if (!mValues.Get(aKey, &aValue)) {
     aValue.SetIsVoid(true);
   }
 }
 
@@ -4755,135 +4934,139 @@ 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;
+    int64_t delta;
 
     if (isNewItem) {
       mWriteOptimizer.AddItem(aKey, aValue);
 
       int64_t sizeOfKey = static_cast<int64_t>(aKey.Length());
-      sizeOfItem = sizeOfKey + static_cast<int64_t>(aValue.Length());
-
-      mUpdateBatchUsage += sizeOfItem;
+
+      delta = sizeOfKey + static_cast<int64_t>(aValue.UTF16Length());
+
+      mUpdateBatchUsage += delta;
 
       mSizeOfKeys += sizeOfKey;
-      mSizeOfItems += sizeOfItem;
+      mSizeOfItems += sizeOfKey + static_cast<int64_t>(aValue.Length());
+      ;
     } else {
       mWriteOptimizer.UpdateItem(aKey, aValue);
 
-      sizeOfItem = static_cast<int64_t>(aValue.Length()) -
-                   static_cast<int64_t>(oldValue.Length());
-
-      mUpdateBatchUsage += sizeOfItem;
-
-      mSizeOfItems += sizeOfItem;
+      delta = static_cast<int64_t>(aValue.UTF16Length()) -
+              static_cast<int64_t>(oldValue.UTF16Length());
+
+      mUpdateBatchUsage += delta;
+
+      mSizeOfItems += static_cast<int64_t>(aValue.Length()) -
+                      static_cast<int64_t>(oldValue.Length());
     }
 
     if (IsPersistent()) {
       if (oldValue.IsVoid()) {
-        mConnection->AddItem(aKey, aValue, sizeOfItem);
+        mConnection->AddItem(aKey, aValue, delta);
       } else {
-        mConnection->UpdateItem(aKey, aValue, sizeOfItem);
+        mConnection->UpdateItem(aKey, aValue, delta);
       }
     }
   }
 
   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);
 
     int64_t sizeOfKey = static_cast<int64_t>(aKey.Length());
-    int64_t sizeOfItem = sizeOfKey + static_cast<int64_t>(oldValue.Length());
-
-    mUpdateBatchUsage -= sizeOfItem;
+
+    int64_t delta = -sizeOfKey - static_cast<int64_t>(oldValue.UTF16Length());
+
+    mUpdateBatchUsage += delta;
 
     mSizeOfKeys -= sizeOfKey;
-    mSizeOfItems -= sizeOfItem;
+    mSizeOfItems -= sizeOfKey + static_cast<int64_t>(oldValue.Length());
 
     if (IsPersistent()) {
-      mConnection->RemoveItem(aKey, -sizeOfItem);
-    }
-  }
-
-  NotifyObservers(aDatabase, aDocumentURI, aKey, aOldValue, VoidString());
+      mConnection->RemoveItem(aKey, delta);
+    }
+  }
+
+  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;
+    int64_t delta = 0;
     for (auto iter = mValues.ConstIter(); !iter.Done(); iter.Next()) {
       const nsAString& key = iter.Key();
-      const nsAString& value = iter.Data();
-
-      sizeOfItems += (static_cast<int64_t>(key.Length()) +
-                      static_cast<int64_t>(value.Length()));
+      const LSValue& value = iter.Data();
+
+      delta += -static_cast<int64_t>(key.Length()) -
+               static_cast<int64_t>(value.UTF16Length());
 
       NotifySnapshots(aDatabase, key, value, /* aAffectsOrder */ true);
     }
 
     mValues.Clear();
 
     mWriteOptimizer.Clear();
 
-    mUpdateBatchUsage -= sizeOfItems;
+    mUpdateBatchUsage += delta;
 
     mSizeOfKeys = 0;
     mSizeOfItems = 0;
 
     if (IsPersistent()) {
-      mConnection->Clear(-sizeOfItems);
-    }
-  }
-
-  NotifyObservers(aDatabase, aDocumentURI, VoidString(), VoidString(),
-                  VoidString());
+      mConnection->Clear(delta);
+    }
+  }
+
+  NotifyObservers(aDatabase, aDocumentURI, VoidString(), VoidLSValue(),
+                  VoidLSValue());
 }
 
 void Datastore::PrivateBrowsingClear() {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(mPrivateBrowsingId);
   MOZ_ASSERT(!mClosed);
   MOZ_ASSERT(!mInUpdateBatch);
 
@@ -5044,18 +5227,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;
     }
@@ -5077,18 +5259,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;
@@ -5278,19 +5460,19 @@ mozilla::ipc::IPCResult Database::RecvAl
   }
 
   AllowToClose();
 
   return IPC_OK();
 }
 
 PBackgroundLSSnapshotParent* Database::AllocPBackgroundLSSnapshotParent(
-    const nsString& aDocumentURI, const bool& aIncreasePeakUsage,
-    const int64_t& aRequestedSize, const int64_t& aMinSize,
-    LSSnapshotInitInfo* aInitInfo) {
+    const nsString& aDocumentURI, const nsString& aKey,
+    const bool& aIncreasePeakUsage, const int64_t& aRequestedSize,
+    const int64_t& aMinSize, LSSnapshotInitInfo* aInitInfo) {
   AssertIsOnBackgroundThread();
 
   if (NS_WARN_IF(aIncreasePeakUsage && aRequestedSize <= 0)) {
     ASSERT_UNLESS_FUZZING();
     return nullptr;
   }
 
   if (NS_WARN_IF(aIncreasePeakUsage && aMinSize <= 0)) {
@@ -5306,49 +5488,55 @@ PBackgroundLSSnapshotParent* Database::A
   RefPtr<Snapshot> snapshot = new Snapshot(this, aDocumentURI);
 
   // Transfer ownership to IPDL.
   return snapshot.forget().take();
 }
 
 mozilla::ipc::IPCResult Database::RecvPBackgroundLSSnapshotConstructor(
     PBackgroundLSSnapshotParent* aActor, const nsString& aDocumentURI,
-    const bool& aIncreasePeakUsage, const int64_t& aRequestedSize,
-    const int64_t& aMinSize, LSSnapshotInitInfo* aInitInfo) {
+    const nsString& aKey, const bool& aIncreasePeakUsage,
+    const int64_t& aRequestedSize, const int64_t& aMinSize,
+    LSSnapshotInitInfo* aInitInfo) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT_IF(aIncreasePeakUsage, aRequestedSize > 0);
   MOZ_ASSERT_IF(aIncreasePeakUsage, aMinSize > 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.
+  bool addKeyToUnknownItems;
   nsTHashtable<nsStringHashKey> loadedItems;
   nsTArray<LSItemInfo> itemInfos;
   uint32_t nextLoadIndex;
   uint32_t totalLength;
   int64_t initialUsage;
   int64_t peakUsage;
   LSSnapshot::LoadState loadState;
-  mDatastore->GetSnapshotInitInfo(loadedItems, itemInfos, nextLoadIndex,
-                                  totalLength, initialUsage, peakUsage,
-                                  loadState);
+  mDatastore->GetSnapshotInitInfo(aKey, addKeyToUnknownItems, loadedItems,
+                                  itemInfos, nextLoadIndex, totalLength,
+                                  initialUsage, peakUsage, loadState);
+
+  nsTHashtable<nsStringHashKey> unknownItems;
+  if (addKeyToUnknownItems) {
+    unknownItems.PutEntry(aKey);
+  }
 
   if (aIncreasePeakUsage) {
     int64_t size = mDatastore->RequestUpdateUsage(aRequestedSize, aMinSize);
     peakUsage += size;
   }
 
-  snapshot->Init(loadedItems, nextLoadIndex, totalLength, initialUsage,
-                 peakUsage, loadState);
+  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;
 
   return IPC_OK();
 }
@@ -5386,28 +5574,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;
   }
 }
@@ -5552,17 +5740,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);
@@ -5641,17 +5829,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();
         }
@@ -5774,18 +5962,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);
 }
 
@@ -6535,18 +6723,18 @@ nsresult PrepareDatastoreOp::DatabaseWor
       return NS_ERROR_FILE_NO_DEVICE_SPACE;
     }
 
     mozStorageTransaction transaction(
         connection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
 
     nsCOMPtr<mozIStorageStatement> stmt;
     rv = connection->CreateStatement(
-        NS_LITERAL_CSTRING("INSERT INTO data (key, value) "
-                           "SELECT key, value "
+        NS_LITERAL_CSTRING("INSERT INTO data (key, value, utf16Length) "
+                           "SELECT key, value, utf16Length(value) "
                            "FROM webappsstore2 "
                            "WHERE originKey = :originKey "
                            "AND originAttributes = :originAttributes;"
 
                            ),
         getter_AddRefs(stmt));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
@@ -7157,46 +7345,60 @@ nsresult PrepareDatastoreOp::LoadDataOp:
              NestedState::DatabaseWorkLoadData);
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
       !MayProceedOnNonOwningThread()) {
     return NS_ERROR_FAILURE;
   }
 
   Connection::CachedStatement stmt;
-  nsresult rv =
-      mConnection->GetCachedStatement(NS_LITERAL_CSTRING("SELECT key, value "
-                                                         "FROM data;"),
-                                      &stmt);
+  nsresult rv = mConnection->GetCachedStatement(
+      NS_LITERAL_CSTRING("SELECT key, value, utf16Length, compressed "
+                         "FROM data;"),
+      &stmt);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   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);
+    nsCString buffer;
+    rv = stmt->GetUTF8String(1, buffer);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    int32_t utf16Length;
+    rv = stmt->GetInt32(2, &utf16Length);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
+    int32_t compressed;
+    rv = stmt->GetInt32(3, &compressed);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    LSValue value(buffer, utf16Length, compressed);
+
     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();
+    mPrepareDatastoreOp->mDEBUGUsage += key.Length() + value.UTF16Length();
 #endif
   }
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
--- a/dom/localstorage/LSDatabase.cpp
+++ b/dom/localstorage/LSDatabase.cpp
@@ -115,17 +115,17 @@ void LSDatabase::NoteFinishedSnapshot(LS
 }
 
 nsresult LSDatabase::GetLength(LSObject* aObject, uint32_t* aResult) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aObject);
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(!mAllowedToClose);
 
-  nsresult rv = EnsureSnapshot(aObject);
+  nsresult rv = EnsureSnapshot(aObject, VoidString());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = mSnapshot->GetLength(aResult);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -135,17 +135,17 @@ nsresult LSDatabase::GetLength(LSObject*
 
 nsresult LSDatabase::GetKey(LSObject* aObject, uint32_t aIndex,
                             nsAString& aResult) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aObject);
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(!mAllowedToClose);
 
-  nsresult rv = EnsureSnapshot(aObject);
+  nsresult rv = EnsureSnapshot(aObject, VoidString());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = mSnapshot->GetKey(aIndex, aResult);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -155,17 +155,17 @@ nsresult LSDatabase::GetKey(LSObject* aO
 
 nsresult LSDatabase::GetItem(LSObject* aObject, const nsAString& aKey,
                              nsAString& aResult) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aObject);
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(!mAllowedToClose);
 
-  nsresult rv = EnsureSnapshot(aObject);
+  nsresult rv = EnsureSnapshot(aObject, aKey);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = mSnapshot->GetItem(aKey, aResult);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -174,17 +174,17 @@ nsresult LSDatabase::GetItem(LSObject* a
 }
 
 nsresult LSDatabase::GetKeys(LSObject* aObject, nsTArray<nsString>& aKeys) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aObject);
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(!mAllowedToClose);
 
-  nsresult rv = EnsureSnapshot(aObject);
+  nsresult rv = EnsureSnapshot(aObject, VoidString());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = mSnapshot->GetKeys(aKeys);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -195,17 +195,17 @@ nsresult LSDatabase::GetKeys(LSObject* a
 nsresult LSDatabase::SetItem(LSObject* aObject, const nsAString& aKey,
                              const nsAString& aValue,
                              LSNotifyInfo& aNotifyInfo) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aObject);
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(!mAllowedToClose);
 
-  nsresult rv = EnsureSnapshot(aObject);
+  nsresult rv = EnsureSnapshot(aObject, aKey);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = mSnapshot->SetItem(aKey, aValue, aNotifyInfo);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -215,17 +215,17 @@ nsresult LSDatabase::SetItem(LSObject* a
 
 nsresult LSDatabase::RemoveItem(LSObject* aObject, const nsAString& aKey,
                                 LSNotifyInfo& aNotifyInfo) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aObject);
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(!mAllowedToClose);
 
-  nsresult rv = EnsureSnapshot(aObject);
+  nsresult rv = EnsureSnapshot(aObject, aKey);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = mSnapshot->RemoveItem(aKey, aNotifyInfo);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -234,17 +234,17 @@ nsresult LSDatabase::RemoveItem(LSObject
 }
 
 nsresult LSDatabase::Clear(LSObject* aObject, LSNotifyInfo& aNotifyInfo) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aObject);
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(!mAllowedToClose);
 
-  nsresult rv = EnsureSnapshot(aObject);
+  nsresult rv = EnsureSnapshot(aObject, VoidString());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = mSnapshot->Clear(aNotifyInfo);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -257,17 +257,17 @@ nsresult LSDatabase::BeginExplicitSnapsh
   MOZ_ASSERT(aObject);
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(!mAllowedToClose);
 
   if (mSnapshot) {
     return NS_ERROR_ALREADY_INITIALIZED;
   }
 
-  nsresult rv = EnsureSnapshot(aObject, /* aExplicit */ true);
+  nsresult rv = EnsureSnapshot(aObject, VoidString(), /* aExplicit */ true);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
 nsresult LSDatabase::EndExplicitSnapshot(LSObject* aObject) {
@@ -285,44 +285,45 @@ nsresult LSDatabase::EndExplicitSnapshot
   nsresult rv = mSnapshot->End();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
-nsresult LSDatabase::EnsureSnapshot(LSObject* aObject, bool aExplicit) {
+nsresult LSDatabase::EnsureSnapshot(LSObject* aObject, const nsAString& aKey,
+                                    bool aExplicit) {
   MOZ_ASSERT(aObject);
   MOZ_ASSERT(mActor);
   MOZ_ASSERT_IF(mSnapshot, !aExplicit);
   MOZ_ASSERT(!mAllowedToClose);
 
   if (mSnapshot) {
     return NS_OK;
   }
 
   RefPtr<LSSnapshot> snapshot = new LSSnapshot(this);
 
   LSSnapshotChild* actor = new LSSnapshotChild(snapshot);
 
   LSSnapshotInitInfo initInfo;
   bool ok = mActor->SendPBackgroundLSSnapshotConstructor(
-      actor, aObject->DocumentURI(),
+      actor, aObject->DocumentURI(), nsString(aKey),
       /* increasePeakUsage */ true,
       /* requestedSize */ 131072,
       /* minSize */ 4096, &initInfo);
   if (NS_WARN_IF(!ok)) {
     return NS_ERROR_FAILURE;
   }
 
   snapshot->SetActor(actor);
 
   // This add refs snapshot.
-  nsresult rv = snapshot->Init(initInfo, aExplicit);
+  nsresult rv = snapshot->Init(aKey, initInfo, aExplicit);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // This is cleared in LSSnapshot::Run() before the snapshot is destroyed.
   mSnapshot = snapshot;
 
   return NS_OK;
--- a/dom/localstorage/LSDatabase.h
+++ b/dom/localstorage/LSDatabase.h
@@ -74,17 +74,18 @@ class LSDatabase final {
 
   nsresult BeginExplicitSnapshot(LSObject* aObject);
 
   nsresult EndExplicitSnapshot(LSObject* aObject);
 
  private:
   ~LSDatabase();
 
-  nsresult EnsureSnapshot(LSObject* aObject, bool aExplicit = false);
+  nsresult EnsureSnapshot(LSObject* aObject, const nsAString& aKey,
+                          bool aExplicit = false);
 
   void AllowToClose();
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_localstorage_LSDatabase_h
--- a/dom/localstorage/LSSnapshot.cpp
+++ b/dom/localstorage/LSSnapshot.cpp
@@ -54,42 +54,46 @@ LSSnapshot::~LSSnapshot() {
 void LSSnapshot::SetActor(LSSnapshotChild* aActor) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aActor);
   MOZ_ASSERT(!mActor);
 
   mActor = aActor;
 }
 
-nsresult LSSnapshot::Init(const LSSnapshotInitInfo& aInitInfo, bool aExplicit) {
+nsresult LSSnapshot::Init(const nsAString& aKey,
+                          const LSSnapshotInitInfo& aInitInfo, bool aExplicit) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(!mSelfRef);
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(mLoadState == LoadState::Initial);
   MOZ_ASSERT(!mInitialized);
   MOZ_ASSERT(!mSentFinish);
 
   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);
+    mValues.Put(itemInfo.key(), value.AsString());
   }
 
   if (loadState == LoadState::Partial) {
+    if (aInitInfo.addKeyToUnknownItems()) {
+      mUnknownItems.PutEntry(aKey);
+    }
     mInitLength = aInitInfo.totalLength();
     mLength = mInitLength;
   } else if (loadState == LoadState::AllOrderedKeys) {
     mInitLength = aInitInfo.totalLength();
   } else {
     MOZ_ASSERT(loadState == LoadState::AllOrderedItems);
   }
 
@@ -234,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;
@@ -280,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;
@@ -427,36 +431,39 @@ 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.AsString();
+
         if (result.IsVoid()) {
           mUnknownItems.PutEntry(aKey);
         } else {
           mLoadedItems.PutEntry(aKey);
           mValues.Put(aKey, result);
 
           // mLoadedItems.Count()==mInitLength is checked below.
         }
 
         for (uint32_t i = 0; i < itemInfos.Length(); i++) {
           const LSItemInfo& itemInfo = itemInfos[i];
 
           mLoadedItems.PutEntry(itemInfo.key());
-          mValues.Put(itemInfo.key(), itemInfo.value());
+          mValues.Put(itemInfo.key(), itemInfo.value().AsString());
         }
 
         if (mLoadedItems.Count() == mInitLength) {
           mLoadedItems.Clear();
           mUnknownItems.Clear();
           mLength = 0;
           mLoadState = LoadState::AllUnorderedItems;
         }
@@ -472,34 +479,37 @@ 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.AsString();
+
           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++) {
             const LSItemInfo& itemInfo = itemInfos[i];
 
             mLoadedItems.PutEntry(itemInfo.key());
-            mValues.Put(itemInfo.key(), itemInfo.value());
+            mValues.Put(itemInfo.key(), itemInfo.value().AsString());
           }
 
           if (mLoadedItems.Count() == mInitLength) {
             mLoadedItems.Clear();
             MOZ_ASSERT(mLength == 0);
             mLoadState = LoadState::AllOrderedItems;
           }
         }
--- 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;
@@ -107,17 +109,18 @@ class LSSnapshot final : public nsIRunna
     AssertIsOnOwningThread();
     MOZ_ASSERT(mActor);
 
     mActor = nullptr;
   }
 
   bool Explicit() const { return mExplicit; }
 
-  nsresult Init(const LSSnapshotInitInfo& aInitInfo, bool aExplicit);
+  nsresult Init(const nsAString& aKey, const LSSnapshotInitInfo& aInitInfo,
+                bool aExplicit);
 
   nsresult GetLength(uint32_t* aResult);
 
   nsresult GetKey(uint32_t aIndex, nsAString& aResult);
 
   nsresult GetItem(const nsAString& aKey, nsAString& aResult);
 
   nsresult GetKeys(nsTArray<nsString>& aKeys);
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(VoidCString(), 0, false);
+
+  return sVoidLSValue;
+}
+
+}  // namespace dom
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/localstorage/LSValue.h
@@ -0,0 +1,123 @@
+/* -*- 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
+
+#include "SnappyUtils.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 compressed or utf-8 encoding representations. The
+ * LSValue type exists to support these alternate representations, dynamically
+ * decompressing/re-encoding to utf-16 while still tracking value size on a
+ * utf-16 basis for quota purposes.
+ */
+class LSValue final {
+  friend struct IPC::ParamTraits<LSValue>;
+
+  nsCString mBuffer;
+  uint32_t mUTF16Length;
+  bool mCompressed;
+
+ public:
+  LSValue() : mUTF16Length(0), mCompressed(false) {}
+
+  explicit LSValue(const nsACString& aBuffer, uint32_t aUTF16Length,
+                   bool aCompressed)
+      : mBuffer(aBuffer),
+        mUTF16Length(aUTF16Length),
+        mCompressed(aCompressed) {}
+
+  explicit LSValue(const nsAString& aBuffer) : mUTF16Length(aBuffer.Length()) {
+    if (aBuffer.IsVoid()) {
+      mBuffer.SetIsVoid(true);
+      mCompressed = false;
+    } else {
+      CopyUTF16toUTF8(aBuffer, mBuffer);
+      nsCString buffer;
+      if ((mCompressed = SnappyCompress(mBuffer, buffer))) {
+        mBuffer = buffer;
+      }
+    }
+  }
+
+  bool IsVoid() const { return mBuffer.IsVoid(); }
+
+  void SetIsVoid(bool aVal) { mBuffer.SetIsVoid(aVal); }
+
+  /**
+   * This represents the "physical" length that the parent process uses for
+   * the size of value/item computation. This can also be used to see how much
+   * memory the value is using at rest or what the cost is for sending the value
+   * over IPC.
+   */
+  uint32_t Length() const { return mBuffer.Length(); }
+
+  /*
+   * This represents the "logical" length that content sees and that is also
+   * used for quota management purposes.
+   */
+  uint32_t UTF16Length() const { return mUTF16Length; }
+
+  bool IsCompressed() const { return mCompressed; }
+
+  bool Equals(const LSValue& aOther) const {
+    return mBuffer == aOther.mBuffer &&
+           mBuffer.IsVoid() == aOther.mBuffer.IsVoid() &&
+           mUTF16Length == aOther.mUTF16Length &&
+           mCompressed == aOther.mCompressed;
+  }
+
+  bool operator==(const LSValue& aOther) const { return Equals(aOther); }
+
+  bool operator!=(const LSValue& aOther) const { return !Equals(aOther); }
+
+  operator const nsCString&() const { return mBuffer; }
+
+  operator Span<const char>() const { return mBuffer; }
+
+  class Converter {
+    nsString mBuffer;
+
+   public:
+    explicit Converter(const LSValue& aValue) {
+      if (aValue.mBuffer.IsVoid()) {
+        mBuffer.SetIsVoid(true);
+      } else if (aValue.mCompressed) {
+        nsCString buffer;
+        MOZ_ALWAYS_TRUE(SnappyUncompress(aValue.mBuffer, buffer));
+        CopyUTF8toUTF16(buffer, mBuffer);
+      } else {
+        CopyUTF8toUTF16(aValue.mBuffer, mBuffer);
+      }
+    }
+    Converter(Converter&& aOther) : mBuffer(aOther.mBuffer) {}
+    ~Converter() {}
+
+    operator const nsString&() const { return mBuffer; }
+
+   private:
+    Converter() = delete;
+    Converter(const Converter&) = delete;
+    Converter& operator=(const Converter&) = delete;
+    Converter& operator=(const Converter&&) = delete;
+  };
+
+  Converter AsString() const { return Converter(const_cast<LSValue&>(*this)); }
+};
+
+const LSValue& VoidLSValue();
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif  // mozilla_dom_localstorage_LSValue_h
--- a/dom/localstorage/PBackgroundLSDatabase.ipdl
+++ b/dom/localstorage/PBackgroundLSDatabase.ipdl
@@ -18,16 +18,24 @@ namespace dom {
 /**
  * Initial LSSnapshot state as produced by Datastore::GetSnapshotInitInfo.  See
  * `LSSnapshot::LoadState` for more details about the possible states and a
  * high level overview.
  */
 struct LSSnapshotInitInfo
 {
   /**
+   * Boolean indicating whether the `key` provided as an argument to the
+   * PBackgroundLSSnapshot constructor did not exist in the Datastore and should
+   * be treated as an unknown and therefore undefined value. Note that `key` may
+   * have been provided as a void string, in which case this value is forced to
+   * be false.
+   */
+  bool addKeyToUnknownItems;
+  /**
    * As many key/value or key/void pairs as the snapshot prefill byte budget
    * allowed.
    */
   LSItemInfo[] itemInfos;
   /**
    * The total number of key/value pairs in LocalStorage for this origin at the
    * time the snapshot was created.  (And the point of the snapshot is to
    * conceptually freeze the state of the Datastore in time, so this value does
@@ -94,21 +102,30 @@ parent:
    * Datastore state in the parent.
    *
    * This needs to be synchronous because LocalStorage's semantics are
    * synchronous.  Note that the Datastore 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.  Additionally, the response
    * is explicitly bounded in size by the tunable snapshot prefill byte limit.
    *
+   * @param key
+   *   If key is non-void, then the snapshot is being triggered by a direct
+   *   access to a localStorage key (get, set, or removal, with set/removal
+   *   requiring the old value in order to properly populate the "storage"
+   *   event), the key being requested. It's possible the key is not present in
+   *   localStorage, in which case LSSnapshotInitInfo::addKeyToUnknownItems will
+   *   be true indicating that there is no such key/value pair, otherwise it
+   *   will be false.
    * @param increasePeakUsage
    *   Whether the parent should attempt to pre-allocate some amount of quota
    *   usage to the Snapshot.
    */
   sync PBackgroundLSSnapshot(nsString documentURI,
+                             nsString key,
                              bool increasePeakUsage,
                              int64_t requestedSize,
                              int64_t minSize)
     returns (LSSnapshotInitInfo initInfo);
 
 child:
   /**
    * Only sent by the parent in response to the child's DeleteMe request.
--- 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,46 @@
  * 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);
+    WriteParam(aMsg, aParam.mUTF16Length);
+    WriteParam(aMsg, aParam.mCompressed);
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter,
+                   paramType* aResult) {
+    return ReadParam(aMsg, aIter, &aResult->mBuffer) &&
+           ReadParam(aMsg, aIter, &aResult->mUTF16Length) &&
+           ReadParam(aMsg, aIter, &aResult->mCompressed);
+  }
+
+  static void Log(const paramType& aParam, std::wstring* aLog) {
+    LogParam(aParam.mBuffer, aLog);
+    LogParam(aParam.mUTF16Length, aLog);
+    LogParam(aParam.mCompressed, aLog);
+  }
+};
+
 }  // namespace IPC
 
 #endif  // mozilla_dom_localstorage_SerializationHelpers_h
new file mode 100644
--- /dev/null
+++ b/dom/localstorage/SnappyUtils.cpp
@@ -0,0 +1,63 @@
+/* -*- 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 "SnappyUtils.h"
+
+#include "snappy/snappy.h"
+
+namespace mozilla {
+namespace dom {
+
+bool SnappyCompress(const nsACString& aSource, nsACString& aDest) {
+  MOZ_ASSERT(!aSource.IsVoid());
+
+  size_t uncompressedLength = aSource.Length();
+
+  if (uncompressedLength <= 16) {
+    return false;
+  }
+
+  size_t compressedLength = snappy::MaxCompressedLength(uncompressedLength);
+
+  aDest.SetLength(compressedLength);
+
+  snappy::RawCompress(aSource.BeginReading(), uncompressedLength,
+                      aDest.BeginWriting(), &compressedLength);
+
+  if (compressedLength >= uncompressedLength) {
+    return false;
+  }
+
+  aDest.SetLength(compressedLength);
+
+  return true;
+}
+
+bool SnappyUncompress(const nsACString& aSource, nsACString& aDest) {
+  MOZ_ASSERT(!aSource.IsVoid());
+
+  const char* compressed = aSource.BeginReading();
+
+  size_t compressedLength = aSource.Length();
+
+  size_t uncompressedLength;
+  if (!snappy::GetUncompressedLength(compressed, compressedLength,
+                                     &uncompressedLength)) {
+    return false;
+  }
+
+  aDest.SetLength(uncompressedLength);
+
+  if (!snappy::RawUncompress(compressed, compressedLength,
+                             aDest.BeginWriting())) {
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace dom
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/localstorage/SnappyUtils.h
@@ -0,0 +1,20 @@
+/* -*- 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_SnappyUtils_h
+#define mozilla_dom_localstorage_SnappyUtils_h
+
+namespace mozilla {
+namespace dom {
+
+bool SnappyCompress(const nsACString& aSource, nsACString& aDest);
+
+bool SnappyUncompress(const nsACString& aSource, nsACString& aDest);
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif  // mozilla_dom_localstorage_SnappyUtils_h
--- a/dom/localstorage/moz.build
+++ b/dom/localstorage/moz.build
@@ -29,28 +29,32 @@ EXPORTS.mozilla.dom.localstorage += [
 ]
 
 EXPORTS.mozilla.dom += [
     'LocalStorageCommon.h',
     'LocalStorageManager2.h',
     'LSObject.h',
     'LSObserver.h',
     'LSSnapshot.h',
+    'LSValue.h',
+    'SnappyUtils.h',
 ]
 
 UNIFIED_SOURCES += [
     'ActorsChild.cpp',
     'ActorsParent.cpp',
     'LocalStorageCommon.cpp',
     'LocalStorageManager2.cpp',
     'LSDatabase.cpp',
     'LSObject.cpp',
     'LSObserver.cpp',
     'LSSnapshot.cpp',
+    'LSValue.cpp',
     'ReportInternalError.cpp',
+    'SnappyUtils.cpp',
 ]
 
 IPDL_SOURCES += [
     'PBackgroundLSDatabase.ipdl',
     'PBackgroundLSObserver.ipdl',
     'PBackgroundLSRequest.ipdl',
     'PBackgroundLSSharedTypes.ipdlh',
     'PBackgroundLSSimpleRequest.ipdl',
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1ee9bfbf2e0b3f0e825ed25ccf2016c3a00ffe2d
GIT binary patch
literal 4092
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
F{{nXtMhXA`
new file mode 100644
--- /dev/null
+++ b/dom/localstorage/test/unit/test_schema3upgrade.js
@@ -0,0 +1,39 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+async function testSteps() {
+  const url = "http://example.com";
+
+  info("Setting pref");
+
+  Services.prefs.setBoolPref("dom.storage.next_gen", true);
+
+  info("Clearing");
+
+  let request = clear();
+  await requestFinished(request);
+
+  info("Installing package");
+
+  // The profile contains one initialized origin directory with local
+  // storage data, local storage archive, a script for origin initialization,
+  // the storage database and the web apps store database:
+  // - storage/default/http+++example.com
+  // - storage/ls-archive.sqlite
+  // - create_db.js
+  // - storage.sqlite
+  // - webappsstore.sqlite
+  // The file create_db.js in the package was run locally (with a build with
+  // local storage archive version 1 and database schema version 2),
+  // specifically it was temporarily added to xpcshell.ini and then executed:
+  //   mach xpcshell-test --interactive dom/localstorage/test/unit/create_db.js
+  // Note: to make it become the profile in the test, additional manual steps
+  // are needed.
+  // 1. Remove the folder "storage/temporary".
+  installPackage("schema3upgrade_profile");
+
+  let storage = getLocalStorage(getPrincipal(url));
+  storage.open();
+}
--- a/dom/localstorage/test/unit/xpcshell.ini
+++ b/dom/localstorage/test/unit/xpcshell.ini
@@ -4,16 +4,17 @@
 
 [DEFAULT]
 head = head.js
 support-files =
   archive_profile.zip
   corruptedDatabase_profile.zip
   groupMismatch_profile.zip
   migration_profile.zip
+  schema3upgrade_profile.zip
   stringLength2_profile.zip
   stringLength_profile.zip
 
 [test_archive.js]
 [test_corruptedDatabase.js]
 [test_databaseShadowing1.js]
 run-sequentially = test_databaseShadowing2.js depends on a file produced by this test
 [test_databaseShadowing2.js]
@@ -30,13 +31,14 @@ run-sequentially = this test depends on 
 run-sequentially = test_databaseShadowing_clearOriginsByPrefix2.js depends on a file produced by this test
 [test_databaseShadowing_clearOriginsByPrefix2.js]
 run-sequentially = this test depends on a file produced by test_databaseShadowing_clearOriginsByPrefix1.js
 [test_eviction.js]
 [test_groupLimit.js]
 [test_groupMismatch.js]
 [test_migration.js]
 [test_originInit.js]
+[test_schema3upgrade.js]
 [test_snapshotting.js]
 requesttimeoutfactor = 4
 [test_stringLength.js]
 [test_stringLength2.js]
 [test_usage.js]
--- a/dom/quota/ActorsParent.cpp
+++ b/dom/quota/ActorsParent.cpp
@@ -213,17 +213,17 @@ const char kResourceOriginPrefix[] = "re
 #define METADATA_TMP_FILE_NAME ".metadata-tmp"
 #define METADATA_V2_FILE_NAME ".metadata-v2"
 #define METADATA_V2_TMP_FILE_NAME ".metadata-v2-tmp"
 
 #define WEB_APPS_STORE_FILE_NAME "webappsstore.sqlite"
 #define LS_ARCHIVE_FILE_NAME "ls-archive.sqlite"
 #define LS_ARCHIVE_TMP_FILE_NAME "ls-archive-tmp.sqlite"
 
-const uint32_t kLocalStorageArchiveVersion = 1;
+const uint32_t kLocalStorageArchiveVersion = 3;
 
 const char kProfileDoChangeTopic[] = "profile-do-change";
 
 /******************************************************************************
  * SQLite functions
  ******************************************************************************/
 
 int32_t MakeStorageVersion(uint32_t aMajorStorageVersion,
@@ -441,17 +441,16 @@ nsresult LoadLocalStorageArchiveVersion(
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   aVersion = version;
   return NS_OK;
 }
 
-/*
 nsresult SaveLocalStorageArchiveVersion(mozIStorageConnection* aConnection,
                                         uint32_t aVersion) {
   AssertIsOnIOThread();
   MOZ_ASSERT(aConnection);
 
   nsCOMPtr<mozIStorageStatement> stmt;
   nsresult rv = aConnection->CreateStatement(
       NS_LITERAL_CSTRING("UPDATE database SET version = :version;"),
@@ -467,17 +466,16 @@ nsresult SaveLocalStorageArchiveVersion(
 
   rv = stmt->Execute();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
-*/
 
 /******************************************************************************
  * Quota manager class declarations
  ******************************************************************************/
 
 }  // namespace
 
 class DirectoryLockImpl final : public DirectoryLock {
@@ -5256,27 +5254,35 @@ nsresult QuotaManager::UpgradeLocalStora
   rv = InitializeLocalStorageArchive(aConnection, 1);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
-/*
 nsresult QuotaManager::UpgradeLocalStorageArchiveFrom1To2(
     nsCOMPtr<mozIStorageConnection>& aConnection) {
   nsresult rv = SaveLocalStorageArchiveVersion(aConnection, 2);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
-*/
+
+nsresult QuotaManager::UpgradeLocalStorageArchiveFrom2To3(
+    nsCOMPtr<mozIStorageConnection>& aConnection) {
+  nsresult rv = SaveLocalStorageArchiveVersion(aConnection, 3);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
 
 #ifdef DEBUG
 
 void QuotaManager::AssertStorageIsInitialized() const {
   AssertIsOnIOThread();
   MOZ_ASSERT(mStorageInitialized);
 }
 
@@ -5490,27 +5496,28 @@ nsresult QuotaManager::EnsureStorageIsIn
         MOZ_ASSERT(version == 0);
 
         rv = InitializeLocalStorageArchive(connection,
                                            kLocalStorageArchiveVersion);
         if (NS_WARN_IF(NS_FAILED(rv))) {
           return rv;
         }
       } else {
-        static_assert(kLocalStorageArchiveVersion == 1,
+        static_assert(kLocalStorageArchiveVersion == 3,
                       "Upgrade function needed due to LocalStorage archive "
                       "version increase.");
 
         while (version != kLocalStorageArchiveVersion) {
           if (version == 0) {
             rv = UpgradeLocalStorageArchiveFrom0To1(connection);
-          } /* else if (version == 1) {
+          } else if (version == 1) {
             rv = UpgradeLocalStorageArchiveFrom1To2(connection);
-          } */
-          else {
+          } else if (version == 2) {
+            rv = UpgradeLocalStorageArchiveFrom2To3(connection);
+          } else {
             QM_WARNING(
                 "Unable to initialize LocalStorage archive, no upgrade path is "
                 "available!");
             return NS_ERROR_FAILURE;
           }
 
           if (NS_WARN_IF(NS_FAILED(rv))) {
             return rv;
--- a/dom/quota/QuotaManager.h
+++ b/dom/quota/QuotaManager.h
@@ -435,20 +435,21 @@ class QuotaManager final : public Backgr
       nsCOMPtr<mozIStorageConnection>& aConnection);
 
   nsresult DowngradeLocalStorageArchive(
       nsCOMPtr<mozIStorageConnection>& aConnection);
 
   nsresult UpgradeLocalStorageArchiveFrom0To1(
       nsCOMPtr<mozIStorageConnection>& aConnection);
 
-  /*
-    nsresult UpgradeLocalStorageArchiveFrom1To2(
-        nsCOMPtr<mozIStorageConnection>& aConnection);
-  */
+  nsresult UpgradeLocalStorageArchiveFrom1To2(
+      nsCOMPtr<mozIStorageConnection>& aConnection);
+
+  nsresult UpgradeLocalStorageArchiveFrom2To3(
+      nsCOMPtr<mozIStorageConnection>& aConnection);
 
   nsresult InitializeRepository(PersistenceType aPersistenceType);
 
   nsresult InitializeOrigin(PersistenceType aPersistenceType,
                             const nsACString& aGroup, const nsACString& aOrigin,
                             int64_t aAccessTime, bool aPersisted,
                             nsIFile* aDirectory);
 
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..8883ea4630468f1c63076254109d53429752b8ca
GIT binary patch
literal 7792
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
ARsaA1
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e475d3d17540b10922e395f28afeb97a56e07bff
GIT binary patch
literal 7986
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
EKamI<D*ylh
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/unit/test_localStorageArchive2upgrade.js
@@ -0,0 +1,65 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify that local storage directories are not removed
+ * during local storage archive upgrade from version 1 to version 2.
+ * See bug 1546310.
+ */
+
+async function testSteps() {
+  const lsDirs = [
+    "storage/default/http+++example.com/ls",
+    "storage/default/http+++localhost/ls",
+    "storage/default/http+++www.mozilla.org/ls",
+  ];
+
+  info("Clearing");
+
+  let request = clear();
+  await requestFinished(request);
+
+  info("Installing package");
+
+  // The profile contains three initialized origin directories with local
+  // storage data, local storage archive, a script for origin initialization,
+  // the storage database and the web apps store database:
+  // - storage/default/https+++example.com
+  // - storage/default/https+++localhost
+  // - storage/default/https+++www.mozilla.org
+  // - storage/ls-archive.sqlite
+  // - create_db.js
+  // - storage.sqlite
+  // - webappsstore.sqlite
+  // The file create_db.js in the package was run locally (with a build with
+  // local storage archive version 1), specifically it was temporarily added to
+  // xpcshell.ini and then executed:
+  //   mach xpcshell-test --interactive dom/localstorage/test/unit/create_db.js
+  // Note: to make it become the profile in the test, additional manual steps
+  // are needed.
+  // 1. Remove the folder "storage/temporary".
+  installPackage("localStorageArchive2upgrade_profile");
+
+  info("Checking ls dirs");
+
+  for (let lsDir of lsDirs) {
+    let dir = getRelativeFile(lsDir);
+
+    exists = dir.exists();
+    ok(exists, "ls directory does exist");
+  }
+
+  request = init();
+  request = await requestFinished(request);
+
+  info("Checking ls dirs");
+
+  for (let lsDir of lsDirs) {
+    let dir = getRelativeFile(lsDir);
+
+    exists = dir.exists();
+    ok(exists, "ls directory does exist");
+  }
+}
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/unit/test_localStorageArchive3upgrade.js
@@ -0,0 +1,65 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify that local storage directories are not removed
+ * during local storage archive upgrade from version 2 to version 3.
+ * See bug 1513937.
+ */
+
+async function testSteps() {
+  const lsDirs = [
+    "storage/default/http+++example.com/ls",
+    "storage/default/http+++localhost/ls",
+    "storage/default/http+++www.mozilla.org/ls",
+  ];
+
+  info("Clearing");
+
+  let request = clear();
+  await requestFinished(request);
+
+  info("Installing package");
+
+  // The profile contains three initialized origin directories with local
+  // storage data, local storage archive, a script for origin initialization,
+  // the storage database and the web apps store database:
+  // - storage/default/https+++example.com
+  // - storage/default/https+++localhost
+  // - storage/default/https+++www.mozilla.org
+  // - storage/ls-archive.sqlite
+  // - create_db.js
+  // - storage.sqlite
+  // - webappsstore.sqlite
+  // The file create_db.js in the package was run locally (with a build with
+  // local storage archive version 2), specifically it was temporarily added to
+  // xpcshell.ini and then executed:
+  //   mach xpcshell-test --interactive dom/localstorage/test/unit/create_db.js
+  // Note: to make it become the profile in the test, additional manual steps
+  // are needed.
+  // 1. Remove the folder "storage/temporary".
+  installPackage("localStorageArchive3upgrade_profile");
+
+  info("Checking ls dirs");
+
+  for (let lsDir of lsDirs) {
+    let dir = getRelativeFile(lsDir);
+
+    exists = dir.exists();
+    ok(exists, "ls directory does exist");
+  }
+
+  request = init();
+  request = await requestFinished(request);
+
+  info("Checking ls dirs");
+
+  for (let lsDir of lsDirs) {
+    let dir = getRelativeFile(lsDir);
+
+    exists = dir.exists();
+    ok(exists, "ls directory does exist");
+  }
+}
--- a/dom/quota/test/unit/xpcshell.ini
+++ b/dom/quota/test/unit/xpcshell.ini
@@ -9,16 +9,18 @@ support-files =
   clearStorageForPrincipal_profile.zip
   createLocalStorage_profile.zip
   defaultStorageUpgrade_profile.zip
   getUsage_profile.zip
   groupMismatch_profile.zip
   idbSubdirUpgrade1_profile.zip
   idbSubdirUpgrade2_profile.zip
   localStorageArchive1upgrade_profile.zip
+  localStorageArchive2upgrade_profile.zip
+  localStorageArchive3upgrade_profile.zip
   localStorageArchiveDowngrade_profile.zip
   morgueCleanup_profile.zip
   obsoleteOriginAttributes_profile.zip
   originAttributesUpgrade_profile.zip
   removeLocalStorage1_profile.zip
   removeLocalStorage2_profile.zip
   storagePersistentUpgrade_profile.zip
   tempMetadataCleanup_profile.zip
@@ -31,16 +33,18 @@ support-files =
 [test_clearStorageForPrincipal.js]
 [test_defaultStorageUpgrade.js]
 [test_getUsage.js]
 [test_groupMismatch.js]
 [test_idbSubdirUpgrade.js]
 [test_initTemporaryStorage.js]
 [test_listInitializedOrigins.js]
 [test_localStorageArchive1upgrade.js]
+[test_localStorageArchive2upgrade.js]
+[test_localStorageArchive3upgrade.js]
 [test_localStorageArchiveDowngrade.js]
 [test_morgueCleanup.js]
 [test_obsoleteOriginAttributesUpgrade.js]
 [test_obsoleteOrigins.js]
 [test_originAttributesUpgrade.js]
 [test_persist.js]
 [test_persist_eviction.js]
 [test_persist_globalLimit.js]
--- a/dom/workers/ScriptLoader.cpp
+++ b/dom/workers/ScriptLoader.cpp
@@ -544,37 +544,41 @@ NS_IMPL_ISUPPORTS(LoaderListener, nsIStr
 
 class ScriptLoaderRunnable final : public nsIRunnable, public nsINamed {
   friend class ScriptExecutorRunnable;
   friend class CachePromiseHandler;
   friend class CacheScriptLoader;
   friend class LoaderListener;
 
   WorkerPrivate* mWorkerPrivate;
+  UniquePtr<SerializedStackHolder> mOriginStack;
+  nsString mOriginStackJSON;
   nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
   nsTArray<ScriptLoadInfo> mLoadInfos;
   RefPtr<CacheCreator> mCacheCreator;
   Maybe<ClientInfo> mClientInfo;
   Maybe<ServiceWorkerDescriptor> mController;
   bool mIsMainScript;
   WorkerScriptType mWorkerScriptType;
   bool mCanceledMainThread;
   ErrorResult& mRv;
 
  public:
   NS_DECL_THREADSAFE_ISUPPORTS
 
   ScriptLoaderRunnable(WorkerPrivate* aWorkerPrivate,
+                       UniquePtr<SerializedStackHolder> aOriginStack,
                        nsIEventTarget* aSyncLoopTarget,
                        nsTArray<ScriptLoadInfo>& aLoadInfos,
                        const Maybe<ClientInfo>& aClientInfo,
                        const Maybe<ServiceWorkerDescriptor>& aController,
                        bool aIsMainScript, WorkerScriptType aWorkerScriptType,
                        ErrorResult& aRv)
       : mWorkerPrivate(aWorkerPrivate),
+        mOriginStack(std::move(aOriginStack)),
         mSyncLoopTarget(aSyncLoopTarget),
         mClientInfo(aClientInfo),
         mController(aController),
         mIsMainScript(aIsMainScript),
         mWorkerScriptType(aWorkerScriptType),
         mCanceledMainThread(false),
         mRv(aRv) {
     aWorkerPrivate->AssertIsOnWorkerThread();
@@ -831,16 +835,24 @@ class ScriptLoaderRunnable final : publi
 
   nsresult RunInternal() {
     AssertIsOnMainThread();
 
     if (IsMainWorkerScript()) {
       mWorkerPrivate->SetLoadingWorkerScript(true);
     }
 
+    // Convert the origin stack to JSON (which must be done on the main
+    // thread) explicitly, so that we can use the stack to notify the net
+    // monitor about every script we load.
+    if (mOriginStack) {
+      ConvertSerializedStackToJSON(std::move(mOriginStack),
+                                   mOriginStackJSON);
+    }
+
     if (!mWorkerPrivate->IsServiceWorker() || IsDebuggerScript()) {
       for (uint32_t index = 0, len = mLoadInfos.Length(); index < len;
            ++index) {
         nsresult rv = LoadScript(index);
         if (NS_WARN_IF(NS_FAILED(rv))) {
           LoadingFinished(index, rv);
           return rv;
         }
@@ -957,16 +969,21 @@ class ScriptLoaderRunnable final : publi
           mClientInfo, mController, IsMainWorkerScript(), mWorkerScriptType,
           mWorkerPrivate->ContentPolicyType(), loadFlags,
           mWorkerPrivate->CookieSettings(), getter_AddRefs(channel));
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     }
 
+    // Associate any originating stack with the channel.
+    if (!mOriginStackJSON.IsEmpty()) {
+      NotifyNetworkMonitorAlternateStack(channel, mOriginStackJSON);
+    }
+
     // We need to know which index we're on in OnStreamComplete so we know
     // where to put the result.
     RefPtr<LoaderListener> listener = new LoaderListener(this, aIndex);
 
     // We don't care about progress so just use the simple stream loader for
     // OnStreamComplete notification only.
     nsCOMPtr<nsIStreamLoader> loader;
     rv = NS_NewStreamLoader(getter_AddRefs(loader), listener);
@@ -2076,16 +2093,17 @@ void ScriptExecutorRunnable::LogExceptio
   xpcReport->Init(report.report(), report.toStringResult().c_str(),
                   aWorkerPrivate->IsChromeWorker(), aWorkerPrivate->WindowID());
 
   RefPtr<AsyncErrorReporter> r = new AsyncErrorReporter(xpcReport);
   NS_DispatchToMainThread(r);
 }
 
 void LoadAllScripts(WorkerPrivate* aWorkerPrivate,
+                    UniquePtr<SerializedStackHolder> aOriginStack,
                     nsTArray<ScriptLoadInfo>& aLoadInfos, bool aIsMainScript,
                     WorkerScriptType aWorkerScriptType, ErrorResult& aRv) {
   aWorkerPrivate->AssertIsOnWorkerThread();
   NS_ASSERTION(!aLoadInfos.IsEmpty(), "Bad arguments!");
 
   AutoSyncLoopHolder syncLoop(aWorkerPrivate, Canceling);
   nsCOMPtr<nsIEventTarget> syncLoopTarget = syncLoop.GetEventTarget();
   if (!syncLoopTarget) {
@@ -2096,18 +2114,18 @@ void LoadAllScripts(WorkerPrivate* aWork
   Maybe<ClientInfo> clientInfo;
   Maybe<ServiceWorkerDescriptor> controller;
   if (!aIsMainScript) {
     clientInfo = aWorkerPrivate->GetClientInfo();
     controller = aWorkerPrivate->GetController();
   }
 
   RefPtr<ScriptLoaderRunnable> loader = new ScriptLoaderRunnable(
-      aWorkerPrivate, syncLoopTarget, aLoadInfos, clientInfo, controller,
-      aIsMainScript, aWorkerScriptType, aRv);
+      aWorkerPrivate, std::move(aOriginStack), syncLoopTarget, aLoadInfos, clientInfo,
+      controller, aIsMainScript, aWorkerScriptType, aRv);
 
   NS_ASSERTION(aLoadInfos.IsEmpty(), "Should have swapped!");
 
   RefPtr<StrongWorkerRef> workerRef =
       StrongWorkerRef::Create(aWorkerPrivate, "ScriptLoader", [loader]() {
         NS_DispatchToMainThread(NewRunnableMethod(
             "ScriptLoader::CancelMainThreadWithBindingAborted", loader,
             &ScriptLoaderRunnable::CancelMainThreadWithBindingAborted));
@@ -2217,32 +2235,37 @@ void ReportLoadError(ErrorResult& aRv, n
   }
 
   aRv.ThrowDOMException(
       aLoadResult, NS_LITERAL_CSTRING("Failed to load worker script at \"") +
                        NS_ConvertUTF16toUTF8(aScriptURL) +
                        NS_LITERAL_CSTRING("\""));
 }
 
-void LoadMainScript(WorkerPrivate* aWorkerPrivate, const nsAString& aScriptURL,
+void LoadMainScript(WorkerPrivate* aWorkerPrivate,
+                    UniquePtr<SerializedStackHolder> aOriginStack,
+                    const nsAString& aScriptURL,
                     WorkerScriptType aWorkerScriptType, ErrorResult& aRv) {
   nsTArray<ScriptLoadInfo> loadInfos;
 
   ScriptLoadInfo* info = loadInfos.AppendElement();
   info->mURL = aScriptURL;
   info->mLoadFlags = aWorkerPrivate->GetLoadFlags();
 
   // We are loading the main script, so the worker's Client must be
   // reserved.
   info->mReservedClientInfo = aWorkerPrivate->GetClientInfo();
 
-  LoadAllScripts(aWorkerPrivate, loadInfos, true, aWorkerScriptType, aRv);
+  LoadAllScripts(aWorkerPrivate, std::move(aOriginStack), loadInfos, true,
+                 aWorkerScriptType, aRv);
 }
 
-void Load(WorkerPrivate* aWorkerPrivate, const nsTArray<nsString>& aScriptURLs,
+void Load(WorkerPrivate* aWorkerPrivate,
+          UniquePtr<SerializedStackHolder> aOriginStack,
+          const nsTArray<nsString>& aScriptURLs,
           WorkerScriptType aWorkerScriptType, ErrorResult& aRv) {
   const uint32_t urlCount = aScriptURLs.Length();
 
   if (!urlCount) {
     return;
   }
 
   if (urlCount > MAX_CONCURRENT_SCRIPTS) {
@@ -2253,15 +2276,16 @@ void Load(WorkerPrivate* aWorkerPrivate,
   nsTArray<ScriptLoadInfo> loadInfos;
   loadInfos.SetLength(urlCount);
 
   for (uint32_t index = 0; index < urlCount; index++) {
     loadInfos[index].mURL = aScriptURLs[index];
     loadInfos[index].mLoadFlags = aWorkerPrivate->GetLoadFlags();
   }
 
-  LoadAllScripts(aWorkerPrivate, loadInfos, false, aWorkerScriptType, aRv);
+  LoadAllScripts(aWorkerPrivate, std::move(aOriginStack), loadInfos, false,
+                 aWorkerScriptType, aRv);
 }
 
 }  // namespace workerinternals
 
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/workers/ScriptLoader.h
+++ b/dom/workers/ScriptLoader.h
@@ -21,16 +21,17 @@ class nsICookieSettings;
 namespace mozilla {
 
 class ErrorResult;
 
 namespace dom {
 
 struct WorkerLoadInfo;
 class WorkerPrivate;
+class SerializedStackHolder;
 
 enum WorkerScriptType { WorkerScript, DebuggerScript };
 
 namespace workerinternals {
 
 nsresult ChannelFromScriptURLMainThread(
     nsIPrincipal* aPrincipal, Document* aParentDoc, nsILoadGroup* aLoadGroup,
     nsIURI* aScriptURL, const Maybe<ClientInfo>& aClientInfo,
@@ -40,20 +41,24 @@ nsresult ChannelFromScriptURLMainThread(
 nsresult ChannelFromScriptURLWorkerThread(JSContext* aCx,
                                           WorkerPrivate* aParent,
                                           const nsAString& aScriptURL,
                                           WorkerLoadInfo& aLoadInfo);
 
 void ReportLoadError(ErrorResult& aRv, nsresult aLoadResult,
                      const nsAString& aScriptURL);
 
-void LoadMainScript(WorkerPrivate* aWorkerPrivate, const nsAString& aScriptURL,
+void LoadMainScript(WorkerPrivate* aWorkerPrivate,
+                    UniquePtr<SerializedStackHolder> aOriginStack,
+                    const nsAString& aScriptURL,
                     WorkerScriptType aWorkerScriptType, ErrorResult& aRv);
 
-void Load(WorkerPrivate* aWorkerPrivate, const nsTArray<nsString>& aScriptURLs,
+void Load(WorkerPrivate* aWorkerPrivate,
+          UniquePtr<SerializedStackHolder> aOriginStack,
+          const nsTArray<nsString>& aScriptURLs,
           WorkerScriptType aWorkerScriptType, ErrorResult& aRv);
 
 }  // namespace workerinternals
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif /* mozilla_dom_workers_scriptloader_h__ */
--- a/dom/workers/WorkerDebugger.cpp
+++ b/dom/workers/WorkerDebugger.cpp
@@ -98,18 +98,18 @@ class CompileDebuggerScriptRunnable fina
     if (mozilla::StaticPrefs::dom_performance_enable_scheduler_timing()) {
       aWorkerPrivate->EnsurePerformanceCounter();
     }
 
     JS::Rooted<JSObject*> global(aCx, globalScope->GetWrapper());
 
     ErrorResult rv;
     JSAutoRealm ar(aCx, global);
-    workerinternals::LoadMainScript(aWorkerPrivate, mScriptURL, DebuggerScript,
-                                    rv);
+    workerinternals::LoadMainScript(aWorkerPrivate, nullptr, mScriptURL,
+                                    DebuggerScript, rv);
     rv.WouldReportJSException();
     // Explicitly ignore NS_BINDING_ABORTED on rv.  Or more precisely, still
     // return false and don't SetWorkerScriptExecutedSuccessfully() in that
     // case, but don't throw anything on aCx.  The idea is to not dispatch error
     // events if our load is canceled with that error code.
     if (rv.ErrorCodeIs(NS_BINDING_ABORTED)) {
       rv.SuppressException();
       return false;
@@ -450,17 +450,17 @@ void WorkerDebugger::ReportErrorToDebugg
   }
 
   // We need a JSContext to be able to read any stack associated with the error.
   // This will not run any scripts.
   AutoJSAPI jsapi;
   DebugOnly<bool> ok = jsapi.Init(xpc::UnprivilegedJunkScope());
   MOZ_ASSERT(ok, "UnprivilegedJunkScope should exist");
 
-  WorkerErrorReport report(nullptr);
+  WorkerErrorReport report;
   report.mMessage = aMessage;
   report.mFilename = aFilename;
   WorkerErrorReport::LogErrorToConsole(jsapi.cx(), report, 0);
 }
 
 RefPtr<PerformanceInfoPromise> WorkerDebugger::ReportPerformanceInfo() {
   AssertIsOnMainThread();
   nsCOMPtr<nsPIDOMWindowOuter> top;
--- a/dom/workers/WorkerError.cpp
+++ b/dom/workers/WorkerError.cpp
@@ -194,30 +194,18 @@ void WorkerErrorBase::AssignErrorBase(JS
   mErrorNumber = aReport->errorNumber;
 }
 
 void WorkerErrorNote::AssignErrorNote(JSErrorNotes::Note* aNote) {
   WorkerErrorBase::AssignErrorBase(aNote);
   xpc::ErrorNote::ErrorNoteToMessageString(aNote, mMessage);
 }
 
-WorkerErrorReport::WorkerErrorReport(WorkerPrivate* aWorkerPrivate)
-    : StructuredCloneHolder(CloningSupported, TransferringNotSupported,
-                            StructuredCloneScope::SameProcessDifferentThread),
-      mFlags(0),
-      mExnType(JSEXN_ERR),
-      mMutedError(false) {
-  if (aWorkerPrivate) {
-    RefPtr<StrongWorkerRef> workerRef =
-        StrongWorkerRef::Create(aWorkerPrivate, "WorkerErrorReport");
-    if (workerRef) {
-      mWorkerRef = new ThreadSafeWorkerRef(workerRef);
-    }
-  }
-}
+WorkerErrorReport::WorkerErrorReport()
+    : mFlags(0), mExnType(JSEXN_ERR), mMutedError(false) {}
 
 void WorkerErrorReport::AssignErrorReport(JSErrorReport* aReport) {
   WorkerErrorBase::AssignErrorBase(aReport);
   xpc::ErrorReport::ErrorReportToMessageString(aReport, mMessage);
 
   mLine.Assign(aReport->linebuf(), aReport->linebufLength());
   mFlags = aReport->flags;
   MOZ_ASSERT(aReport->exnType >= JSEXN_FIRST && aReport->exnType < JSEXN_LIMIT);
@@ -366,31 +354,18 @@ void WorkerErrorReport::LogErrorToConsol
                                           uint64_t aInnerWindowId) {
   nsTArray<ErrorDataNote> notes;
   for (size_t i = 0, len = aReport.mNotes.Length(); i < len; i++) {
     const WorkerErrorNote& note = aReport.mNotes.ElementAt(i);
     notes.AppendElement(ErrorDataNote(note.mLineNumber, note.mColumnNumber,
                                       note.mMessage, note.mFilename));
   }
 
-  // Read any stack associated with the report.
-  JS::RootedValue stackValue(aCx);
-  if (aReport.HasData() && aReport.mWorkerRef) {
-    nsIPrincipal* principal = aReport.mWorkerRef->Private()->GetPrincipal();
-    nsJSPrincipals::AutoSetActiveWorkerPrincipal set(principal);
-    aReport.Read(xpc::CurrentNativeGlobal(aCx), aCx, &stackValue,
-                 IgnoreErrors());
-  }
-  JS::RootedObject stack(aCx);
-  JS::RootedObject stackGlobal(aCx);
-  if (stackValue.isObject()) {
-    stack = &stackValue.toObject();
-    stackGlobal = JS::CurrentGlobalOrNull(aCx);
-    MOZ_ASSERT(stackGlobal);
-  }
+  JS::RootedObject stack(aCx, aReport.ReadStack(aCx));
+  JS::RootedObject stackGlobal(aCx, JS::CurrentGlobalOrNull(aCx));
 
   ErrorData errorData(aReport.mLineNumber, aReport.mColumnNumber,
                       aReport.mFlags, aReport.mMessage, aReport.mFilename,
                       aReport.mLine, notes);
   LogErrorToConsole(errorData, aInnerWindowId, stack, stackGlobal);
 }
 
 /* static */
--- a/dom/workers/WorkerError.h
+++ b/dom/workers/WorkerError.h
@@ -2,19 +2,19 @@
 /* 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_workers_WorkerError_h
 #define mozilla_dom_workers_WorkerError_h
 
+#include "mozilla/dom/SerializedStackHolder.h"
 #include "mozilla/dom/WorkerCommon.h"
 #include "jsapi.h"
-#include "WorkerRef.h"
 
 namespace mozilla {
 
 class DOMEventTargetHelper;
 
 namespace dom {
 
 class ErrorData;
@@ -33,33 +33,25 @@ class WorkerErrorBase {
 
 class WorkerErrorNote : public WorkerErrorBase {
  public:
   void AssignErrorNote(JSErrorNotes::Note* aNote);
 };
 
 class WorkerPrivate;
 
-// The StructuredCloneHolder superclass is used to encode the error's stack
-// data, if there is any.
-class WorkerErrorReport : public WorkerErrorBase, public StructuredCloneHolder {
+class WorkerErrorReport : public WorkerErrorBase, public SerializedStackHolder {
  public:
   nsString mLine;
   uint32_t mFlags;
   JSExnType mExnType;
   bool mMutedError;
   nsTArray<WorkerErrorNote> mNotes;
 
-  // Hold a reference on the originating worker until the error has been
-  // processed.
-  RefPtr<ThreadSafeWorkerRef> mWorkerRef;
-
-  // Create a new error report. aWorkerPrivate represents the worker where the
-  // error originated.
-  explicit WorkerErrorReport(WorkerPrivate* aWorkerPrivate);
+  WorkerErrorReport();
 
   void AssignErrorReport(JSErrorReport* aReport);
 
   // aWorkerPrivate is the worker thread we're on (or the main thread, if null)
   // aTarget is the worker object that we are going to fire an error at
   // (if any).
   static void ReportError(
       JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aFireAtScope,
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -297,22 +297,24 @@ class ModifyBusyCountRunnable final : pu
     }
     // Don't do anything here as it's possible that aWorkerPrivate has been
     // deleted.
   }
 };
 
 class CompileScriptRunnable final : public WorkerDebuggeeRunnable {
   nsString mScriptURL;
+  UniquePtr<SerializedStackHolder> mOriginStack;
 
  public:
   explicit CompileScriptRunnable(WorkerPrivate* aWorkerPrivate,
+                                 UniquePtr<SerializedStackHolder> aOriginStack,
                                  const nsAString& aScriptURL)
       : WorkerDebuggeeRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount),
-        mScriptURL(aScriptURL) {}
+        mScriptURL(aScriptURL), mOriginStack(aOriginStack.release()) {}
 
  private:
   // We can't implement PreRun effectively, because at the point when that would
   // run we have not yet done our load so don't know things like our final
   // principal and whatnot.
 
   virtual bool WorkerRun(JSContext* aCx,
                          WorkerPrivate* aWorkerPrivate) override {
@@ -332,18 +334,18 @@ class CompileScriptRunnable final : publ
     // content loading.
     aWorkerPrivate->EnsurePerformanceStorage();
 
     if (mozilla::StaticPrefs::dom_performance_enable_scheduler_timing()) {
       aWorkerPrivate->EnsurePerformanceCounter();
     }
 
     ErrorResult rv;
-    workerinternals::LoadMainScript(aWorkerPrivate, mScriptURL, WorkerScript,
-                                    rv);
+    workerinternals::LoadMainScript(aWorkerPrivate, std::move(mOriginStack),
+                                    mScriptURL, WorkerScript, rv);
     rv.WouldReportJSException();
     // Explicitly ignore NS_BINDING_ABORTED on rv.  Or more precisely, still
     // return false and don't SetWorkerScriptExecutedSuccessfully() in that
     // case, but don't throw anything on aCx.  The idea is to not dispatch error
     // events if our load is canceled with that error code.
     if (rv.ErrorCodeIs(NS_BINDING_ABORTED)) {
       rv.SuppressException();
       return false;
@@ -2251,18 +2253,20 @@ already_AddRefed<WorkerPrivate> WorkerPr
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   worker->EnableDebugger();
 
   MOZ_DIAGNOSTIC_ASSERT(worker->PrincipalIsValid());
 
+  UniquePtr<SerializedStackHolder> stack = GetCurrentStackForNetMonitor(aCx);
+
   RefPtr<CompileScriptRunnable> compiler =
-      new CompileScriptRunnable(worker, aScriptURL);
+      new CompileScriptRunnable(worker, std::move(stack), aScriptURL);
   if (!compiler->Dispatch()) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   worker->mSelfRef = worker;
 
   return worker.forget();
@@ -4048,30 +4052,29 @@ void WorkerPrivate::ReportError(JSContex
   if (!JS_GetPendingException(aCx, &exn)) {
     // Probably shouldn't actually happen?  But let's go ahead and just use null
     // for lack of anything better.
     exn.setNull();
   }
   JS::RootedObject exnStack(aCx, JS::GetPendingExceptionStack(aCx));
   JS_ClearPendingException(aCx);
 
-  UniquePtr<WorkerErrorReport> report = MakeUnique<WorkerErrorReport>(this);
+  UniquePtr<WorkerErrorReport> report = MakeUnique<WorkerErrorReport>();
   if (aReport) {
     report->AssignErrorReport(aReport);
   } else {
     report->mFlags = nsIScriptError::errorFlag | nsIScriptError::exceptionFlag;
   }
 
   JS::RootedObject stack(aCx), stackGlobal(aCx);
   xpc::FindExceptionStackForConsoleReport(nullptr, exn, exnStack, &stack,
                                           &stackGlobal);
 
   if (stack) {
-    JS::RootedValue stackValue(aCx, JS::ObjectValue(*stack));
-    report->Write(aCx, stackValue, IgnoreErrors());
+    report->SerializeWorkerStack(aCx, this, stack);
   }
 
   if (report->mMessage.IsEmpty() && aToStringResult) {
     nsDependentCString toStringResult(aToStringResult.c_str());
     if (!AppendUTF8toUTF16(toStringResult, report->mMessage,
                            mozilla::fallible)) {
       // Try again, with only a 1 KB string. Do this infallibly this time.
       // If the user doesn't have 1 KB to spare we're done anyways.
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -230,20 +230,25 @@ void WorkerGlobalScope::SetOnerror(OnErr
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   EventListenerManager* elm = GetOrCreateListenerManager();
   if (elm) {
     elm->SetEventHandler(aHandler);
   }
 }
 
-void WorkerGlobalScope::ImportScripts(const Sequence<nsString>& aScriptURLs,
+void WorkerGlobalScope::ImportScripts(JSContext* aCx,
+                                      const Sequence<nsString>& aScriptURLs,
                                       ErrorResult& aRv) {
   mWorkerPrivate->AssertIsOnWorkerThread();
-  workerinternals::Load(mWorkerPrivate, aScriptURLs, WorkerScript, aRv);
+
+  UniquePtr<SerializedStackHolder> stack = GetCurrentStackForNetMonitor(aCx);
+
+  workerinternals::Load(mWorkerPrivate, std::move(stack), aScriptURLs,
+                        WorkerScript, aRv);
 }
 
 int32_t WorkerGlobalScope::SetTimeout(JSContext* aCx, Function& aHandler,
                                       const int32_t aTimeout,
                                       const Sequence<JS::Value>& aArguments,
                                       ErrorResult& aRv) {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
@@ -901,17 +906,17 @@ void WorkerDebuggerGlobalScope::LoadSubS
       return;
     }
 
     ar.emplace(aCx, sandbox);
   }
 
   nsTArray<nsString> urls;
   urls.AppendElement(aURL);
-  workerinternals::Load(mWorkerPrivate, urls, DebuggerScript, aRv);
+  workerinternals::Load(mWorkerPrivate, nullptr, urls, DebuggerScript, aRv);
 }
 
 void WorkerDebuggerGlobalScope::EnterEventLoop() {
   // We're on the worker thread here, and WorkerPrivate's refcounting is
   // non-threadsafe: you can only do it on the parent thread.  What that
   // means in practice is that we're relying on it being kept alive while
   // we run.  Hopefully.
   MOZ_KnownLive(mWorkerPrivate)->EnterDebuggerEventLoop();
--- a/dom/workers/WorkerScope.h
+++ b/dom/workers/WorkerScope.h
@@ -100,17 +100,18 @@ class WorkerGlobalScope : public DOMEven
 
   already_AddRefed<WorkerNavigator> Navigator();
 
   already_AddRefed<WorkerNavigator> GetExistingNavigator() const;
 
   OnErrorEventHandlerNonNull* GetOnerror();
   void SetOnerror(OnErrorEventHandlerNonNull* aHandler);
 
-  void ImportScripts(const Sequence<nsString>& aScriptURLs, ErrorResult& aRv);
+  void ImportScripts(JSContext* aCx, const Sequence<nsString>& aScriptURLs,
+                     ErrorResult& aRv);
 
   int32_t SetTimeout(JSContext* aCx, Function& aHandler, const int32_t aTimeout,
                      const Sequence<JS::Value>& aArguments, ErrorResult& aRv);
   int32_t SetTimeout(JSContext* aCx, const nsAString& aHandler,
                      const int32_t aTimeout,
                      const Sequence<JS::Value>& /* unused */, ErrorResult& aRv);
   void ClearTimeout(int32_t aHandle);
   int32_t SetInterval(JSContext* aCx, Function& aHandler,
--- a/gfx/wr/webrender/res/brush.glsl
+++ b/gfx/wr/webrender/res/brush.glsl
@@ -19,17 +19,16 @@ void brush_vs(
 
 #define VECS_PER_SEGMENT                    2
 
 #define BRUSH_FLAG_PERSPECTIVE_INTERPOLATION    1
 #define BRUSH_FLAG_SEGMENT_RELATIVE             2
 #define BRUSH_FLAG_SEGMENT_REPEAT_X             4
 #define BRUSH_FLAG_SEGMENT_REPEAT_Y             8
 #define BRUSH_FLAG_TEXEL_RECT                  16
-#define BRUSH_FLAG_SNAP_TO_PRIMITIVE           32
 
 #define INVALID_SEGMENT_INDEX                   0xffff
 
 void main(void) {
     // Load the brush instance from vertex attributes.
     int prim_header_address = aData.x;
     int clip_address = aData.y;
     int segment_index = aData.z & 0xffff;
@@ -60,25 +59,24 @@ void main(void) {
     // Fetch the dynamic picture that we are drawing on.
     PictureTask pic_task = fetch_picture_task(ph.render_task_index);
     ClipArea clip_area = fetch_clip_area(clip_address);
 
     Transform transform = fetch_transform(ph.transform_id);
 
     // Write the normal vertex information out.
     if (transform.is_axis_aligned) {
-        bool snap_to_primitive = (brush_flags & BRUSH_FLAG_SNAP_TO_PRIMITIVE) != 0;
         vi = write_vertex(
             segment_rect,
             ph.local_clip_rect,
             ph.z,
             transform,
             pic_task,
             ph.local_rect,
-            snap_to_primitive
+            ph.snap_offsets
         );
 
         // TODO(gw): transform bounds may be referenced by
         //           the fragment shader when running in
         //           the alpha pass, even on non-transformed
         //           items. For now, just ensure it has no
         //           effect. We can tidy this up as we move
         //           more items to be brush shaders.
--- a/gfx/wr/webrender/res/prim_shared.glsl
+++ b/gfx/wr/webrender/res/prim_shared.glsl
@@ -42,35 +42,37 @@ varying vec4 vClipMaskUv;
 #define COLOR_MODE_IMAGE              9
 
 uniform HIGHP_SAMPLER_FLOAT sampler2D sPrimitiveHeadersF;
 uniform HIGHP_SAMPLER_FLOAT isampler2D sPrimitiveHeadersI;
 
 // Instanced attributes
 in ivec4 aData;
 
-#define VECS_PER_PRIM_HEADER_F 2U
+#define VECS_PER_PRIM_HEADER_F 3U
 #define VECS_PER_PRIM_HEADER_I 2U
 
 struct PrimitiveHeader {
     RectWithSize local_rect;
     RectWithSize local_clip_rect;
+    vec4 snap_offsets;
     float z;
     int specific_prim_address;
     int render_task_index;
     int transform_id;
     ivec4 user_data;
 };
 
 PrimitiveHeader fetch_prim_header(int index) {
     PrimitiveHeader ph;
 
     ivec2 uv_f = get_fetch_uv(index, VECS_PER_PRIM_HEADER_F);
     vec4 local_rect = TEXEL_FETCH(sPrimitiveHeadersF, uv_f, 0, ivec2(0, 0));
     vec4 local_clip_rect = TEXEL_FETCH(sPrimitiveHeadersF, uv_f, 0, ivec2(1, 0));
+    ph.snap_offsets = TEXEL_FETCH(sPrimitiveHeadersF, uv_f, 0, ivec2(2, 0));
     ph.local_rect = RectWithSize(local_rect.xy, local_rect.zw);
     ph.local_clip_rect = RectWithSize(local_clip_rect.xy, local_clip_rect.zw);
 
     ivec2 uv_i = get_fetch_uv(index, VECS_PER_PRIM_HEADER_I);
     ivec4 data0 = TEXEL_FETCH(sPrimitiveHeadersI, uv_i, 0, ivec2(0, 0));
     ivec4 data1 = TEXEL_FETCH(sPrimitiveHeadersI, uv_i, 0, ivec2(1, 0));
     ph.z = float(data0.x);
     ph.render_task_index = data0.y;
@@ -88,41 +90,29 @@ struct VertexInfo {
 };
 
 VertexInfo write_vertex(RectWithSize instance_rect,
                         RectWithSize local_clip_rect,
                         float z,
                         Transform transform,
                         PictureTask task,
                         RectWithSize snap_rect,
-                        bool snap_to_primitive) {
+                        vec4 snap_offsets) {
 
     // Select the corner of the local rect that we are processing.
     vec2 local_pos = instance_rect.p0 + instance_rect.size * aPosition.xy;
 
     // Clamp to the two local clip rects.
     vec2 clamped_local_pos = clamp_rect(local_pos, local_clip_rect);
 
-    RectWithSize visible_rect;
-    if (snap_to_primitive) {
-        // We are producing a picture. The snap rect has already taken into
-        // account the clipping we wish to consider for snapping purposes.
-        visible_rect = snap_rect;
-    } else {
-        // Compute the visible rect to snap against. This ensures segments along
-        // the edges are snapped consistently with other nearby primitives.
-        visible_rect = intersect_rects(local_clip_rect, snap_rect);
-    }
-
     /// Compute the snapping offset.
     vec2 snap_offset = compute_snap_offset(
         clamped_local_pos,
-        transform.m,
-        visible_rect,
-        task.device_pixel_scale
+        snap_rect,
+        snap_offsets
     );
 
     // Transform the current vertex to world space.
     vec4 world_pos = transform.m * vec4(clamped_local_pos, 0.0, 1.0);
 
     // Convert the world positions to device pixel space.
     vec2 device_pos = world_pos.xy * task.device_pixel_scale;
 
--- a/gfx/wr/webrender/res/snap.glsl
+++ b/gfx/wr/webrender/res/snap.glsl
@@ -1,68 +1,22 @@
 /* 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/. */
 
 #ifdef WR_VERTEX_SHADER
 
-vec4 compute_snap_positions(
-    mat4 transform,
-    RectWithSize snap_rect,
-    float device_pixel_scale
-) {
-    // Ensure that the snap rect is at *least* one device pixel in size.
-    // TODO(gw): It's not clear to me that this is "correct". Specifically,
-    //           how should it interact with sub-pixel snap rects when there
-    //           is a transform with scale present? But it does fix
-    //           the test cases we have in Servo that are failing without it
-    //           and seem better than not having this at all.
-    snap_rect.size = max(snap_rect.size, vec2(1.0 / device_pixel_scale));
-
-    // Transform the snap corners to the world space.
-    vec4 world_snap_p0 = transform * vec4(snap_rect.p0, 0.0, 1.0);
-    vec4 world_snap_p1 = transform * vec4(snap_rect.p0 + snap_rect.size, 0.0, 1.0);
-    // Snap bounds in world coordinates, adjusted for pixel ratio. XY = top left, ZW = bottom right
-    vec4 world_snap = device_pixel_scale * vec4(world_snap_p0.xy, world_snap_p1.xy) /
-                                           vec4(world_snap_p0.ww, world_snap_p1.ww);
-    return world_snap;
-}
-
 /// Given a point within a local rectangle, and the device space corners
-/// of a snapped primitive, return the snap offsets. This *must* exactly
-/// match the logic in the GLSL compute_snap_offset_impl function.
-vec2 compute_snap_offset_impl(
+/// offsets for the unsnapped primitive, return the snap offsets. This *must*
+/// exactly match the logic in the Rust compute_snap_offset_impl function.
+vec2 compute_snap_offset(
     vec2 reference_pos,
     RectWithSize reference_rect,
     vec4 snap_positions
 ) {
-    /// World offsets applied to the corners of the snap rectangle.
-    vec4 snap_offsets = floor(snap_positions + 0.5) - snap_positions;
-
     /// Compute the position of this vertex inside the snap rectangle.
     vec2 normalized_snap_pos = (reference_pos - reference_rect.p0) / reference_rect.size;
 
     /// Compute the actual world offset for this vertex needed to make it snap.
-    return mix(snap_offsets.xy, snap_offsets.zw, normalized_snap_pos);
-}
-
-// Compute a snapping offset in world space (adjusted to pixel ratio),
-// given local position on the transform and a snap rectangle.
-vec2 compute_snap_offset(vec2 local_pos,
-                         mat4 transform,
-                         RectWithSize snap_rect,
-                         float device_pixel_scale) {
-    vec4 snap_positions = compute_snap_positions(
-        transform,
-        snap_rect,
-        device_pixel_scale
-    );
-
-    vec2 snap_offsets = compute_snap_offset_impl(
-        local_pos,
-        snap_rect,
-        snap_positions
-    );
-
-    return snap_offsets;
+    return mix(snap_positions.xy, snap_positions.zw, normalized_snap_pos);
 }
 
 #endif //WR_VERTEX_SHADER
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -13,16 +13,17 @@ use gpu_types::{BrushFlags, BrushInstanc
 use gpu_types::{ClipMaskInstance, SplitCompositeInstance, SnapOffsets};
 use gpu_types::{PrimitiveInstanceData, RasterizationSpace, GlyphInstance};
 use gpu_types::{PrimitiveHeader, PrimitiveHeaderIndex, TransformPaletteId, TransformPalette};
 use internal_types::{FastHashMap, SavedTargetIndex, TextureSource};
 use picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive};
 use prim_store::{DeferredResolve, EdgeAaSegmentMask, PrimitiveInstanceKind, PrimitiveVisibilityIndex};
 use prim_store::{VisibleGradientTile, PrimitiveInstance, PrimitiveOpacity, SegmentInstanceIndex};
 use prim_store::{BrushSegment, ClipMaskKind, ClipTaskIndex, VECS_PER_SEGMENT};
+use prim_store::{recompute_snap_offsets};
 use prim_store::image::ImageSource;
 use render_backend::DataStores;
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskTree, TileBlit};
 use renderer::{BlendMode, ImageBufferKind, ShaderColorMode};
 use renderer::{BLOCKS_PER_UV_RECT, MAX_VERTEX_TEXTURE_WIDTH};
 use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache, ImageProperties};
 use scene::FilterOpHelpers;
 use smallvec::SmallVec;
@@ -620,32 +621,35 @@ impl AlphaBatchBuilder {
         let z_id = z_generator.next();
 
         let prim_common_data = &ctx.data_stores.as_common_data(&prim_instance);
         let prim_rect = LayoutRect::new(
             prim_instance.prim_origin,
             prim_common_data.prim_size,
         );
 
+        let snap_offsets = prim_info.snap_offsets;
+
         if is_chased {
             println!("\tbatch {:?} with bound {:?}", prim_rect, bounding_rect);
         }
 
         match prim_instance.kind {
             PrimitiveInstanceKind::Clear { data_handle } => {
                 let prim_data = &ctx.data_stores.prim[data_handle];
                 let prim_cache_address = gpu_cache.get_address(&prim_data.gpu_cache_handle);
 
                 // TODO(gw): We can abstract some of the common code below into
                 //           helper methods, as we port more primitives to make
                 //           use of interning.
 
                 let prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
                     local_clip_rect: prim_info.combined_local_clip_rect,
+                    snap_offsets,
                     task_address,
                     specific_prim_address: prim_cache_address,
                     transform_id,
                 };
 
                 let prim_header_index = prim_headers.push(
                     &prim_header,
                     z_id,
@@ -710,16 +714,17 @@ impl AlphaBatchBuilder {
                     specified_blend_mode
                 } else {
                     BlendMode::None
                 };
 
                 let prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
                     local_clip_rect: prim_info.combined_local_clip_rect,
+                    snap_offsets,
                     task_address,
                     specific_prim_address: prim_cache_address,
                     transform_id,
                 };
 
                 let batch_params = BrushBatchParameters::instanced(
                     BrushBatchKind::Image(ImageBufferKind::Texture2DArray),
                     [
@@ -762,16 +767,17 @@ impl AlphaBatchBuilder {
                 let prim_data = &ctx.data_stores.text_run[data_handle];
                 let glyph_fetch_buffer = &mut self.glyph_fetch_buffer;
                 let alpha_batch_list = &mut self.batch_lists.last_mut().unwrap().alpha_batch_list;
                 let prim_cache_address = gpu_cache.get_address(&prim_data.gpu_cache_handle);
 
                 let prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
                     local_clip_rect: prim_info.combined_local_clip_rect,
+                    snap_offsets,
                     task_address,
                     specific_prim_address: prim_cache_address,
                     transform_id,
                 };
 
                 let clip_task_address = ctx.get_prim_clip_task_address(
                     prim_info.clip_task_index,
                     render_tasks,
@@ -929,16 +935,17 @@ impl AlphaBatchBuilder {
                     BlendMode::PremultipliedAlpha
                 } else {
                     BlendMode::None
                 };
 
                 let prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
                     local_clip_rect: prim_info.combined_local_clip_rect,
+                    snap_offsets,
                     task_address,
                     specific_prim_address: prim_cache_address,
                     transform_id,
                 };
 
                 let prim_header_index = prim_headers.push(
                     &prim_header,
                     z_id,
@@ -973,18 +980,19 @@ impl AlphaBatchBuilder {
                 );
             }
             PrimitiveInstanceKind::Picture { pic_index, segment_instance_index, .. } => {
                 let picture = &ctx.prim_store.pictures[pic_index.0];
                 let non_segmented_blend_mode = BlendMode::PremultipliedAlpha;
                 let prim_cache_address = gpu_cache.get_address(&ctx.globals.default_image_handle);
 
                 let prim_header = PrimitiveHeader {
-                    local_rect: picture.local_rect,
+                    local_rect: picture.snapped_local_rect,
                     local_clip_rect: prim_info.combined_local_clip_rect,
+                    snap_offsets,
                     task_address,
                     specific_prim_address: prim_cache_address,
                     transform_id,
                 };
 
                 match picture.context_3d {
                     // Convert all children of the 3D hierarchy root into batches.
                     Picture3DContext::In { root_data: Some(ref list), .. } => {
@@ -1011,18 +1019,19 @@ impl AlphaBatchBuilder {
 
                             // Get clip task, if set, for the picture primitive.
                             let clip_task_address = ctx.get_prim_clip_task_address(
                                 prim_info.clip_task_index,
                                 render_tasks,
                             ).unwrap_or(OPAQUE_TASK_ADDRESS);
 
                             let prim_header = PrimitiveHeader {
-                                local_rect: pic.local_rect,
+                                local_rect: pic.snapped_local_rect,
                                 local_clip_rect: prim_info.combined_local_clip_rect,
+                                snap_offsets,
                                 task_address,
                                 specific_prim_address: GpuCacheAddress::invalid(),
                                 transform_id: transforms
                                     .get_id(
                                         child.spatial_node_index,
                                         root_spatial_node_index,
                                         ctx.clip_scroll_tree,
                                     ),
@@ -1070,29 +1079,22 @@ impl AlphaBatchBuilder {
                     // hierarchy, since we process them with the root.
                     Picture3DContext::In { root_data: None, .. } => return,
                     // Proceed for non-3D pictures.
                     Picture3DContext::Out => ()
                 }
 
                 match picture.raster_config {
                     Some(ref raster_config) => {
-                        // All pictures must snap to their primitive rect instead of the
-                        // visible rect like most primitives. This is because the picture's
-                        // visible rect includes the effect of the picture's clip rect,
-                        // which was not considered by the picture's children. The primitive
-                        // rect however is simply the union of the visible rect of the
-                        // children, which they snapped to, which is precisely what we also
-                        // need to snap to in order to be consistent.
-                        let mut brush_flags = BrushFlags::SNAP_TO_PRIMITIVE;
-
                         // If the child picture was rendered in local space, we can safely
                         // interpolate the UV coordinates with perspective correction.
-                        if raster_config.establishes_raster_root {
-                            brush_flags |= BrushFlags::PERSPECTIVE_INTERPOLATION;
+                        let brush_flags = if raster_config.establishes_raster_root {
+                            BrushFlags::PERSPECTIVE_INTERPOLATION
+                        } else {
+                            BrushFlags::empty()
                         };
 
                         let clip_task_address = ctx.get_prim_clip_task_address(
                             prim_info.clip_task_index,
                             render_tasks,
                         ).unwrap_or(OPAQUE_TASK_ADDRESS);
 
                         let surface = ctx.surfaces[raster_config.surface_index.0].surface;
@@ -1120,17 +1122,17 @@ impl AlphaBatchBuilder {
 
                                     return;
                                 }
 
                                 // Construct a local clip rect that ensures we only draw pixels where
                                 // the local bounds of the picture extend to within the edge tiles.
                                 let local_clip_rect = prim_info
                                     .combined_local_clip_rect
-                                    .intersection(&picture.local_rect)
+                                    .intersection(&picture.snapped_local_rect)
                                     .and_then(|rect| {
                                         rect.intersection(&tile_cache.local_clip_rect)
                                     });
 
                                 if let Some(local_clip_rect) = local_clip_rect {
                                     // Step through each tile in the cache, and draw it with an image
                                     // brush primitive if visible.
 
@@ -1139,19 +1141,27 @@ impl AlphaBatchBuilder {
                                     );
 
                                     for tile_index in &tile_cache.tiles_to_draw {
                                         let tile = &tile_cache.tiles[tile_index.0];
 
                                         // Get the local rect of the tile.
                                         let tile_rect = tile.local_rect;
 
+                                        // Adjust the snap offsets for the tile.
+                                        let snap_offsets = recompute_snap_offsets(
+                                            tile_rect,
+                                            prim_rect,
+                                            snap_offsets,
+                                        );
+
                                         let prim_header = PrimitiveHeader {
                                             local_rect: tile_rect,
                                             local_clip_rect,
+                                            snap_offsets,
                                             task_address,
                                             specific_prim_address: prim_cache_address,
                                             transform_id,
                                         };
 
                                         let prim_header_index = prim_headers.push(&prim_header, z_id, [
                                             ShaderColorMode::Image as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
                                             RasterizationSpace::Local as i32,
@@ -1338,16 +1348,17 @@ impl AlphaBatchBuilder {
                                             get_shader_opacity(1.0),
                                             0,
                                         ]);
 
                                         let shadow_rect = prim_header.local_rect.translate(&offset);
 
                                         let shadow_prim_header = PrimitiveHeader {
                                             local_rect: shadow_rect,
+                                            snap_offsets: prim_info.shadow_snap_offsets,
                                             specific_prim_address: shadow_prim_address,
                                             ..prim_header
                                         };
 
                                         let shadow_prim_header_index = prim_headers.push(&shadow_prim_header, z_id_shadow, [
                                             ShaderColorMode::Alpha as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
                                             RasterizationSpace::Screen as i32,
                                             get_shader_opacity(1.0),
@@ -1614,18 +1625,19 @@ impl AlphaBatchBuilder {
                                     let segment_instance = &ctx.scratch.segment_instances[segment_instance_index];
                                     let segments = Some(&ctx.scratch.segments[segment_instance.segments_range]);
                                     (gpu_cache.get_address(&segment_instance.gpu_cache_handle), segments)
                                 } else {
                                     (prim_cache_address, None)
                                 };
 
                                 let prim_header = PrimitiveHeader {
-                                    local_rect: picture.local_rect,
+                                    local_rect: picture.snapped_local_rect,
                                     local_clip_rect: prim_info.combined_local_clip_rect,
+                                    snap_offsets,
                                     task_address,
                                     specific_prim_address: prim_cache_address,
                                     transform_id,
                                 };
 
                                 let prim_header_index = prim_headers.push(
                                     &prim_header,
                                     z_id,
@@ -1701,16 +1713,17 @@ impl AlphaBatchBuilder {
                     specified_blend_mode
                 } else {
                     BlendMode::None
                 };
 
                 let prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
                     local_clip_rect: prim_info.combined_local_clip_rect,
+                    snap_offsets: snap_offsets,
                     task_address,
                     specific_prim_address: prim_cache_address,
                     transform_id,
                 };
 
                 let batch_params = BrushBatchParameters::shared(
                     BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)),
                     textures,
@@ -1774,16 +1787,17 @@ impl AlphaBatchBuilder {
                     let segment_instance = &ctx.scratch.segment_instances[segment_instance_index];
                     let segments = Some(&ctx.scratch.segments[segment_instance.segments_range]);
                     (gpu_cache.get_address(&segment_instance.gpu_cache_handle), segments)
                 };
 
                 let prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
                     local_clip_rect: prim_info.combined_local_clip_rect,
+                    snap_offsets: snap_offsets,
                     task_address,
                     specific_prim_address: prim_cache_address,
                     transform_id,
                 };
 
                 let prim_header_index = prim_headers.push(
                     &prim_header,
                     z_id,
@@ -1881,16 +1895,17 @@ impl AlphaBatchBuilder {
                     let segment_instance = &ctx.scratch.segment_instances[segment_instance_index];
                     let segments = Some(&ctx.scratch.segments[segment_instance.segments_range]);
                     (gpu_cache.get_address(&segment_instance.gpu_cache_handle), segments)
                 };
 
                 let prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
                     local_clip_rect: prim_info.combined_local_clip_rect,
+                    snap_offsets: snap_offsets,
                     task_address,
                     specific_prim_address: prim_cache_address,
                     transform_id,
                 };
 
                 let prim_header_index = prim_headers.push(
                     &prim_header,
                     z_id,
@@ -1985,16 +2000,17 @@ impl AlphaBatchBuilder {
                         let segment_instance = &ctx.scratch.segment_instances[image_instance.segment_instance_index];
                         let segments = Some(&ctx.scratch.segments[segment_instance.segments_range]);
                         (gpu_cache.get_address(&segment_instance.gpu_cache_handle), segments)
                     };
 
                     let prim_header = PrimitiveHeader {
                         local_rect: prim_rect,
                         local_clip_rect: prim_info.combined_local_clip_rect,
+                        snap_offsets: snap_offsets,
                         task_address,
                         specific_prim_address: prim_cache_address,
                         transform_id,
                     };
 
                     let prim_header_index = prim_headers.push(
                         &prim_header,
                         z_id,
@@ -2037,16 +2053,17 @@ impl AlphaBatchBuilder {
                             gpu_blocks.push(tile_rect.into());
                             gpu_blocks.push(GpuBlockData::EMPTY);
                         }
 
                         let gpu_handle = gpu_cache.push_per_frame_blocks(&gpu_blocks);
                         let prim_header = PrimitiveHeader {
                             local_rect: prim_rect,
                             local_clip_rect: image_instance.tight_local_clip_rect,
+                            snap_offsets,
                             task_address,
                             specific_prim_address: gpu_cache.get_address(&gpu_handle),
                             transform_id,
                         };
                         let prim_header_index = prim_headers.push(&prim_header, z_id, prim_user_data);
 
                         for (i, tile) in chunk.iter().enumerate() {
                             if let Some((batch_kind, textures, uv_rect_address)) = get_image_tile_params(
@@ -2082,16 +2099,17 @@ impl AlphaBatchBuilder {
             PrimitiveInstanceKind::LinearGradient { data_handle, gradient_index, .. } => {
                 let gradient = &ctx.prim_store.linear_gradients[gradient_index];
                 let prim_data = &ctx.data_stores.linear_grad[data_handle];
                 let specified_blend_mode = BlendMode::PremultipliedAlpha;
 
                 let mut prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
                     local_clip_rect: prim_info.combined_local_clip_rect,
+                    snap_offsets,
                     task_address,
                     specific_prim_address: GpuCacheAddress::invalid(),
                     transform_id,
                 };
 
                 let non_segmented_blend_mode = if !prim_data.opacity.is_opaque ||
                     prim_info.clip_task_index != ClipTaskIndex::INVALID ||
                     transform_kind == TransformedRectKind::Complex
@@ -2220,16 +2238,17 @@ impl AlphaBatchBuilder {
             }
             PrimitiveInstanceKind::RadialGradient { data_handle, ref visible_tiles_range, .. } => {
                 let prim_data = &ctx.data_stores.radial_grad[data_handle];
                 let specified_blend_mode = BlendMode::PremultipliedAlpha;
 
                 let mut prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
                     local_clip_rect: prim_info.combined_local_clip_rect,
+                    snap_offsets,
                     task_address,
                     specific_prim_address: GpuCacheAddress::invalid(),
                     transform_id,
                 };
 
                 if visible_tiles_range.is_empty() {
                     let non_segmented_blend_mode = if !prim_data.opacity.is_opaque ||
                         prim_info.clip_task_index != ClipTaskIndex::INVALID ||
@@ -2489,20 +2508,28 @@ fn add_gradient_tiles(
         },
         bounding_rect,
         z_id,
     );
 
     let user_data = [stops_handle.as_int(gpu_cache), 0, 0, 0];
 
     for tile in visible_tiles {
+        // Adjust the snap offsets for the tile.
+        let snap_offsets = recompute_snap_offsets(
+            tile.local_rect,
+            base_prim_header.local_rect,
+            base_prim_header.snap_offsets,
+        );
+
         let prim_header = PrimitiveHeader {
             specific_prim_address: gpu_cache.get_address(&tile.handle),
             local_rect: tile.local_rect,
             local_clip_rect: tile.local_clip_rect,
+            snap_offsets,
             ..*base_prim_header
         };
         let prim_header_index = prim_headers.push(&prim_header, z_id, user_data);
 
         batch.push(PrimitiveInstanceData::from(
             BrushInstance {
                 prim_header_index,
                 clip_task_address,
--- a/gfx/wr/webrender/src/gpu_types.rs
+++ b/gfx/wr/webrender/src/gpu_types.rs
@@ -230,16 +230,17 @@ impl PrimitiveHeaders {
         user_data: [i32; 4],
     ) -> PrimitiveHeaderIndex {
         debug_assert_eq!(self.headers_int.len(), self.headers_float.len());
         let id = self.headers_float.len();
 
         self.headers_float.push(PrimitiveHeaderF {
             local_rect: prim_header.local_rect,
             local_clip_rect: prim_header.local_clip_rect,
+            snap_offsets: prim_header.snap_offsets,
         });
 
         self.headers_int.push(PrimitiveHeaderI {
             z,
             task_address: prim_header.task_address,
             specific_prim_address: prim_header.specific_prim_address.as_int(),
             transform_id: prim_header.transform_id,
             user_data,
@@ -250,29 +251,31 @@ impl PrimitiveHeaders {
 }
 
 // This is a convenience type used to make it easier to pass
 // the common parts around during batching.
 #[derive(Debug)]
 pub struct PrimitiveHeader {
     pub local_rect: LayoutRect,
     pub local_clip_rect: LayoutRect,
+    pub snap_offsets: SnapOffsets,
     pub task_address: RenderTaskAddress,
     pub specific_prim_address: GpuCacheAddress,
     pub transform_id: TransformPaletteId,
 }
 
 // f32 parts of a primitive header
 #[derive(Debug)]
 #[repr(C)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PrimitiveHeaderF {
     pub local_rect: LayoutRect,
     pub local_clip_rect: LayoutRect,
+    pub snap_offsets: SnapOffsets,
 }
 
 // i32 parts of a primitive header
 // TODO(gw): Compress parts of these down to u16
 #[derive(Debug)]
 #[repr(C)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
@@ -358,18 +361,16 @@ bitflags! {
         /// rather than primitive rect.
         const SEGMENT_RELATIVE = 0x2;
         /// Repeat UVs horizontally.
         const SEGMENT_REPEAT_X = 0x4;
         /// Repeat UVs vertically.
         const SEGMENT_REPEAT_Y = 0x8;
         /// The extra segment data is a texel rect.
         const SEGMENT_TEXEL_RECT = 0x10;
-        /// Snap to the primitive rect instead of the visible rect.
-        const SNAP_TO_PRIMITIVE = 0x20;
     }
 }
 
 // TODO(gw): Some of these fields can be moved to the primitive
 //           header since they are constant, and some can be
 //           compressed to a smaller size.
 #[repr(C)]
 pub struct BrushInstance {
@@ -623,16 +624,21 @@ pub struct SnapOffsets {
 
 impl SnapOffsets {
     pub fn empty() -> Self {
         SnapOffsets {
             top_left: DeviceVector2D::zero(),
             bottom_right: DeviceVector2D::zero(),
         }
     }
+
+    pub fn is_empty(&self) -> bool {
+        let zero = DeviceVector2D::zero();
+        self.top_left == zero && self.bottom_right == zero
+    }
 }
 
 #[derive(Debug, Copy, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ImageSource {
     pub p0: DevicePoint,
     pub p1: DevicePoint,
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -853,21 +853,28 @@ impl TileCache {
                 None => true,
             };
             self.opacity_bindings.insert(*id, OpacityBindingInfo {
                 value: *value,
                 changed,
             });
         }
 
-        // Map the picture rect to world space and work out the tiles that we need
-        // in order to ensure the screen is covered.
-        let pic_world_rect = world_mapper
-            .map(&pic_rect)
-            .expect("bug: unable to map picture rect to world");
+        // Map the picture rect to world and device space and work out the tiles
+        // that we need in order to ensure the screen is covered. We haven't done
+        // any snapping yet, so we need to round out in device space to ensure we
+        // cover all pixels the picture may touch.
+        let pic_device_rect = {
+            let unsnapped_world_rect = world_mapper
+                .map(&pic_rect)
+                .expect("bug: unable to map picture rect to world");
+            (unsnapped_world_rect * frame_context.global_device_pixel_scale)
+                .round_out()
+        };
+        let pic_world_rect = pic_device_rect / frame_context.global_device_pixel_scale;
 
         // If the bounding rect of the picture to cache doesn't intersect with
         // the visible world rect at all, just take the screen world rect as
         // a reference for the area to create tiles for. This allows existing
         // tiles to be retained in case they are still valid if / when they
         // get scrolled back onto the screen.
 
         let needed_world_rect = frame_context
@@ -887,17 +894,16 @@ impl TileCache {
 
         // Apply the scroll delta so that existing tiles still get used.
         world_ref_point += scroll_delta;
 
         // Work out the required device rect that we need to cover the screen,
         // given the world reference point constraint.
         let device_ref_point = world_ref_point * frame_context.global_device_pixel_scale;
         let device_world_rect = frame_context.screen_world_rect * frame_context.global_device_pixel_scale;
-        let pic_device_rect = pic_world_rect * frame_context.global_device_pixel_scale;
         let needed_device_rect = pic_device_rect
             .intersection(&device_world_rect)
             .unwrap_or(device_world_rect);
 
         // Expand the needed device rect vertically by a small number of tiles. This
         // ensures that as tiles are scrolled in/out of view, they are retained for
         // a while before being discarded.
         // TODO(gw): On some pages it might be worth also inflating horizontally.
@@ -1728,17 +1734,16 @@ impl<'a> PictureUpdateState<'a> {
                     clip_data_store,
                 );
             }
 
             picture_primitives[pic_index.0].post_update(
                 prim_list,
                 self,
                 frame_context,
-                gpu_cache,
             );
         }
     }
 
     /// Process the picture tree again in a depth-first order,
     /// and adjust the raster roots of the pictures that want to establish
     /// their own roots but are not able to due to the size constraints.
     fn assign_raster_roots(
@@ -2185,18 +2190,26 @@ pub struct PicturePrimitive {
     // picture.
     pub extra_gpu_data_handle: GpuCacheHandle,
 
     /// The spatial node index of this picture when it is
     /// composited into the parent picture.
     pub spatial_node_index: SpatialNodeIndex,
 
     /// The local rect of this picture. It is built
-    /// dynamically during the first picture traversal.
-    pub local_rect: LayoutRect,
+    /// dynamically when updating visibility. It takes
+    /// into account snapping in device space for its
+    /// children.
+    pub snapped_local_rect: LayoutRect,
+
+    /// The local rect of this picture. It is built
+    /// dynamically during the first picture traversal. It
+    /// does not take into account snapping in device for
+    /// its children.
+    pub unsnapped_local_rect: LayoutRect,
 
     /// If false, this picture needs to (re)build segments
     /// if it supports segment rendering. This can occur
     /// if the local rect of the picture changes due to
     /// transform animation and/or scrolling.
     pub segments_are_valid: bool,
 
     /// If Some(..) the tile cache that is associated with this picture.
@@ -2211,17 +2224,18 @@ impl PicturePrimitive {
     pub fn print<T: PrintTreePrinter>(
         &self,
         pictures: &[Self],
         self_index: PictureIndex,
         pt: &mut T,
     ) {
         pt.new_level(format!("{:?}", self_index));
         pt.add_item(format!("prim_count: {:?}", self.prim_list.prim_instances.len()));
-        pt.add_item(format!("local_rect: {:?}", self.local_rect));
+        pt.add_item(format!("snapped_local_rect: {:?}", self.snapped_local_rect));
+        pt.add_item(format!("unsnapped_local_rect: {:?}", self.unsnapped_local_rect));
         pt.add_item(format!("spatial_node_index: {:?}", self.spatial_node_index));
         pt.add_item(format!("raster_config: {:?}", self.raster_config));
         pt.add_item(format!("requested_composite_mode: {:?}", self.requested_composite_mode));
 
         for (index, _) in &self.prim_list.pictures {
             pictures[index.0].print(pictures, *index, pt);
         }
 
@@ -2320,17 +2334,18 @@ impl PicturePrimitive {
             context_3d,
             frame_output_pipeline_id,
             extra_gpu_data_handle: GpuCacheHandle::new(),
             apply_local_clip_rect,
             is_backface_visible,
             pipeline_id,
             requested_raster_space,
             spatial_node_index,
-            local_rect: LayoutRect::zero(),
+            snapped_local_rect: LayoutRect::zero(),
+            unsnapped_local_rect: LayoutRect::zero(),
             tile_cache,
             options,
             segments_are_valid: false,
         }
     }
 
     pub fn take_context(
         &mut self,
@@ -2744,17 +2759,16 @@ impl PicturePrimitive {
 
     /// Called after updating child pictures during the initial
     /// picture traversal.
     fn post_update(
         &mut self,
         prim_list: PrimitiveList,
         state: &mut PictureUpdateState,
         frame_context: &FrameBuildingContext,
-        gpu_cache: &mut GpuCache,
     ) {
         // Restore the pictures list used during recursion.
         self.prim_list = prim_list;
 
         // Pop the state information about this picture.
         state.pop_picture();
 
         for cluster in &mut self.prim_list.clusters {
@@ -2817,32 +2831,20 @@ impl PicturePrimitive {
                 surface.rect = surface.rect.inflate(inflation_size, inflation_size);
                 surface.rect * TypedScale::new(1.0)
             };
 
             // Pop this surface from the stack
             let surface_index = state.pop_surface();
             debug_assert_eq!(surface_index, raster_config.surface_index);
 
-            // If the local rect changed (due to transforms in child primitives) then
-            // invalidate the GPU cache location to re-upload the new local rect
-            // and stretch size. Drop shadow filters also depend on the local rect
-            // size for the extra GPU cache data handle.
-            // TODO(gw): In future, if we support specifying a flag which gets the
-            //           stretch size from the segment rect in the shaders, we can
-            //           remove this invalidation here completely.
-            if self.local_rect != surface_rect {
-                if let PictureCompositeMode::Filter(FilterOp::DropShadow(..)) = raster_config.composite_mode {
-                    gpu_cache.invalidate(&self.extra_gpu_data_handle);
-                }
-                // Invalidate any segments built for this picture, since the local
-                // rect has changed.
-                self.segments_are_valid = false;
-                self.local_rect = surface_rect;
-            }
+            // Snapping may change the local rect slightly, and as such should just be
+            // considered an estimated size for determining if we need raster roots and
+            // preparing the tile cache.
+            self.unsnapped_local_rect = surface_rect;
 
             // Check if any of the surfaces can't be rasterized in local space but want to.
             if raster_config.establishes_raster_root {
                 if surface_rect.size.width > MAX_SURFACE_SIZE ||
                     surface_rect.size.height > MAX_SURFACE_SIZE
                 {
                     raster_config.establishes_raster_root = false;
                     state.are_raster_roots_assigned = false;
@@ -2905,17 +2907,17 @@ impl PicturePrimitive {
 
         let (map_raster_to_world, map_pic_to_raster) = create_raster_mappers(
             self.spatial_node_index,
             raster_spatial_node_index,
             frame_context.screen_world_rect,
             frame_context.clip_scroll_tree,
         );
 
-        let pic_rect = PictureRect::from_untyped(&self.local_rect.to_untyped());
+        let pic_rect = PictureRect::from_untyped(&self.snapped_local_rect.to_untyped());
 
         let (clipped, unclipped) = match get_raster_rects(
             pic_rect,
             &map_pic_to_raster,
             &map_raster_to_world,
             clipped_prim_bounding_rect,
             device_pixel_scale,
         ) {
@@ -3067,24 +3069,24 @@ impl PicturePrimitive {
                     //           drop-shadow where we need a different local
                     //           rect for the shadow. To tidy this up in future,
                     //           we could consider abstracting the code in prim_store.rs
                     //           that writes a brush primitive header.
 
                     // Basic brush primitive header is (see end of prepare_prim_for_render_inner in prim_store.rs)
                     //  [brush specific data]
                     //  [segment_rect, segment data]
-                    let shadow_rect = self.local_rect.translate(&offset);
+                    let shadow_rect = self.snapped_local_rect.translate(&offset);
 
                     // ImageBrush colors
                     request.push(color.premultiplied());
                     request.push(PremultipliedColorF::WHITE);
                     request.push([
-                        self.local_rect.size.width,
-                        self.local_rect.size.height,
+                        self.snapped_local_rect.size.width,
+                        self.snapped_local_rect.size.height,
                         0.0,
                         0.0,
                     ]);
 
                     // segment rect / extra data
                     request.push(shadow_rect);
                     request.push([0.0, 0.0, 0.0, 0.0]);
                 }
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -5,34 +5,35 @@
 use api::{BorderRadius, ClipMode, ColorF};
 use api::{FilterOp, ImageRendering, RepeatMode};
 use api::{PremultipliedColorF, PropertyBinding, Shadow, GradientStop};
 use api::{BoxShadowClipMode, LineStyle, LineOrientation};
 use api::{PrimitiveKeyKind, RasterSpace};
 use api::units::*;
 use border::{get_max_scale_for_border, build_border_instances};
 use border::BorderSegmentCacheKey;
+use box_shadow::{BLUR_SAMPLE_SCALE};
 use clip::{ClipStore};
 use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex, VisibleFace};
 use clip::{ClipDataStore, ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem};
 use debug_colors;
 use debug_render::DebugItem;
 use display_list_flattener::{CreateShadow, IsVisible};
-use euclid::{SideOffsets2D, TypedTransform3D, TypedRect, TypedScale, TypedSize2D};
+use euclid::{SideOffsets2D, TypedTransform3D, TypedRect, TypedScale, TypedSize2D, TypedPoint2D};
 use euclid::approxeq::ApproxEq;
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
 use frame_builder::{FrameVisibilityContext, FrameVisibilityState};
 use glyph_rasterizer::GlyphKey;
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest, ToGpuBlocks};
 use gpu_types::{BrushFlags, SnapOffsets};
 use image::{Repetition};
 use intern;
 use malloc_size_of::MallocSizeOf;
 use picture::{PictureCompositeMode, PicturePrimitive, SurfaceInfo};
-use picture::{ClusterIndex, PrimitiveList, RecordedDirtyRegion, SurfaceIndex, RetainedTiles, RasterConfig};
+use picture::{ClusterIndex, PrimitiveList, RecordedDirtyRegion, SurfaceIndex, RetainedTiles};
 use prim_store::borders::{ImageBorderDataHandle, NormalBorderDataHandle};
 use prim_store::gradient::{GRADIENT_FP_STOPS, GradientCacheKey, GradientStopKey};
 use prim_store::gradient::{LinearGradientPrimitive, LinearGradientDataHandle, RadialGradientDataHandle};
 use prim_store::image::{ImageDataHandle, ImageInstance, VisibleImageTile, YuvImageDataHandle};
 use prim_store::line_dec::LineDecorationDataHandle;
 use prim_store::picture::PictureDataHandle;
 use prim_store::text_run::{TextRunDataHandle, TextRunPrimitive};
 #[cfg(debug_assertions)]
@@ -44,17 +45,17 @@ use renderer::{MAX_VERTEX_TEXTURE_WIDTH}
 use resource_cache::{ImageProperties, ImageRequest};
 use scene::SceneProperties;
 use segment::SegmentBuilder;
 use std::{cmp, fmt, hash, ops, u32, usize, mem};
 #[cfg(debug_assertions)]
 use std::sync::atomic::{AtomicUsize, Ordering};
 use storage;
 use texture_cache::TEXTURE_REGION_DIMENSIONS;
-use util::{ScaleOffset, MatrixHelpers, MaxRect, Recycler, TransformedRectKind};
+use util::{ScaleOffset, MatrixHelpers, MaxRect, Recycler};
 use util::{pack_as_float, project_rect, raster_rect_to_device_pixels};
 use util::{scale_factors, clamp_to_scale_factor};
 use internal_types::LayoutPrimitiveInfo;
 use smallvec::SmallVec;
 
 pub mod borders;
 pub mod gradient;
 pub mod image;
@@ -1365,16 +1366,28 @@ pub struct PrimitiveVisibility {
     /// has no clip mask. Otherwise, it may store the offset of the
     /// global clip mask task for this primitive, or the first of
     /// a list of clip task ids (one per segment).
     pub clip_task_index: ClipTaskIndex,
 
     /// The current combined local clip for this primitive, from
     /// the primitive local clip above and the current clip chain.
     pub combined_local_clip_rect: LayoutRect,
+
+    /// The snap offsets in device space for this primitive. They are
+    /// generated based on the visible rect, which is the local rect
+    /// clipped by the combined local clip for most primitives, or
+    /// just the local rect for pictures.
+    pub snap_offsets: SnapOffsets,
+
+    /// The snap offsets in device space for the drop shadow for
+    /// picture primitives, if applicable. Similar to snap offsets,
+    /// they are generated based on the local rect translated by the
+    /// drop shadow offset.
+    pub shadow_snap_offsets: SnapOffsets,
 }
 
 #[derive(Clone, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 pub struct PrimitiveInstance {
     /// Identifies the kind of primitive this
     /// instance is, and references to where
     /// the relevant information for the primitive
@@ -1717,34 +1730,34 @@ impl PrimitiveStore {
     /// Update visibility pass - update each primitive visibility struct, and
     /// build the clip chain instance if appropriate.
     pub fn update_visibility(
         &mut self,
         pic_index: PictureIndex,
         parent_surface_index: SurfaceIndex,
         frame_context: &FrameVisibilityContext,
         frame_state: &mut FrameVisibilityState,
-    ) {
+    ) -> Option<PictureRect> {
         let (mut prim_list, surface_index, apply_local_clip_rect, raster_space) = {
             let pic = &mut self.pictures[pic_index.0];
 
             let prim_list = mem::replace(&mut pic.prim_list, PrimitiveList::empty());
             let surface_index = match pic.raster_config {
                 Some(ref raster_config) => raster_config.surface_index,
                 None => parent_surface_index,
             };
 
             if let Some(mut tile_cache) = pic.tile_cache.take() {
                 debug_assert!(frame_state.tile_cache.is_none());
 
                 // If we have a tile cache for this picture, see if any of the
                 // relative transforms have changed, which means we need to
                 // re-map the dependencies of any child primitives.
                 tile_cache.pre_update(
-                    pic.local_rect,
+                    pic.unsnapped_local_rect,
                     frame_context,
                     frame_state,
                     surface_index,
                 );
 
                 frame_state.tile_cache = Some(tile_cache);
             }
 
@@ -1759,16 +1772,23 @@ impl PrimitiveStore {
 
         let map_surface_to_world = SpaceMapper::new_with_target(
             ROOT_SPATIAL_NODE_INDEX,
             surface.surface_spatial_node_index,
             frame_context.screen_world_rect,
             frame_context.clip_scroll_tree,
         );
 
+        let mut map_local_to_raster = SpaceMapper::new(
+            surface.raster_spatial_node_index,
+            RasterRect::max_rect(),
+        );
+
+        let mut surface_rect = PictureRect::zero();
+
         for prim_instance in &mut prim_list.prim_instances {
             prim_instance.reset();
 
             if prim_instance.is_chased() {
                 #[cfg(debug_assertions)] // needed for ".id" part
                 println!("\tpreparing {:?} in {:?}", prim_instance.id, pic_index);
             }
 
@@ -1780,17 +1800,22 @@ impl PrimitiveStore {
                 continue;
             }
 
             map_local_to_surface.set_target_spatial_node(
                 prim_instance.spatial_node_index,
                 frame_context.clip_scroll_tree,
             );
 
-            let (is_passthrough, prim_local_rect) = match prim_instance.kind {
+            map_local_to_raster.set_target_spatial_node(
+                prim_instance.spatial_node_index,
+                frame_context.clip_scroll_tree,
+            );
+
+            let (is_passthrough, snap_to_visible, prim_local_rect, prim_shadow_rect) = match prim_instance.kind {
                 PrimitiveInstanceKind::Picture { pic_index, .. } => {
                     let is_composite = {
                         let pic = &self.pictures[pic_index.0];
                         if !pic.is_visible() {
                             continue;
                         }
 
                         // If this picture has a surface, we will handle any active clips from parents
@@ -1806,17 +1831,17 @@ impl PrimitiveStore {
                     };
 
                     if is_composite {
                         frame_state.clip_chain_stack.push_surface();
                     } else {
                         frame_state.clip_chain_stack.push_clip(prim_instance.clip_chain_id);
                     }
 
-                    self.update_visibility(
+                    let pic_surface_rect = self.update_visibility(
                         pic_index,
                         surface_index,
                         frame_context,
                         frame_state,
                     );
 
                     let pic = &self.pictures[pic_index.0];
 
@@ -1824,48 +1849,71 @@ impl PrimitiveStore {
                     // the content of children, which may move due to the spatial
                     // node they are attached to. Other parts of the code (such as
                     // segment generation) reads the origin from the prim instance,
                     // so ensure that is kept up to date here.
                     // TODO(gw): It's unfortunate that the prim origin is duplicated
                     //           this way. In future, we could perhaps just store the
                     //           size in the picture primitive, to that there isn't
                     //           any duplicated data.
-                    prim_instance.prim_origin = pic.local_rect.origin;
+                    prim_instance.prim_origin = pic.snapped_local_rect.origin;
 
                     // Similar to above, pop either the clip chain or root entry off the current clip stack.
                     if is_composite {
                         frame_state.clip_chain_stack.pop_surface();
                     } else {
                         frame_state.clip_chain_stack.pop_clip();
                     }
 
-                    (pic.raster_config.is_none(), pic.local_rect)
+                    let shadow_rect = match pic.raster_config {
+                        Some(ref rc) => match rc.composite_mode {
+                            // If we have a drop shadow filter, we also need to include the shadow in
+                            // our local rect for the purpose of calculating the size of the picture.
+                            PictureCompositeMode::Filter(FilterOp::DropShadow(offset, ..)) => pic.snapped_local_rect.translate(&offset),
+                            _ => LayoutRect::zero(),
+                        }
+                        None => {
+                            if let Some(ref rect) = pic_surface_rect {
+                                surface_rect = surface_rect.union(rect);
+                            }
+                            LayoutRect::zero()
+                        }
+                    };
+
+                    if prim_instance.is_chased() {
+                        if pic.unsnapped_local_rect != pic.snapped_local_rect {
+                            println!("\tsnapped from {:?} to {:?}", pic.unsnapped_local_rect, pic.snapped_local_rect);
+                        }
+                    }
+
+                    (pic.raster_config.is_none(), false, pic.snapped_local_rect, shadow_rect)
                 }
                 _ => {
                     let prim_data = &frame_state.data_stores.as_common_data(&prim_instance);
 
                     let prim_rect = LayoutRect::new(
                         prim_instance.prim_origin,
                         prim_data.prim_size,
                     );
 
-                    (false, prim_rect)
+                    (false, true, prim_rect, LayoutRect::zero())
                 }
             };
 
             if is_passthrough {
                 let vis_index = PrimitiveVisibilityIndex(frame_state.scratch.prim_info.len() as u32);
 
                 frame_state.scratch.prim_info.push(
                     PrimitiveVisibility {
                         clipped_world_rect: WorldRect::max_rect(),
                         clip_chain: ClipChainInstance::empty(),
                         clip_task_index: ClipTaskIndex::INVALID,
                         combined_local_clip_rect: LayoutRect::zero(),
+                        snap_offsets: SnapOffsets::empty(),
+                        shadow_snap_offsets: SnapOffsets::empty(),
                     }
                 );
 
                 prim_instance.visibility_info = vis_index;
             } else {
                 if prim_local_rect.size.width <= 0.0 || prim_local_rect.size.height <= 0.0 {
                     if prim_instance.is_chased() {
                         println!("\tculled for zero local rectangle");
@@ -1950,38 +1998,81 @@ impl PrimitiveStore {
 
                 if prim_instance.is_chased() {
                     println!("\teffective clip chain from {:?} {}",
                         clip_chain.clips_range,
                         if apply_local_clip_rect { "(applied)" } else { "" },
                     );
                 }
 
-                let combined_local_clip_rect = if apply_local_clip_rect {
-                    clip_chain.local_clip_rect
-                } else {
-                    prim_instance.local_clip_rect
-                };
-
                 // Check if the clip bounding rect (in pic space) is visible on screen
                 // This includes both the prim bounding rect + local prim clip rect!
                 let world_rect = match map_surface_to_world.map(&clip_chain.pic_clip_rect) {
                     Some(world_rect) => world_rect,
                     None => {
                         continue;
                     }
                 };
 
                 let clipped_world_rect = match world_rect.intersection(&frame_context.screen_world_rect) {
                     Some(rect) => rect,
                     None => {
                         continue;
                     }
                 };
 
+                let combined_local_clip_rect = if apply_local_clip_rect {
+                    clip_chain.local_clip_rect
+                } else {
+                    prim_instance.local_clip_rect
+                };
+
+                // All pictures must snap to their primitive rect instead of the
+                // visible rect like most primitives. This is because the picture's
+                // visible rect includes the effect of the picture's clip rect,
+                // which was not considered by the picture's children. The primitive
+                // rect however is simply the union of the visible rect of the
+                // children, which they snapped to, which is precisely what we also
+                // need to snap to in order to be consistent.
+                let visible_rect = if snap_to_visible {
+                    combined_local_clip_rect
+                        .intersection(&prim_local_rect)
+                        .unwrap_or(LayoutRect::zero())
+                } else {
+                    prim_local_rect
+                };
+
+                // This is how primitives get snapped. In general, snapping a picture's
+                // visible rect here will have no effect, but if it is rasterized in its
+                // own space, or it has a blur or drop shadow effect applied, it may
+                // provide a snapping offset.
+                let (snapped_visible_rect, snap_offsets) = get_snapped_rect(
+                    visible_rect,
+                    &map_local_to_raster,
+                    surface.device_pixel_scale,
+                ).unwrap_or((visible_rect, SnapOffsets::empty()));
+
+                let (combined_visible_rect, shadow_snap_offsets) = if !prim_shadow_rect.is_empty() {
+                    let (snapped_shadow_rect, shadow_snap_offsets) = get_snapped_rect(
+                        prim_shadow_rect,
+                        &map_local_to_raster,
+                        surface.device_pixel_scale,
+                    ).unwrap_or((prim_shadow_rect, SnapOffsets::empty()));
+
+                    (snapped_visible_rect.union(&snapped_shadow_rect), shadow_snap_offsets)
+                } else {
+                    (snapped_visible_rect, SnapOffsets::empty())
+                };
+
+                // Include the snapped primitive/picture local rect, including any shadows,
+                // in the area affected by the surface.
+                if let Some(rect) = map_local_to_surface.map(&combined_visible_rect) {
+                    surface_rect = surface_rect.union(&rect);
+                }
+
                 // When the debug display is enabled, paint a colored rectangle around each
                 // primitive.
                 if frame_context.debug_flags.contains(::api::DebugFlags::PRIMITIVE_DBG) {
                     let debug_color = match prim_instance.kind {
                         PrimitiveInstanceKind::Picture { .. } => ColorF::TRANSPARENT,
                         PrimitiveInstanceKind::TextRun { .. } => debug_colors::RED,
                         PrimitiveInstanceKind::LineDecoration { .. } => debug_colors::PURPLE,
                         PrimitiveInstanceKind::NormalBorder { .. } |
@@ -2002,16 +2093,18 @@ impl PrimitiveStore {
                 let vis_index = PrimitiveVisibilityIndex(frame_state.scratch.prim_info.len() as u32);
 
                 frame_state.scratch.prim_info.push(
                     PrimitiveVisibility {
                         clipped_world_rect,
                         clip_chain,
                         clip_task_index: ClipTaskIndex::INVALID,
                         combined_local_clip_rect,
+                        snap_offsets,
+                        shadow_snap_offsets,
                     }
                 );
 
                 prim_instance.visibility_info = vis_index;
 
                 self.request_resources_for_prim(
                     prim_instance,
                     surface,
@@ -2019,32 +2112,74 @@ impl PrimitiveStore {
                     &map_local_to_surface,
                     frame_context,
                     frame_state,
                 );
             }
         }
 
         let pic = &mut self.pictures[pic_index.0];
-
-        if let Some(RasterConfig { composite_mode: PictureCompositeMode::TileCache { .. }, .. }) = pic.raster_config {
-            let mut tile_cache = frame_state.tile_cache.take().unwrap();
-
-            // Build the dirty region(s) for this tile cache.
-            tile_cache.post_update(
-                frame_state.resource_cache,
-                frame_state.gpu_cache,
-                frame_context,
-                frame_state.scratch,
+        pic.prim_list = prim_list;
+
+        // If the local rect changed (due to transforms in child primitives) then
+        // invalidate the GPU cache location to re-upload the new local rect
+        // and stretch size. Drop shadow filters also depend on the local rect
+        // size for the extra GPU cache data handle.
+        // TODO(gw): In future, if we support specifying a flag which gets the
+        //           stretch size from the segment rect in the shaders, we can
+        //           remove this invalidation here completely.
+        if let Some(ref raster_config) = pic.raster_config {
+            // Inflate the local bounding rect if required by the filter effect.
+            // This inflaction factor is to be applied to the surface itself.
+            let inflation_size = match raster_config.composite_mode {
+                PictureCompositeMode::Filter(FilterOp::Blur(_)) => surface.inflation_factor,
+                PictureCompositeMode::Filter(FilterOp::DropShadow(_, blur_radius, _)) =>
+                    (blur_radius * BLUR_SAMPLE_SCALE).ceil(),
+                _ => 0.0,
+            };
+            surface_rect = surface_rect.inflate(inflation_size, inflation_size);
+
+            // Layout space for the picture is picture space from the
+            // perspective of its child primitives.
+            let pic_local_rect = surface_rect * TypedScale::new(1.0);
+            if pic.snapped_local_rect != pic_local_rect {
+                if let PictureCompositeMode::Filter(FilterOp::DropShadow(..)) = raster_config.composite_mode {
+                    frame_state.gpu_cache.invalidate(&pic.extra_gpu_data_handle);
+                }
+                // Invalidate any segments built for this picture, since the local
+                // rect has changed.
+                pic.segments_are_valid = false;
+                pic.snapped_local_rect = pic_local_rect;
+            }
+
+            if let PictureCompositeMode::TileCache { .. } = raster_config.composite_mode {
+                let mut tile_cache = frame_state.tile_cache.take().unwrap();
+
+                // Build the dirty region(s) for this tile cache.
+                tile_cache.post_update(
+                    frame_state.resource_cache,
+                    frame_state.gpu_cache,
+                    frame_context,
+                    frame_state.scratch,
+                );
+
+                pic.tile_cache = Some(tile_cache);
+            }
+
+            None
+        } else {
+            let parent_surface = &frame_context.surfaces[parent_surface_index.0 as usize];
+            let map_surface_to_parent_surface = SpaceMapper::new_with_target(
+                parent_surface.surface_spatial_node_index,
+                surface.surface_spatial_node_index,
+                PictureRect::max_rect(),
+                frame_context.clip_scroll_tree,
             );
-
-            pic.tile_cache = Some(tile_cache);
+            map_surface_to_parent_surface.map(&surface_rect)
         }
-
-        pic.prim_list = prim_list;
     }
 
     fn request_resources_for_prim(
         &mut self,
         prim_instance: &mut PrimitiveInstance,
         surface: &SurfaceInfo,
         raster_space: RasterSpace,
         map_local_to_surface: &SpaceMapper<LayoutPixel, PicturePixel>,
@@ -2943,17 +3078,17 @@ impl PrimitiveStore {
                     frame_state,
                     data_stores,
                 ) {
                     if let Some(ref mut splitter) = pic_state.plane_splitter {
                         PicturePrimitive::add_split_plane(
                             splitter,
                             frame_state.transforms,
                             prim_instance.spatial_node_index,
-                            pic.local_rect,
+                            pic.snapped_local_rect,
                             &prim_info.combined_local_clip_rect,
                             frame_context.screen_world_rect,
                             plane_split_anchor,
                         );
                     }
 
                     // If this picture uses segments, ensure the GPU cache is
                     // up to date with segment local rects.
@@ -3281,17 +3416,17 @@ impl PrimitiveInstance {
                     // building logic below will be run.
                     if !pic.segments_are_valid {
                         *segment_instance_index = SegmentInstanceIndex::INVALID;
                         pic.segments_are_valid = true;
                     }
 
                     // Override the prim local rect with the dynamically calculated
                     // local rect for the picture.
-                    prim_local_rect = pic.local_rect;
+                    prim_local_rect = pic.snapped_local_rect;
 
                     segment_instance_index
                 } else {
                     return;
                 }
             }
             PrimitiveInstanceKind::TextRun { .. } |
             PrimitiveInstanceKind::NormalBorder { .. } |
@@ -3353,17 +3488,16 @@ impl PrimitiveInstance {
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         prim_store: &PrimitiveStore,
         data_stores: &mut DataStores,
         segments_store: &mut SegmentStorage,
         segment_instances_store: &mut SegmentInstanceStorage,
         clip_mask_instances: &mut Vec<ClipMaskKind>,
         unclipped: &DeviceRect,
-        prim_snap_offsets: SnapOffsets,
         device_pixel_scale: DevicePixelScale,
     ) -> bool {
         let segments = match self.kind {
             PrimitiveInstanceKind::TextRun { .. } |
             PrimitiveInstanceKind::Clear { .. } |
             PrimitiveInstanceKind::LineDecoration { .. } => {
                 return false;
             }
@@ -3463,17 +3597,17 @@ impl PrimitiveInstance {
                 prim_info.clipped_world_rect,
                 root_spatial_node_index,
                 pic_context.surface_index,
                 pic_state,
                 frame_context,
                 frame_state,
                 &mut data_stores.clip,
                 unclipped,
-                prim_snap_offsets,
+                prim_info.snap_offsets,
                 device_pixel_scale,
             );
             clip_mask_instances.push(clip_mask_kind);
         } else {
             let dirty_world_rect = frame_state.current_dirty_region().combined.world_rect;
 
             for segment in segments {
                 // Build a clip chain for the smaller segment rect. This will
@@ -3505,17 +3639,17 @@ impl PrimitiveInstance {
                     prim_info.clipped_world_rect,
                     root_spatial_node_index,
                     pic_context.surface_index,
                     pic_state,
                     frame_context,
                     frame_state,
                     &mut data_stores.clip,
                     unclipped,
-                    prim_snap_offsets,
+                    prim_info.snap_offsets,
                     device_pixel_scale,
                 );
                 clip_mask_instances.push(clip_mask_kind);
             }
         }
 
         true
     }
@@ -3533,28 +3667,23 @@ impl PrimitiveInstance {
     ) {
         let prim_info = &mut scratch.prim_info[self.visibility_info.0 as usize];
         let device_pixel_scale = frame_state.surfaces[pic_context.surface_index.0].device_pixel_scale;
 
         if self.is_chased() {
             println!("\tupdating clip task with pic rect {:?}", prim_info.clip_chain.pic_clip_rect);
         }
 
-        // Get the device space rect for the primitive if it was unclipped,
-        // including any snap offsets applied to the corners.
-        let (unclipped, prim_snap_offsets) = match get_unclipped_device_rect(
-            self.spatial_node_index,
-            root_spatial_node_index,
+        // Get the device space rect for the primitive if it was unclipped.
+        let unclipped = match get_unclipped_device_rect(
             prim_info.clip_chain.pic_clip_rect,
             &pic_state.map_pic_to_raster,
             device_pixel_scale,
-            frame_context,
-            frame_state,
         ) {
-            Some(info) => info,
+            Some(rect) => rect,
             None => return,
         };
 
         self.build_segments_if_needed(
             &prim_info.clip_chain,
             frame_state,
             prim_store,
             data_stores,
@@ -3571,32 +3700,31 @@ impl PrimitiveInstance {
             frame_context,
             frame_state,
             prim_store,
             data_stores,
             &mut scratch.segments,
             &mut scratch.segment_instances,
             &mut scratch.clip_mask_instances,
             &unclipped,
-            prim_snap_offsets,
             device_pixel_scale,
         ) {
             if self.is_chased() {
                 println!("\tsegment tasks have been created for clipping");
             }
             return;
         }
 
         if prim_info.clip_chain.needs_mask {
             // Get a minimal device space rect, clipped to the screen that we
             // need to allocate for the clip mask, as well as interpolated
             // snap offsets.
             if let Some((device_rect, snap_offsets)) = get_clipped_device_rect(
                 &unclipped,
-                prim_snap_offsets,
+                prim_info.snap_offsets,
                 &pic_state.map_raster_to_world,
                 prim_info.clipped_world_rect,
                 device_pixel_scale,
             ) {
                 let clip_task = RenderTask::new_mask(
                     device_rect,
                     prim_info.clip_chain.clips_range,
                     root_spatial_node_index,
@@ -3626,25 +3754,24 @@ impl PrimitiveInstance {
 }
 
 /// Mimics the GLSL mix() function.
 fn mix(x: f32, y: f32, a: f32) -> f32 {
     x * (1.0 - a) + y * a
 }
 
 /// Given a point within a local rectangle, and the device space corners
-/// of a snapped primitive, return the snap offsets. This *must* exactly
-/// match the logic in the GLSL compute_snap_offset_impl function.
-fn compute_snap_offset_impl(
-    reference_pos: PicturePoint,
-    reference_rect: PictureRect,
+/// of a snapped primitive, return the snap offsets.
+fn compute_snap_offset_impl<PixelSpace>(
+    reference_pos: TypedPoint2D<f32, PixelSpace>,
+    reference_rect: TypedRect<f32, PixelSpace>,
     prim_top_left: DevicePoint,
     prim_bottom_right: DevicePoint,
 ) -> DeviceVector2D {
-    let normalized_snap_pos = PicturePoint::new(
+    let normalized_snap_pos = TypedPoint2D::<f32, PixelSpace>::new(
         (reference_pos.x - reference_rect.origin.x) / reference_rect.size.width,
         (reference_pos.y - reference_rect.origin.y) / reference_rect.size.height,
     );
 
     let top_left = DeviceVector2D::new(
         (prim_top_left.x + 0.5).floor() - prim_top_left.x,
         (prim_top_left.y + 0.5).floor() - prim_top_left.y,
     );
@@ -3655,69 +3782,65 @@ fn compute_snap_offset_impl(
     );
 
     DeviceVector2D::new(
         mix(top_left.x, bottom_right.x, normalized_snap_pos.x),
         mix(top_left.y, bottom_right.y, normalized_snap_pos.y),
     )
 }
 
+/// Given the snapping offsets for a primitive rectangle, recompute
+/// the snapping offsets to be relative to given local rectangle.
+/// This *must* exactly match the logic in the GLSL
+/// compute_snap_offset function.
+pub fn recompute_snap_offsets<PixelSpace>(
+    local_rect: TypedRect<f32, PixelSpace>,
+    prim_rect: TypedRect<f32, PixelSpace>,
+    snap_offsets: SnapOffsets,
+) -> SnapOffsets
+{
+    if prim_rect.is_empty() || snap_offsets.is_empty() {
+        return SnapOffsets::empty();
+    }
+
+    let normalized_top_left = TypedPoint2D::<f32, PixelSpace>::new(
+        (local_rect.origin.x - prim_rect.origin.x) / prim_rect.size.width,
+        (local_rect.origin.y - prim_rect.origin.y) / prim_rect.size.height,
+    );
+
+    let normalized_bottom_right = TypedPoint2D::<f32, PixelSpace>::new(
+        (local_rect.origin.x + local_rect.size.width - prim_rect.origin.x) / prim_rect.size.width,
+        (local_rect.origin.y + local_rect.size.height - prim_rect.origin.y) / prim_rect.size.height,
+    );
+
+    let top_left = DeviceVector2D::new(
+        mix(snap_offsets.top_left.x, snap_offsets.bottom_right.x, normalized_top_left.x),
+        mix(snap_offsets.top_left.y, snap_offsets.bottom_right.y, normalized_top_left.y),
+    );
+
+    let bottom_right = DeviceVector2D::new(
+        mix(snap_offsets.top_left.x, snap_offsets.bottom_right.x, normalized_bottom_right.x),
+        mix(snap_offsets.top_left.y, snap_offsets.bottom_right.y, normalized_bottom_right.y),
+    );
+
+    SnapOffsets {
+        top_left,
+        bottom_right,
+    }
+}
+
 /// Retrieve the exact unsnapped device space rectangle for a primitive.
-/// If the transform is axis-aligned, compute the snapping offsets that
-/// the shaders will apply.
 fn get_unclipped_device_rect(
-    prim_spatial_node_index: SpatialNodeIndex,
-    root_spatial_node_index: SpatialNodeIndex,
     prim_rect: PictureRect,
     map_to_raster: &SpaceMapper<PicturePixel, RasterPixel>,
     device_pixel_scale: DevicePixelScale,
-    frame_context: &FrameBuildingContext,
-    frame_state: &mut FrameBuildingState,
-) -> Option<(DeviceRect, SnapOffsets)> {
-    let unclipped_raster_rect = map_to_raster.map(&prim_rect)?;
-
-    let unclipped_device_rect = {
-        let world_rect = unclipped_raster_rect * TypedScale::new(1.0);
-        let device_rect = world_rect * device_pixel_scale;
-        device_rect
-    };
-
-    let transform_id = frame_state.transforms.get_id(
-        prim_spatial_node_index,
-        root_spatial_node_index,
-        frame_context.clip_scroll_tree,
-    );
-
-    match transform_id.transform_kind() {
-        TransformedRectKind::AxisAligned => {
-            let top_left = compute_snap_offset_impl(
-                prim_rect.origin,
-                prim_rect,
-                unclipped_device_rect.origin,
-                unclipped_device_rect.bottom_right(),
-            );
-
-            let bottom_right = compute_snap_offset_impl(
-                prim_rect.bottom_right(),
-                prim_rect,
-                unclipped_device_rect.origin,
-                unclipped_device_rect.bottom_right(),
-            );
-
-            let snap_offsets = SnapOffsets {
-                top_left,
-                bottom_right,
-            };
-
-            Some((unclipped_device_rect, snap_offsets))
-        }
-        TransformedRectKind::Complex => {
-            Some((unclipped_device_rect, SnapOffsets::empty()))
-        }
-    }
+) -> Option<DeviceRect> {
+    let raster_rect = map_to_raster.map(&prim_rect)?;
+    let world_rect = raster_rect * TypedScale::new(1.0);
+    Some(world_rect * device_pixel_scale)
 }
 
 /// Given an unclipped device rect, try to find a minimal device space
 /// rect to allocate a clip mask for, by clipping to the screen. This
 /// function is very similar to get_raster_rects below. It is far from
 /// ideal, and should be refactored as part of the support for setting
 /// scale per-raster-root.
 fn get_clipped_device_rect(
@@ -3805,16 +3928,71 @@ pub fn get_raster_rects(
     // Ensure that we won't try to allocate a zero-sized clip render task.
     if clipped.is_empty() {
         return None;
     }
 
     Some((clipped.to_i32(), unclipped))
 }
 
+/// Snap the given rect in raster space if the transform is
+/// axis-aligned. It return the snapped rect transformed back into the
+/// given pixel space, and the snap offsets in device space.
+pub fn get_snapped_rect<PixelSpace>(
+    prim_rect: TypedRect<f32, PixelSpace>,
+    map_to_raster: &SpaceMapper<PixelSpace, RasterPixel>,
+    device_pixel_scale: DevicePixelScale,
+) -> Option<(TypedRect<f32, PixelSpace>, SnapOffsets)> where PixelSpace: fmt::Debug {
+    let is_axis_aligned = match map_to_raster.kind {
+        CoordinateSpaceMapping::Local |
+        CoordinateSpaceMapping::ScaleOffset(..) => true,
+        CoordinateSpaceMapping::Transform(ref transform) => transform.preserves_2d_axis_alignment(),
+    };
+
+    if is_axis_aligned {
+       let raster_rect = map_to_raster.map(&prim_rect)?;
+
+       let device_rect = {
+            let world_rect = raster_rect * TypedScale::new(1.0);
+            world_rect * device_pixel_scale
+        };
+
+        let top_left = compute_snap_offset_impl(
+            prim_rect.origin,
+            prim_rect,
+            device_rect.origin,
+            device_rect.bottom_right(),
+        );
+
+        let bottom_right = compute_snap_offset_impl(
+            prim_rect.bottom_right(),
+            prim_rect,
+            device_rect.origin,
+            device_rect.bottom_right(),
+        );
+
+        let snap_offsets = SnapOffsets {
+            top_left,
+            bottom_right,
+        };
+
+        let snapped_device_rect = DeviceRect::new(
+            device_rect.origin + top_left,
+            device_rect.size + (bottom_right - top_left).to_size()
+        );
+
+        let snapped_world_rect = snapped_device_rect / device_pixel_scale;
+        let snapped_raster_rect = snapped_world_rect * TypedScale::new(1.0);
+        let snapped_prim_rect = map_to_raster.unmap(&snapped_raster_rect)?;
+        Some((snapped_prim_rect, snap_offsets))
+    } else {
+        None
+    }
+}
+
 /// Get the inline (horizontal) and block (vertical) sizes
 /// for a given line decoration.
 pub fn get_line_decoration_sizes(
     rect_size: &LayoutSize,
     orientation: LineOrientation,
     style: LineStyle,
     wavy_line_thickness: f32,
 ) -> Option<(f32, f32)> {
index 543ffe227fc5cb9a6d592215e14d4763f9dad1f7..7e36607b04bcceffac9372814d976f1f9f969ba4
GIT binary patch
literal 11151
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
index 4ce47d9b6980e76bdfa3cadca09402b0cb9f1c24..102ed077b6862077b415096aa92e79714acb2265
GIT binary patch
literal 3830
zc%1E5e^8Ql7=Mil>-<$$rmdjtnw@pnvKqG(QYlw0qv@#=OB|;z-(6yDDpNvpS7|2m
zig|7&b!^KeO0EdaRCb<&ITvdcBo38Cxl%weKtyC;&}!{knz{e{gZn=3=bjfn&+~k~
z&-0K^3=eZ%xO^c5L9V;rCwvG&PG<12I?o4)x9>bX1i97kA_PZfp4TWI<+DhhA|-u)
z0CDr&lO8KVsLsK6ZVOk>J?RpA$MJaSdI)hdf~7q`{UYMCv?A(9mNLPZnkH;Ea?sL|
z)k{*X26mMbltw^7A^CyOmy2nT^IARRv0LS|>YNy{eO)m!hv?%DU7B^6b(nRSb#OQc
z2W0Vd?w~abzP!&QnlG#38<n99?w~#q7Ni0|QpPuS1v9v5LrE|%9sm|g<$;<%(C~Te
z9l3=4t*su3Igp|mQjf)SYucq%@j9j}Dkw$s>sbJ371A<10C4maO$!SE{cX~QV;UwN
zn^DD2?Hr+;VWLTO*|qqsH5okqGJgP^bSGtv*5J2hnyUD3y$gWT%Si3*eN0!kmd_tY
z!i@}5BX_W853H5|E`q~+WA9$T_Q@gGqyqr&AU|Cl4%iw}U|lBw@W-yF_kI9?0iAUl
z0p+!I$37Kvv6({{1Nrbwh<IW*@Vfy<BQJ6blO*P7t~>}UMxH3(%(++opH?1v%*@7H
zzgoF5x*1n6dA2vM81<~RneV3lKBsr$`z4>u+2Uv1ugbmjupFAMrMzE(i#pt?MLhVX
zfWBn%ijy;qTqet6vOqU}_!zwd<>iOtEO<qnQ?*Efkr<lwraKw7)0$L~xe1I$CTmDN
z(=^WdT4KzGiU}II%7HD|(MIPkc62p!3RlCW1Y@ZFNgzXB-LllOPA{YmSHxlvpE@eM
zF>=Myi#w-j-O7@QS`2-3h!pXZ2zKX~!nU2yFyKq=4}>yr0*dT3>G8mci855{httrf
z*XUcXwu+<sIbuX;bMj{OVp>v6l4|{gK3do6TUY6YZsl0NqxpdWrvk4c{EAxQd?YSD
z?)^ChoJ}R-fIKl`+r^i<Y88Ey9`g{+GfY*QP+H8hC%0%(aq@}8oejsu2+=9W%Nug0
zdTi^udl?sniPuunKp0<po9$vHmk$^A+8O;;m_3m&R_#QTOT!W|#mV!Fk?Gr;_m}Qb
zl?JPFR}#}AdqI>QG&1s~uEN@*bZ)j)E_!$>FknM~-ciJNe8%(SLF@8bWPdqlp2zDQ
z;McMfV9a>*<7#bS1%doEZCVp=ucynj*#5(H;86s6eTl!R8v8sO)WV4%UPm6!RASNs
zm5kE<UzMPO9#eV-yWd<Tkf>0MXsXMgbK@F2p58v<p!eQs-?-;i+|7HUmX3pHp)}H-
zgh}UQlM<#5!z3Y|=O}e}SwE#m#>3@Q{^;m1O*RtMn_TzCjr(5>aMF?ObyxwCZRmSO
zWQOKP+cjL2wo^Ct`C+)iWVH)sx^crYFh6V!Q=T765fc{`&VD-g?5*T&B4?aY(ZW|)
zZfKUxWVNsv!|E<U5`7pfGPknZ<eGMC_0QLmD}yot@Yk(2d7lgbW5?iSm?xs;zTwx6
zL+W0JE_Y8!tYx$oFU_>v;P=Tal)NxzY?l5zytF1uz*kEEu#X*EcjSA#6q|K}&x^No
zyWhS5YuW{1_vsc%K?h{IY89}i`7{8OO;Qvd03G$P=9e=7c-Sm88b>I3c}Fh1cVDjS
zwEDx@=0D};A;6`QR9R2Ei-rT~a=Fz4fQsl!MJ)j0>POU_J{bd{>B+YNSEQ|@F%;<Q
l#m(l0wFhdMnFVshe?jmep36CvFZe$T?b;Des0tw;{~ID<5^Vqg
index a057fddec5c0ac561376e79436b41cc6fae447d7..dba46d8899047ff1a09ca3b7ab8cd17ded292138
GIT binary patch
literal 3832
zc%1E5YfO_@82+Hx7J|0Mg`q<~FUfRs(`IIhQND_@Fc-Bb#UjXB)<iZd!dz@YdZCrT
zoWm~J6t7g{CIUhMTP_x8tIlTYv|EtUpwu!aj6$U<z0m7U+c9*SP|=_KXwG+@JbB;e
zocD6x9!lDhI6Y!c1O!3TH-4P34T3^k;OD`j0g`{RH5!6u$Tud$lk-lSdIl<w?ub(L
zW{^|R-xM40f(_Ui3zl@xjx92L7r%6le;ZqL_H*>RJ<h8Gr6UhwdTXSEtxX;cYFQ)f
zuG8XrAM5XVIzEI9@Md$!{6Yh29i+q*LINBV5(Yz|SOf~gvmxw!JA_|1MVKN?5vB-!
zLZsZSRYg(TCCs5z-catN&lqB=O%+8dmoQCfydkqWlhKp|07|XII<x}-kLZjF5&-00
zeRori%-e>!Ri15fJ3YETe?F{TmSGgmeW=eidE5ZFQL3F62LOG0w#hXLfa`~}2*)U-
z#^yIjH6kbd@T#1&HLO}9vpN5=R3!w!8djQ&B_=ZSMjNE+G61Z|N-MAdM(mO<5wFpD
zKhdj>f)NSRNCt8(yBQi05QCd1VGeEw>PEg`bcq4*<}!)Zm;wL`gP{@v0OQrmKQZ$&
zDbO@8K}p<X#?4~W_A{p?fe+WUFo0pU_xD3rBI~G~&Lwt55{Fj!pvtz@%s=N)K<bp?
z^9Ixg3qRyowLVdaDLx3tI7C!;;XDGxf<#Pw|AZJ<qs%+nnPUs>tMXeglJY~JGiyYl
z-lMb$L0CF5=+xQ+@H9BO3Kv<o)8~XhhNEdMM$Q{-+C_w**yq;?P7J@*Rci?u+8msa
zwQQa0Mol^gDo74E5<L!^wD<8_>NR#EE6d{sQMyw;&V<QGv)k*1rO6cz$a*2*i1;9U
z(%#2obPeW>)IV|;zzRLtzt?mb`n?$tPad4C*n&o3DQoyYSzOP_GBxB(d39mxGR~fz
zS8ikKR{1zS75qhi-E-r}PKx)5X0cr{bD{k=AaCiSiI#Q8Wh|$znI#r>&SdXO%`#U9
zTqT!V!bG#Rvt46yeA_S>o6MmZ!;uwO1b@MyxIK%#3(uav$-=K(957}?C6ml2;>~<Q
zC+0%AVZ}iMDs!)Xh<^W%^T_r@^NGweO0h3Lj^LG9#rx6nlLl1c*M8ciZ-ZSj_3bA5
z{ktvC81&VEcGlA;2vS`YZd05x3$4UlCBw%L2sDubT+!<M24N{l<Vc+xQ0WU(?E1ic
z>=xi+7Wip(AH3vJROxA;#EqMbu9Ej;ke)t6koqcL5RN|69c1OLGlTT~<8d=!|BY!l
zl3RrJ(G<T1yWso!ZXzo|%YE7)?V{(0tY=OTq`DU-%ZoB^Q9|H8-hPQ3h7`o2=ZZp+
z<bH<~R5eY|GjElHZ0nIBg>98S&V(;vu}G}cP)9>O+(1)R!JIv8uYOFH`H6SoPP9(^
z>cS8%sH@wR!3(RVICXi}OE_9i)mV{F-urmh`rj)s@t8moS?j+vrH!@K4VH?v{7HKs
z@0rbc;1!^V$J*_LvrKe57erQ9KX!q^KcifP;2l<%QN4!Wl{vNp?kmK`yvILE8C?!~
zNeM1p#*q;loA>=B&C&os2&b>%0EXRd)3$T&xQF9%t$P^5_rM^r{8I^YU>le{Y<n5D
zVF0Y!E3y8)6^t6zY(~EZ0E@RstOLnB*VSLXui<#^!s)H~9o;Qox$CdpJXI6N1;E{w
zj_$Ss0I17qGTH%fdUZ#)j0J%B7LV()6ec>Tjdt@nDR>|0CDoMl0#7TDYd$MYZvsFC
zDP6{G19EeFyX2;a2F%D2cv$%U3{M;HD5$AgD8iE|;2k%>pdgJy5odiLCE)<WL9XoA
dH&NP`KfIoO>6o#j5B#HrHh#1vp>{n@@DE?_?`Hr2
--- a/js/public/SavedFrameAPI.h
+++ b/js/public/SavedFrameAPI.h
@@ -123,16 +123,27 @@ extern JS_PUBLIC_API SavedFrameResult Ge
  * it is the oldest frame in the stack. The `parentp` out parameter is _NOT_
  * guaranteed to be in the cx's compartment. Defaults to nullptr.
  */
 extern JS_PUBLIC_API SavedFrameResult GetSavedFrameParent(
     JSContext* cx, JSPrincipals* principals, Handle<JSObject*> savedFrame,
     MutableHandle<JSObject*> parentp,
     SavedFrameSelfHosted selfHosted = SavedFrameSelfHosted::Include);
 
+/**
+ * Given a SavedFrame object, convert it and its transitive parents to plain
+ * objects. Because SavedFrame objects store their properties on the prototype,
+ * they cannot be usefully stringified to JSON. Assigning their properties to
+ * plain objects allow those objects to be stringified and the saved frame stack
+ * can be encoded as a string.
+ */
+JS_PUBLIC_API JSObject* ConvertSavedFrameToPlainObject(
+    JSContext* cx, JS::HandleObject savedFrame,
+    JS::SavedFrameSelfHosted selfHosted);
+
 }  // namespace JS
 
 namespace js {
 
 /**
  * Get the first SavedFrame object in this SavedFrame stack whose principals are
  * subsumed by the given |principals|. If there is no such frame, return
  * nullptr.
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -1083,16 +1083,71 @@ JS_PUBLIC_API bool IsMaybeWrappedSavedFr
   return obj->canUnwrapAs<js::SavedFrame>();
 }
 
 JS_PUBLIC_API bool IsUnwrappedSavedFrame(JSObject* obj) {
   MOZ_ASSERT(obj);
   return obj->is<js::SavedFrame>();
 }
 
+static bool AssignProperty(JSContext* cx, HandleObject dst, HandleObject src,
+                           const char* property) {
+  RootedValue v(cx);
+  return JS_GetProperty(cx, src, property, &v) &&
+         JS_DefineProperty(cx, dst, property, v, JSPROP_ENUMERATE);
+}
+
+JS_PUBLIC_API JSObject* ConvertSavedFrameToPlainObject
+    (JSContext* cx, HandleObject savedFrameArg, SavedFrameSelfHosted selfHosted) {
+  MOZ_ASSERT(savedFrameArg);
+
+  RootedObject savedFrame(cx, savedFrameArg);
+  RootedObject baseConverted(cx), lastConverted(cx);
+  RootedValue v(cx);
+
+  baseConverted = lastConverted = JS_NewObject(cx, nullptr);
+  if (!baseConverted) {
+    return nullptr;
+  }
+
+  bool foundParent;
+  do {
+    if (!AssignProperty(cx, lastConverted, savedFrame, "source") ||
+        !AssignProperty(cx, lastConverted, savedFrame, "sourceId") ||
+        !AssignProperty(cx, lastConverted, savedFrame, "line") ||
+        !AssignProperty(cx, lastConverted, savedFrame, "column") ||
+        !AssignProperty(cx, lastConverted, savedFrame, "functionDisplayName") ||
+        !AssignProperty(cx, lastConverted, savedFrame, "asyncCause")) {
+      return nullptr;
+    }
+
+    const char* parentProperties[] = { "parent", "asyncParent" };
+    foundParent = false;
+    for (const char* prop : parentProperties) {
+      if (!JS_GetProperty(cx, savedFrame, prop, &v)) {
+        return nullptr;
+      }
+      if (v.isObject()) {
+        RootedObject nextConverted(cx, JS_NewObject(cx, nullptr));
+        if (!nextConverted ||
+            !JS_DefineProperty(cx, lastConverted, prop, nextConverted,
+                               JSPROP_ENUMERATE)) {
+          return nullptr;
+        }
+        lastConverted = nextConverted;
+        savedFrame = &v.toObject();
+        foundParent = true;
+        break;
+      }
+    }
+  } while (foundParent);
+
+  return baseConverted;
+}
+
 } /* namespace JS */
 
 namespace js {
 
 /* static */
 bool SavedFrame::sourceProperty(JSContext* cx, unsigned argc, Value* vp) {
   THIS_SAVEDFRAME(cx, argc, vp, "(get source)", args, frame);
   JSPrincipals* principals = cx->realm()->principals();
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -7227,25 +7227,26 @@ gCSSProperties["grid-template"] = {
     "[foo] [bar] 40px / 100px",
     "[fizz] [buzz] 100px / 40px",
     "[fizz] [buzz] 'foo' / 40px",
     "'foo' / none"
   ]
 };
 if (isGridTemplateSubgridValueEnabled) {
   gCSSProperties["grid-template"].other_values.push(
-    "subgrid",
+    "subgrid / subgrid",
     "subgrid/40px 20px",
     "subgrid [foo] [] [bar baz] / 40px 20px",
     "40px 20px/subgrid",
     "40px 20px/subgrid  [foo] [] repeat(3, [a] [b]) [bar baz]",
     "subgrid/subgrid",
     "subgrid [foo] [] [bar baz]/subgrid [foo] [] [bar baz]"
   );
   gCSSProperties["grid-template"].invalid_values.push(
+    "subgrid",
     "subgrid []",
     "subgrid [] / 'fizz'",
     "subgrid / 'fizz'"
   );
 }
 
 gCSSProperties["grid"] = {
   domProp: "grid",
--- a/layout/style/test/test_grid_container_shorthands.html
+++ b/layout/style/test/test_grid_container_shorthands.html
@@ -141,21 +141,16 @@ var grid_template_test_cases = [
     },
     {
         specified: "[bar] 'fizz' 100px [buzz] \n [a] '.' 200px [b] / [foo] 40px",
         gridTemplateAreas: "\"fizz\" \".\"",
         gridTemplateRows: "[bar] 100px [buzz a] 200px [b]",
         gridTemplateColumns: "[foo] 40px",
     },
     {
-        specified: "subgrid",
-        gridTemplateColumns: isGridTemplateSubgridValueEnabled ? "subgrid" : "none",
-        gridTemplateRows: isGridTemplateSubgridValueEnabled ? "subgrid" : "none",
-    },
-    {
         specified: "subgrid / subgrid",
         gridTemplateColumns: isGridTemplateSubgridValueEnabled ? "subgrid" : "none",
         gridTemplateRows: isGridTemplateSubgridValueEnabled ? "subgrid" : "none",
     },
     {
         specified: "subgrid [foo] / subgrid",
         gridTemplateColumns: isGridTemplateSubgridValueEnabled ? "subgrid" : "none",
         gridTemplateRows: isGridTemplateSubgridValueEnabled ? "subgrid [foo]" : "none",
--- a/security/nss/TAG-INFO
+++ b/security/nss/TAG-INFO
@@ -1,1 +1,1 @@
-4e4eb31ce200
+NSS_3_44_BETA1
--- a/security/nss/cmd/btoa/btoa.c
+++ b/security/nss/cmd/btoa/btoa.c
@@ -115,16 +115,17 @@ main(int argc, char **argv)
 
     progName = strrchr(argv[0], '/');
     if (!progName)
         progName = strrchr(argv[0], '\\');
     progName = progName ? progName + 1 : argv[0];
 
     /* Parse command line arguments */
     optstate = PL_CreateOptState(argc, argv, "i:o:w:");
+    PORT_Assert(optstate);
     while ((status = PL_GetNextOpt(optstate)) == PL_OPT_OK) {
         switch (optstate->option) {
             default:
                 Usage(progName);
                 goto loser;
                 break;
 
             case 'i':
@@ -198,19 +199,17 @@ main(int argc, char **argv)
                 progName, PORT_GetError(), errno);
         goto loser;
     }
     if (suffix) {
         fprintf(outFile, "-----END %s-----\n", suffix);
     }
     exitCode = 0;
 loser:
-    if (optstate) {
-        PL_DestroyOptState(optstate);
-    }
+    PL_DestroyOptState(optstate);
     if (inFile && closeIn) {
         fclose(inFile);
     }
     if (outFile && closeOut) {
         fclose(outFile);
     }
     return exitCode;
 }
--- a/security/nss/cmd/pk11importtest/pk11importtest.c
+++ b/security/nss/cmd/pk11importtest/pk11importtest.c
@@ -180,17 +180,17 @@ cleanup:
     SECKEY_DestroyPublicKey(pubKey);
     SECKEY_DestroyPrivateKey(privKey);
     fprintf(stderr, "%s PrivateKeyImport %s ***********************\n",
             testname, keyFound ? "PASSED" : "FAILED");
     return keyFound ? SECSuccess : SECFailure;
 }
 
 static const char *const usageInfo[] = {
-    "pk11import - test PK11_PrivateKeyImport()"
+    "pk11import - test PK11_PrivateKeyImport()",
     "Options:",
     " -d certdir            directory containing cert database",
     " -k keysize            size of the rsa, dh, and dsa key to test (default 1024)",
     " -C ecc_curve          ecc curve (default )",
     " -f pwFile             file to fetch the password from",
     " -p pwString           password",
     " -r                    skip rsa test",
     " -D                    skip dsa test",
new file mode 100644
--- /dev/null
+++ b/security/nss/coreconf/check_cc.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+
+import os
+import subprocess
+import sys
+
+def main():
+    if sys.platform == 'win32' or len(sys.argv) < 2:
+        print(0)
+    else:
+        cc = os.environ.get('CC', 'cc')
+        try:
+            cc_is_arg = sys.argv[1] in subprocess.check_output(
+              [cc, '--version'], universal_newlines=True)
+        except OSError:
+            # We probably just don't have CC/cc.
+            cc_is_arg = False
+        print(int(cc_is_arg))
+
+if __name__ == '__main__':
+    main()
+
deleted file mode 100644
--- a/security/nss/coreconf/check_cc_clang.py
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/env python
-
-import os
-import subprocess
-import sys
-
-def main():
-    if sys.platform == 'win32':
-        print(0)
-    else:
-        cc = os.environ.get('CC', 'cc')
-        try:
-            cc_is_clang = 'clang' in subprocess.check_output(
-              [cc, '--version'], universal_newlines=True)
-        except OSError:
-            # We probably just don't have CC/cc.
-            cc_is_clang = False
-        print(int(cc_is_clang))
-
-if __name__ == '__main__':
-    main()
--- a/security/nss/coreconf/config.gypi
+++ b/security/nss/coreconf/config.gypi
@@ -59,20 +59,25 @@
               'dll_suffix': 'dylib',
             }, {
               'moz_debug_flags%': '-gdwarf-2',
               'dll_suffix': 'so',
             }],
           ],
         }],
         ['"<(GENERATOR)"=="ninja"', {
-          'cc_is_clang%': '<!(<(python) <(DEPTH)/coreconf/check_cc_clang.py)',
+          'cc_is_clang%': '<!(<(python) <(DEPTH)/coreconf/check_cc.py clang)',
         }, {
           'cc_is_clang%': '0',
         }],
+        ['"<(GENERATOR)"=="ninja"', {
+          'cc_is_gcc%': '<!(<(python) <(DEPTH)/coreconf/check_cc.py gcc)',
+        }, {
+          'cc_is_gcc%': '0',
+        }],
       ],
     },
     # Copy conditionally-set variables out one scope.
     'python%': '<(python)',
     'host_arch%': '<(host_arch)',
     'target_arch%': '<(target_arch)',
     'use_system_zlib%': '<(use_system_zlib)',
     'zlib_libs%': ['<@(zlib_libs)'],
@@ -81,16 +86,17 @@
     'nspr_lib_dir%': '<(nspr_lib_dir)',
     'nspr_include_dir%': '<(nspr_include_dir)',
     'use_system_sqlite%': '<(use_system_sqlite)',
     'sqlite_libs%': ['-lsqlite3'],
     'dll_prefix': '<(dll_prefix)',
     'dll_suffix': '<(dll_suffix)',
     'freebl_name': '<(freebl_name)',
     'cc_is_clang%': '<(cc_is_clang)',
+    'cc_is_gcc%': '<(cc_is_gcc)',
     'cc_use_gnu_ld%': '<(cc_use_gnu_ld)',
     # Some defaults
     'disable_tests%': 0,
     'disable_chachapoly%': 0,
     'disable_dbm%': 0,
     'disable_libpkix%': 1,
     'disable_werror%': 0,
     'mozilla_client%': 0,
--- a/security/nss/coreconf/coreconf.dep
+++ b/security/nss/coreconf/coreconf.dep
@@ -5,8 +5,9 @@
 
 /*
  * A dummy header file that is a dependency for all the object files.
  * Used to force a full recompilation of NSS in Mozilla's Tinderbox
  * depend builds.  See comments in rules.mk.
  */
 
 #error "Do not include this header file."
+
--- a/security/nss/gtests/freebl_gtest/freebl_gtest.gyp
+++ b/security/nss/gtests/freebl_gtest/freebl_gtest.gyp
@@ -35,16 +35,23 @@
         'ghash_unittest.cc',
         'rsa_unittest.cc',
         '<(DEPTH)/gtests/common/gtests.cc'
       ],
       'dependencies': [
         'freebl_gtest_deps',
         '<(DEPTH)/exports.gyp:nss_exports',
       ],
+      'conditions': [
+      [ 'cc_is_gcc==1 and (target_arch=="ia32" or target_arch=="x64")', {
+         'cflags_cc': [
+          '-msse2',
+          ],
+        }],
+      ],
     },
     {
       'target_name': 'prng_gtest',
       'type': 'executable',
       'sources': [
         'prng_kat_unittest.cc',
       ],
       'dependencies': [
--- a/security/nss/gtests/freebl_gtest/mpi_unittest.cc
+++ b/security/nss/gtests/freebl_gtest/mpi_unittest.cc
@@ -1,16 +1,17 @@
 // 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 "gtest/gtest.h"
 
 #include <stdint.h>
 #include <string.h>
+#include <memory>
 
 #ifdef __MACH__
 #include <mach/clock.h>
 #include <mach/mach.h>
 #endif
 
 #include "mpi.h"
 namespace nss_test {
@@ -22,17 +23,17 @@ void gettime(struct timespec* tp) {
 
   host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock);
   clock_get_time(cclock, &mts);
   mach_port_deallocate(mach_task_self(), cclock);
 
   tp->tv_sec = mts.tv_sec;
   tp->tv_nsec = mts.tv_nsec;
 #else
-  clock_gettime(CLOCK_MONOTONIC, tp);
+  ASSERT_NE(0, timespec_get(tp, TIME_UTC));
 #endif
 }
 
 class MPITest : public ::testing::Test {
  protected:
   void TestCmp(const std::string a_string, const std::string b_string,
                int result) {
     mp_int a, b;
@@ -79,31 +80,32 @@ class MPITest : public ::testing::Test {
     }
     std::cerr << std::endl << std::resetiosflags(flags);
   }
 
   void TestToFixedOctets(const std::vector<uint8_t>& ref, size_t len) {
     mp_int a;
     ASSERT_EQ(MP_OKAY, mp_init(&a));
     ASSERT_EQ(MP_OKAY, mp_read_unsigned_octets(&a, ref.data(), ref.size()));
-    uint8_t buf[len];
-    ASSERT_EQ(MP_OKAY, mp_to_fixlen_octets(&a, buf, len));
+    std::unique_ptr<uint8_t[]> buf(new uint8_t[len]);
+    ASSERT_NE(buf, nullptr);
+    ASSERT_EQ(MP_OKAY, mp_to_fixlen_octets(&a, buf.get(), len));
     size_t compare;
     if (len > ref.size()) {
       for (size_t i = 0; i < len - ref.size(); ++i) {
         ASSERT_EQ(0U, buf[i]) << "index " << i << " should be zero";
       }
       compare = ref.size();
     } else {
       compare = len;
     }
     dump("value", ref.data(), ref.size());
-    dump("output", buf, len);
-    ASSERT_EQ(0, memcmp(buf + len - compare, ref.data() + ref.size() - compare,
-                        compare))
+    dump("output", buf.get(), len);
+    ASSERT_EQ(0, memcmp(buf.get() + len - compare,
+                        ref.data() + ref.size() - compare, compare))
         << "comparing " << compare << " octets";
     mp_clear(&a);
   }
 };
 
 TEST_F(MPITest, MpiCmp01Test) { TestCmp("0", "1", -1); }
 TEST_F(MPITest, MpiCmp10Test) { TestCmp("1", "0", 1); }
 TEST_F(MPITest, MpiCmp00Test) { TestCmp("0", "0", 0); }
--- a/security/nss/gtests/freebl_gtest/rsa_unittest.cc
+++ b/security/nss/gtests/freebl_gtest/rsa_unittest.cc
@@ -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 "gtest/gtest.h"
 
 #include <stdint.h>
+#include <memory>
 
 #include "blapi.h"
 #include "secitem.h"
 
 template <class T>
 struct ScopedDelete {
   void operator()(T* ptr) {
     if (ptr) {
--- a/security/nss/gtests/mozpkix_gtest/pkixder_input_tests.cpp
+++ b/security/nss/gtests/mozpkix_gtest/pkixder_input_tests.cpp
@@ -354,16 +354,17 @@ TEST_F(pkixder_input_tests, Skip_WrapAro
 {
   // The original implementation of our buffer read overflow checks was
   // susceptible to integer overflows which could make the checks ineffective.
   // This attempts to verify that we've fixed that. Unfortunately, decrementing
   // a null pointer is undefined behavior according to the C++ language spec.,
   // but this should catch the problem on at least some compilers, if not all of
   // them.
   const uint8_t* der = nullptr;
+  // coverity[FORWARD_NULL]
   --der;
   Input buf;
   ASSERT_EQ(Success, buf.Init(der, 0));
   Reader input(buf);
   ASSERT_EQ(Result::ERROR_BAD_DER, input.Skip(1));
 }
 
 TEST_F(pkixder_input_tests, Skip_ToInputPastEnd)
--- a/security/nss/gtests/ssl_gtest/ssl_0rtt_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_0rtt_unittest.cc
@@ -644,14 +644,235 @@ TEST_P(TlsConnectTls13, ZeroRttOrdering)
   buf.resize(10);
   read = PR_Read(server_->ssl_fd(), buf.data(), buf.size());
   ASSERT_EQ(static_cast<PRInt32>(late_data.size()), read);
   buf.resize(read);
   EXPECT_EQ(late_data, buf);
   EXPECT_EQ(2U, step);
 }
 
+// Early data remains available after the handshake completes for TLS.
+TEST_F(TlsConnectStreamTls13, ZeroRttLateReadTls) {
+  SetupForZeroRtt();
+  client_->Set0RttEnabled(true);
+  server_->Set0RttEnabled(true);
+  ExpectResumption(RESUME_TICKET);
+  client_->Handshake();  // ClientHello
+
+  // Write some early data.
+  const uint8_t data[] = {1, 2, 3, 4, 5, 6, 7, 8};
+  PRInt32 rv = PR_Write(client_->ssl_fd(), data, sizeof(data));
+  EXPECT_EQ(static_cast<PRInt32>(sizeof(data)), rv);
+
+  // Consume the ClientHello and generate ServerHello..Finished.
+  server_->Handshake();
+
+  // Read some of the data.
+  std::vector<uint8_t> small_buffer(1 + sizeof(data) / 2);
+  rv = PR_Read(server_->ssl_fd(), small_buffer.data(), small_buffer.size());
+  EXPECT_EQ(static_cast<PRInt32>(small_buffer.size()), rv);
+  EXPECT_EQ(0, memcmp(data, small_buffer.data(), small_buffer.size()));
+
+  Handshake();  // Complete the handshake.
+  ExpectEarlyDataAccepted(true);
+  CheckConnected();
+
+  // After the handshake, it should be possible to read the remainder.
+  uint8_t big_buf[100];
+  rv = PR_Read(server_->ssl_fd(), big_buf, sizeof(big_buf));
+  EXPECT_EQ(static_cast<PRInt32>(sizeof(data) - small_buffer.size()), rv);
+  EXPECT_EQ(0, memcmp(&data[small_buffer.size()], big_buf,
+                      sizeof(data) - small_buffer.size()));
+
+  // And that's all there is to read.
+  rv = PR_Read(server_->ssl_fd(), big_buf, sizeof(big_buf));
+  EXPECT_GT(0, rv);
+  EXPECT_EQ(PR_WOULD_BLOCK_ERROR, PORT_GetError());
+}
+
+// Early data that arrives before the handshake can be read after the handshake
+// is complete.
+TEST_F(TlsConnectDatagram13, ZeroRttLateReadDtls) {
+  SetupForZeroRtt();
+  client_->Set0RttEnabled(true);
+  server_->Set0RttEnabled(true);
+  ExpectResumption(RESUME_TICKET);
+  client_->Handshake();  // ClientHello
+
+  // Write some early data.
+  const uint8_t data[] = {1, 2, 3};
+  PRInt32 written = PR_Write(client_->ssl_fd(), data, sizeof(data));
+  EXPECT_EQ(static_cast<PRInt32>(sizeof(data)), written);
+
+  Handshake();  // Complete the handshake.
+  ExpectEarlyDataAccepted(true);
+  CheckConnected();
+
+  // Reading at the server should return the early data, which was buffered.
+  uint8_t buf[sizeof(data) + 1] = {0};
+  PRInt32 read = PR_Read(server_->ssl_fd(), buf, sizeof(buf));
+  EXPECT_EQ(static_cast<PRInt32>(sizeof(data)), read);
+  EXPECT_EQ(0, memcmp(data, buf, sizeof(data)));
+}
+
+class PacketHolder : public PacketFilter {
+ public:
+  PacketHolder() = default;
+
+  virtual Action Filter(const DataBuffer& input, DataBuffer* output) {
+    packet_ = input;
+    Disable();
+    return DROP;
+  }
+
+  const DataBuffer& packet() const { return packet_; }
+
+ private:
+  DataBuffer packet_;
+};
+
+// Early data that arrives late is discarded for DTLS.
+TEST_F(TlsConnectDatagram13, ZeroRttLateArrivalDtls) {
+  SetupForZeroRtt();
+  client_->Set0RttEnabled(true);
+  server_->Set0RttEnabled(true);
+  ExpectResumption(RESUME_TICKET);
+  client_->Handshake();  // ClientHello
+
+  // Write some early data.  Twice, so that we can read bits of it.
+  const uint8_t data[] = {1, 2, 3};
+  PRInt32 written = PR_Write(client_->ssl_fd(), data, sizeof(data));
+  EXPECT_EQ(static_cast<PRInt32>(sizeof(data)), written);
+
+  // Block and capture the next packet.
+  auto holder = std::make_shared<PacketHolder>();
+  client_->SetFilter(holder);
+  written = PR_Write(client_->ssl_fd(), data, sizeof(data));
+  EXPECT_EQ(static_cast<PRInt32>(sizeof(data)), written);
+  EXPECT_FALSE(holder->enabled()) << "the filter should disable itself";
+
+  // Consume the ClientHello and generate ServerHello..Finished.
+  server_->Handshake();
+
+  // Read some of the data.
+  std::vector<uint8_t> small_buffer(sizeof(data));
+  PRInt32 read =
+      PR_Read(server_->ssl_fd(), small_buffer.data(), small_buffer.size());
+
+  EXPECT_EQ(static_cast<PRInt32>(small_buffer.size()), read);
+  EXPECT_EQ(0, memcmp(data, small_buffer.data(), small_buffer.size()));
+
+  Handshake();  // Complete the handshake.
+  ExpectEarlyDataAccepted(true);
+  CheckConnected();
+
+  server_->SendDirect(holder->packet());
+
+  // Reading now should return nothing, even though a valid packet was
+  // delivered.
+  read = PR_Read(server_->ssl_fd(), small_buffer.data(), small_buffer.size());
+  EXPECT_GT(0, read);
+  EXPECT_EQ(PR_WOULD_BLOCK_ERROR, PORT_GetError());
+}
+
+// Early data reads in TLS should be coalesced.
+TEST_F(TlsConnectStreamTls13, ZeroRttCoalesceReadTls) {
+  SetupForZeroRtt();
+  client_->Set0RttEnabled(true);
+  server_->Set0RttEnabled(true);
+  ExpectResumption(RESUME_TICKET);
+  client_->Handshake();  // ClientHello
+
+  // Write some early data.  In two writes.
+  const uint8_t data[] = {1, 2, 3, 4, 5, 6};
+  PRInt32 written = PR_Write(client_->ssl_fd(), data, 1);
+  EXPECT_EQ(1, written);
+
+  written = PR_Write(client_->ssl_fd(), data + 1, sizeof(data) - 1);
+  EXPECT_EQ(static_cast<PRInt32>(sizeof(data) - 1), written);
+
+  // Consume the ClientHello and generate ServerHello..Finished.
+  server_->Handshake();
+
+  // Read all of the data.
+  std::vector<uint8_t> buffer(sizeof(data));
+  PRInt32 read = PR_Read(server_->ssl_fd(), buffer.data(), buffer.size());
+  EXPECT_EQ(static_cast<PRInt32>(sizeof(data)), read);
+  EXPECT_EQ(0, memcmp(data, buffer.data(), sizeof(data)));
+
+  Handshake();  // Complete the handshake.
+  ExpectEarlyDataAccepted(true);
+  CheckConnected();
+}
+
+// Early data reads in DTLS should not be coalesced.
+TEST_F(TlsConnectDatagram13, ZeroRttNoCoalesceReadDtls) {
+  SetupForZeroRtt();
+  client_->Set0RttEnabled(true);
+  server_->Set0RttEnabled(true);
+  ExpectResumption(RESUME_TICKET);
+  client_->Handshake();  // ClientHello
+
+  // Write some early data.  In two writes.
+  const uint8_t data[] = {1, 2, 3, 4, 5, 6};
+  PRInt32 written = PR_Write(client_->ssl_fd(), data, 1);
+  EXPECT_EQ(1, written);
+
+  written = PR_Write(client_->ssl_fd(), data + 1, sizeof(data) - 1);
+  EXPECT_EQ(static_cast<PRInt32>(sizeof(data) - 1), written);
+
+  // Consume the ClientHello and generate ServerHello..Finished.
+  server_->Handshake();
+
+  // Try to read all of the data.
+  std::vector<uint8_t> buffer(sizeof(data));
+  PRInt32 read = PR_Read(server_->ssl_fd(), buffer.data(), buffer.size());
+  EXPECT_EQ(1, read);
+  EXPECT_EQ(0, memcmp(data, buffer.data(), 1));
+
+  // Read the remainder.
+  read = PR_Read(server_->ssl_fd(), buffer.data(), buffer.size());
+  EXPECT_EQ(static_cast<PRInt32>(sizeof(data) - 1), read);
+  EXPECT_EQ(0, memcmp(data + 1, buffer.data(), sizeof(data) - 1));
+
+  Handshake();  // Complete the handshake.
+  ExpectEarlyDataAccepted(true);
+  CheckConnected();
+}
+
+// Early data reads in DTLS should fail if the buffer is too small.
+TEST_F(TlsConnectDatagram13, ZeroRttShortReadDtls) {
+  SetupForZeroRtt();
+  client_->Set0RttEnabled(true);
+  server_->Set0RttEnabled(true);
+  ExpectResumption(RESUME_TICKET);
+  client_->Handshake();  // ClientHello
+
+  // Write some early data.  In two writes.
+  const uint8_t data[] = {1, 2, 3, 4, 5, 6};
+  PRInt32 written = PR_Write(client_->ssl_fd(), data, sizeof(data));
+  EXPECT_EQ(static_cast<PRInt32>(sizeof(data)), written);
+
+  // Consume the ClientHello and generate ServerHello..Finished.
+  server_->Handshake();
+
+  // Try to read all of the data into a small buffer.
+  std::vector<uint8_t> buffer(sizeof(data));
+  PRInt32 read = PR_Read(server_->ssl_fd(), buffer.data(), 1);
+  EXPECT_GT(0, read);
+  EXPECT_EQ(SSL_ERROR_RX_SHORT_DTLS_READ, PORT_GetError());
+
+  // Read again with more space.
+  read = PR_Read(server_->ssl_fd(), buffer.data(), buffer.size());
+  EXPECT_EQ(static_cast<PRInt32>(sizeof(data)), read);
+  EXPECT_EQ(0, memcmp(data, buffer.data(), sizeof(data)));
+
+  Handshake();  // Complete the handshake.
+  ExpectEarlyDataAccepted(true);
+  CheckConnected();
+}
+
 #ifndef NSS_DISABLE_TLS_1_3
 INSTANTIATE_TEST_CASE_P(Tls13ZeroRttReplayTest, TlsZeroRttReplayTest,
                         TlsConnectTestBase::kTlsVariantsAll);
 #endif
 
 }  // namespace nss_test
--- a/security/nss/gtests/ssl_gtest/ssl_primitive_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_primitive_unittest.cc
@@ -47,16 +47,17 @@ class AeadTest : public ::testing::Test 
  protected:
   static void EncryptDecrypt(const ScopedSSLAeadContext &ctx,
                              const uint8_t *ciphertext, size_t ciphertext_len) {
     static const uint8_t kAad[] = {'a', 'a', 'd'};
     static const uint8_t kPlaintext[] = {'t', 'e', 'x', 't'};
     static const size_t kMaxSize = 32;
 
     ASSERT_GE(kMaxSize, ciphertext_len);
+    ASSERT_LT(0U, ciphertext_len);
 
     uint8_t output[kMaxSize];
     unsigned int output_len = 0;
     EXPECT_EQ(SECSuccess, SSL_AeadEncrypt(ctx.get(), 0, kAad, sizeof(kAad),
                                           kPlaintext, sizeof(kPlaintext),
                                           output, &output_len, sizeof(output)));
     ASSERT_EQ(ciphertext_len, static_cast<size_t>(output_len));
     EXPECT_EQ(0, memcmp(ciphertext, output, ciphertext_len));
@@ -186,17 +187,17 @@ TEST_F(AeadTest, AeadAes128Gcm) {
                          secret_.get(), kLabel, strlen(kLabel), &ctxInit));
   ScopedSSLAeadContext ctx(ctxInit);
   EXPECT_NE(nullptr, ctx);
 
   EncryptDecrypt(ctx, kCiphertextAes128Gcm, sizeof(kCiphertextAes128Gcm));
 }
 
 TEST_F(AeadTest, AeadAes256Gcm) {
-  SSLAeadContext *ctxInit;
+  SSLAeadContext *ctxInit = nullptr;
   ASSERT_EQ(SECSuccess,
             SSL_MakeAead(SSL_LIBRARY_VERSION_TLS_1_3, TLS_AES_256_GCM_SHA384,
                          secret_.get(), kLabel, strlen(kLabel), &ctxInit));
   ScopedSSLAeadContext ctx(ctxInit);
   EXPECT_NE(nullptr, ctx);
 
   EncryptDecrypt(ctx, kCiphertextAes256Gcm, sizeof(kCiphertextAes256Gcm));
 }
--- a/security/nss/gtests/ssl_gtest/ssl_recordsep_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_recordsep_unittest.cc
@@ -171,16 +171,17 @@ class BadPrSocket : public DummyIOLayerM
     static PRDescIdentity bad_identity = PR_GetUniqueIdentity("bad NSPR id");
     fd_ = DummyIOLayerMethods::CreateFD(bad_identity, this);
 
     // This is terrible, but NSPR doesn't provide an easy way to replace the
     // bottom layer of an IO stack.  Take the DummyPrSocket and replace its
     // NSPR method vtable with the ones from this object.
     dummy_layer_ =
         PR_GetIdentitiesLayer(agent->ssl_fd(), DummyPrSocket::LayerId());
+    EXPECT_TRUE(dummy_layer_);
     original_methods_ = dummy_layer_->methods;
     original_secret_ = dummy_layer_->secret;
     dummy_layer_->methods = fd_->methods;
     dummy_layer_->secret = reinterpret_cast<PRFilePrivate*>(this);
   }
 
   // This will be destroyed before the agent, so we need to restore the state
   // before we tampered with it.
--- a/security/nss/gtests/ssl_gtest/test_io.h
+++ b/security/nss/gtests/ssl_gtest/test_io.h
@@ -28,19 +28,21 @@ class DummyPrSocket;  // Fwd decl.
 // Allow us to inspect a packet before it is written.
 class PacketFilter {
  public:
   enum Action {
     KEEP,    // keep the original packet unmodified
     CHANGE,  // change the packet to a different value
     DROP     // drop the packet
   };
-  PacketFilter(bool enabled = true) : enabled_(enabled) {}
+  explicit PacketFilter(bool on = true) : enabled_(on) {}
   virtual ~PacketFilter() {}
 
+  bool enabled() const { return enabled_; }
+
   virtual Action Process(const DataBuffer& input, DataBuffer* output) {
     if (!enabled_) {
       return KEEP;
     }
     return Filter(input, output);
   }
   void Enable() { enabled_ = true; }
   void Disable() { enabled_ = false; }
--- a/security/nss/gtests/ssl_gtest/tls_connect.cc
+++ b/security/nss/gtests/ssl_gtest/tls_connect.cc
@@ -245,16 +245,17 @@ void TlsConnectTestBase::Reset(const std
   client_.reset(new TlsAgent(client_name, TlsAgent::CLIENT, variant_));
   client_->SetResumptionToken(token);
   server_.reset(new TlsAgent(server_name, TlsAgent::SERVER, variant_));
   if (skip_version_checks_) {
     client_->SkipVersionChecks();
     server_->SkipVersionChecks();
   }
 
+  std::cerr << "Reset" << std::endl;
   Init();
 }
 
 void TlsConnectTestBase::MakeNewServer() {
   auto replacement = std::make_shared<TlsAgent>(
       server_->name(), TlsAgent::SERVER, server_->variant());
   server_ = replacement;
   if (version_) {
--- a/security/nss/gtests/ssl_gtest/tls_esni_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/tls_esni_unittest.cc
@@ -65,17 +65,17 @@ static void GenerateEsniKey(time_t windo
   SECKEYECParams ecParams = {siBuffer, NULL, 0};
   NamedGroup2ECParams(group, &ecParams);
 
   SECKEYPublicKey* pub = nullptr;
   SECKEYPrivateKey* priv = SECKEY_CreateECPrivateKey(&ecParams, &pub, nullptr);
   ASSERT_NE(nullptr, priv);
   SECITEM_FreeItem(&ecParams, PR_FALSE);
   PRUint8 encoded[1024];
-  unsigned int encoded_len;
+  unsigned int encoded_len = 0;
 
   SECStatus rv = SSL_EncodeESNIKeys(
       &cipher_suites[0], cipher_suites.size(), group, pub, 100, windowStart,
       windowStart + 10, encoded, &encoded_len, sizeof(encoded));
   ASSERT_EQ(SECSuccess, rv);
   ASSERT_GT(encoded_len, 0U);
 
   if (pubKey) {
@@ -370,21 +370,23 @@ TEST_P(TlsConnectTls13, ConnectEsniCSMis
   EnsureTlsSetup();
   ScopedSECKEYPublicKey pub;
   ScopedSECKEYPrivateKey priv;
   DataBuffer record;
 
   GenerateEsniKey(time(nullptr), ssl_grp_ec_curve25519, kDefaultSuites, &record,
                   &pub, &priv);
   PRUint8 encoded[1024];
-  unsigned int encoded_len;
+  unsigned int encoded_len = 0;
 
   SECStatus rv = SSL_EncodeESNIKeys(
       &kChaChaSuite[0], kChaChaSuite.size(), ssl_grp_ec_curve25519, pub.get(),
       100, time(0), time(0) + 10, encoded, &encoded_len, sizeof(encoded));
+  ASSERT_EQ(SECSuccess, rv);
+  ASSERT_LT(0U, encoded_len);
   rv = SSL_SetESNIKeyPair(server_->ssl_fd(), priv.get(), encoded, encoded_len);
   ASSERT_EQ(SECSuccess, rv);
   rv = SSL_EnableESNI(client_->ssl_fd(), record.data(), record.len(), "");
   ASSERT_EQ(SECSuccess, rv);
   ConnectExpectAlert(server_, illegal_parameter);
   server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_CLIENT_HELLO);
 }
 
--- a/security/nss/gtests/ssl_gtest/tls_filter.h
+++ b/security/nss/gtests/ssl_gtest/tls_filter.h
@@ -544,19 +544,19 @@ class SelectiveDropFilter : public Packe
 };
 
 // This class selectively drops complete records. The difference from
 // SelectiveDropFilter is that if multiple DTLS records are in the same
 // datagram, we just drop one.
 class SelectiveRecordDropFilter : public TlsRecordFilter {
  public:
   SelectiveRecordDropFilter(const std::shared_ptr<TlsAgent>& a,
-                            uint32_t pattern, bool enabled = true)
+                            uint32_t pattern, bool on = true)
       : TlsRecordFilter(a), pattern_(pattern), counter_(0) {
-    if (!enabled) {
+    if (!on) {
       Disable();
     }
   }
   SelectiveRecordDropFilter(const std::shared_ptr<TlsAgent>& a,
                             std::initializer_list<size_t> records)
       : SelectiveRecordDropFilter(a, ToPattern(records), true) {}
 
   void Reset(uint32_t pattern) {
--- a/security/nss/lib/softoken/pkcs11c.c
+++ b/security/nss/lib/softoken/pkcs11c.c
@@ -1224,16 +1224,17 @@ sftk_CryptInit(CK_SESSION_HANDLE hSessio
             SFTKChaCha20CtrInfo *ctx = PORT_ZNew(SFTKChaCha20CtrInfo);
             if (!ctx) {
                 sftk_FreeAttribute(att);
                 crv = CKR_HOST_MEMORY;
                 break;
             }
             if (att->attrib.ulValueLen != sizeof(ctx->key)) {
                 sftk_FreeAttribute(att);
+                PORT_Free(ctx);
                 crv = CKR_KEY_HANDLE_INVALID;
                 break;
             }
             memcpy(ctx->key, att->attrib.pValue, att->attrib.ulValueLen);
             sftk_FreeAttribute(att);
 
             /* The counter is little endian. */
             PRUint8 *param = pMechanism->pParameter;
--- a/security/nss/lib/softoken/sdb.c
+++ b/security/nss/lib/softoken/sdb.c
@@ -894,20 +894,18 @@ sdb_GetAttributeValueNoLock(SDB *sdb, CK
             newColumns = sqlite3_mprintf("a%x", template[i].type);
         }
         if (!newColumns) {
             error = CKR_HOST_MEMORY;
             goto loser;
         }
         columns = newColumns;
     }
-    if (!columns) {
-        error = CKR_OBJECT_HANDLE_INVALID;
-        goto loser;
-    }
+
+    PORT_Assert(columns);
 
     char *statement = sqlite3_mprintf("SELECT DISTINCT %s FROM %s where id=$ID LIMIT 1;",
                                       columns, table);
     sqlite3_free(columns);
     columns = NULL;
     if (!statement) {
         error = CKR_HOST_MEMORY;
         goto loser;
--- a/security/nss/lib/ssl/sslimpl.h
+++ b/security/nss/lib/ssl/sslimpl.h
@@ -571,18 +571,19 @@ typedef struct DTLSQueuedMessageStr {
 
 struct TLS13KeyShareEntryStr {
     PRCList link;                  /* The linked list link */
     const sslNamedGroupDef *group; /* The group for the entry */
     SECItem key_exchange;          /* The share itself */
 };
 
 typedef struct TLS13EarlyDataStr {
-    PRCList link; /* The linked list link */
-    SECItem data; /* The data */
+    PRCList link;          /* The linked list link */
+    unsigned int consumed; /* How much has been read. */
+    SECItem data;          /* The data */
 } TLS13EarlyData;
 
 typedef enum {
     handshake_hash_unknown = 0,
     handshake_hash_combo = 1,  /* The MD5/SHA-1 combination */
     handshake_hash_single = 2, /* A single hash */
     handshake_hash_record
 } SSL3HandshakeHashType;
--- a/security/nss/lib/ssl/tls13con.c
+++ b/security/nss/lib/ssl/tls13con.c
@@ -5537,35 +5537,55 @@ tls13_MaybeDo0RTTHandshake(sslSocket *ss
     if (rv != SECSuccess) {
         return SECFailure;
     }
 
     return SECSuccess;
 }
 
 PRInt32
-tls13_Read0RttData(sslSocket *ss, void *buf, PRInt32 len)
+tls13_Read0RttData(sslSocket *ss, PRUint8 *buf, PRInt32 len)
 {
-    TLS13EarlyData *msg;
-
     PORT_Assert(!PR_CLIST_IS_EMPTY(&ss->ssl3.hs.bufferedEarlyData));
-    msg = (TLS13EarlyData *)PR_NEXT_LINK(&ss->ssl3.hs.bufferedEarlyData);
-
-    PR_REMOVE_LINK(&msg->link);
-    if (msg->data.len > len) {
-        PORT_SetError(SSL_ERROR_ILLEGAL_PARAMETER_ALERT);
-        return SECFailure;
-    }
-    len = msg->data.len;
-
-    PORT_Memcpy(buf, msg->data.data, msg->data.len);
-    SECITEM_ZfreeItem(&msg->data, PR_FALSE);
-    PORT_ZFree(msg, sizeof(*msg));
-
-    return len;
+    PRInt32 offset = 0;
+    while (!PR_CLIST_IS_EMPTY(&ss->ssl3.hs.bufferedEarlyData)) {
+        TLS13EarlyData *msg =
+            (TLS13EarlyData *)PR_NEXT_LINK(&ss->ssl3.hs.bufferedEarlyData);
+        unsigned int tocpy = msg->data.len - msg->consumed;
+
+        if (tocpy > (len - offset)) {
+            if (IS_DTLS(ss)) {
+                /* In DTLS, we only return entire records.
+                 * So offset and consumed are always zero. */
+                PORT_Assert(offset == 0);
+                PORT_Assert(msg->consumed == 0);
+                PORT_SetError(SSL_ERROR_RX_SHORT_DTLS_READ);
+                return -1;
+            }
+
+            tocpy = len - offset;
+        }
+
+        PORT_Memcpy(buf + offset, msg->data.data + msg->consumed, tocpy);
+        offset += tocpy;
+        msg->consumed += tocpy;
+
+        if (msg->consumed == msg->data.len) {
+            PR_REMOVE_LINK(&msg->link);
+            SECITEM_ZfreeItem(&msg->data, PR_FALSE);
+            PORT_ZFree(msg, sizeof(*msg));
+        }
+
+        /* We are done after one record for DTLS; otherwise, when the buffer fills up. */
+        if (IS_DTLS(ss) || offset == len) {
+            break;
+        }
+    }
+
+    return offset;
 }
 
 static SECStatus
 tls13_SendEndOfEarlyData(sslSocket *ss)
 {
     SECStatus rv;
 
     SSL_TRC(3, ("%d: TLS13[%d]: send EndOfEarlyData", SSL_GETPID(), ss->fd));
--- a/security/nss/lib/ssl/tls13con.h
+++ b/security/nss/lib/ssl/tls13con.h
@@ -99,17 +99,17 @@ SECStatus tls13_SetAlertCipherSpec(sslSo
 tls13ExtensionStatus tls13_ExtensionStatus(PRUint16 extension,
                                            SSLHandshakeType message);
 SECStatus tls13_ProtectRecord(sslSocket *ss,
                               ssl3CipherSpec *cwSpec,
                               SSLContentType type,
                               const PRUint8 *pIn,
                               PRUint32 contentLen,
                               sslBuffer *wrBuf);
-PRInt32 tls13_Read0RttData(sslSocket *ss, void *buf, PRInt32 len);
+PRInt32 tls13_Read0RttData(sslSocket *ss, PRUint8 *buf, PRInt32 len);
 SECStatus tls13_HandleEarlyApplicationData(sslSocket *ss, sslBuffer *origBuf);
 PRBool tls13_ClientAllow0Rtt(const sslSocket *ss, const sslSessionID *sid);
 PRUint16 tls13_EncodeDraftVersion(SSL3ProtocolVersion version,
                                   SSLProtocolVariant variant);
 SECStatus tls13_ClientReadSupportedVersion(sslSocket *ss);
 SECStatus tls13_NegotiateVersion(sslSocket *ss,
                                  const TLSExtension *supported_versions);
 PRBool tls13_ShouldRequestClientAuth(sslSocket *ss);
--- a/security/nss/nss.gyp
+++ b/security/nss/nss.gyp
@@ -193,16 +193,17 @@
             'cmd/vfychain/vfychain.gyp:vfychain',
             'cmd/vfyserv/vfyserv.gyp:vfyserv',
             'gtests/certhigh_gtest/certhigh_gtest.gyp:certhigh_gtest',
             'gtests/cryptohi_gtest/cryptohi_gtest.gyp:cryptohi_gtest',
             'gtests/der_gtest/der_gtest.gyp:der_gtest',
             'gtests/certdb_gtest/certdb_gtest.gyp:certdb_gtest',
             'gtests/freebl_gtest/freebl_gtest.gyp:prng_gtest',
             'gtests/freebl_gtest/freebl_gtest.gyp:blake2b_gtest',
+            'gtests/freebl_gtest/freebl_gtest.gyp:freebl_gtest',
             'gtests/mozpkix_gtest/mozpkix_gtest.gyp:mozpkix_gtest',
             'gtests/nss_bogo_shim/nss_bogo_shim.gyp:nss_bogo_shim',
             'gtests/pk11_gtest/pk11_gtest.gyp:pk11_gtest',
             'gtests/smime_gtest/smime_gtest.gyp:smime_gtest',
             'gtests/softoken_gtest/softoken_gtest.gyp:softoken_gtest',
             'gtests/ssl_gtest/ssl_gtest.gyp:ssl_gtest',
             'gtests/util_gtest/util_gtest.gyp:util_gtest',
           ],
--- a/servo/components/style/properties/shorthands/position.mako.rs
+++ b/servo/components/style/properties/shorthands/position.mako.rs
@@ -249,33 +249,32 @@
 
 <%helpers:shorthand name="grid-template"
                     sub_properties="grid-template-rows grid-template-columns grid-template-areas"
                     spec="https://drafts.csswg.org/css-grid/#propdef-grid-template"
                     products="gecko">
     use crate::parser::Parse;
     use servo_arc::Arc;
     use crate::values::{Either, None_};
-    use crate::values::generics::grid::{LineNameList, TrackSize, TrackList, TrackListType};
+    use crate::values::generics::grid::{TrackSize, TrackList, TrackListType};
     use crate::values::generics::grid::{TrackListValue, concat_serialize_idents};
     use crate::values::specified::{GridTemplateComponent, GenericGridTemplateComponent};
     use crate::values::specified::grid::parse_line_names;
     use crate::values::specified::position::{TemplateAreas, TemplateAreasArc};
 
     /// Parsing for `<grid-template>` shorthand (also used by `grid` shorthand).
     pub fn parse_grid_template<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>,
     ) -> Result<(GridTemplateComponent, GridTemplateComponent, Either<TemplateAreasArc, None_>), ParseError<'i>> {
-        // Other shorthand sub properties also parse `none` and `subgrid` keywords and this
-        // shorthand should know after these keywords there is nothing to parse. Otherwise it
-        // gets confused and rejects the sub properties that contains `none` or `subgrid`.
+        // Other shorthand sub properties also parse the `none` keyword and this shorthand
+        // should know after this keyword there is nothing to parse. Otherwise it gets
+        // confused and rejects the sub properties that contains `none`.
         <% keywords = {
             "none": "GenericGridTemplateComponent::None",
-            "subgrid": "GenericGridTemplateComponent::Subgrid(LineNameList::default())"
         }
         %>
         % for keyword, rust_type in keywords.items():
             if let Ok(x) = input.try(|i| {
                 if i.try(|i| i.expect_ident_matching("${keyword}")).is_ok() {
                     if i.is_exhausted() {
                         return Ok((${rust_type},
                                    ${rust_type},
--- a/testing/web-platform/meta/css/compositing/mix-blend-mode/mix-blend-mode-both-parent-and-blended-with-3D-transform.html.ini
+++ b/testing/web-platform/meta/css/compositing/mix-blend-mode/mix-blend-mode-both-parent-and-blended-with-3D-transform.html.ini
@@ -1,3 +1,3 @@
 [mix-blend-mode-both-parent-and-blended-with-3D-transform.html]
   expected:
-    if os == "android" and not e10s: FAIL
+    if webrender or (os == "android" and not e10s): FAIL