Merge mozilla-central to autoland. a=merge CLOSED TREE
authorOana Pop Rus <opoprus@mozilla.com>
Tue, 07 May 2019 01:33:06 +0300
changeset 531611 16545e6e556eb031b325e6012cf27419f5c56091
parent 531610 df46098ba4b559b4b62b8f05c51bacc424a9075b (current diff)
parent 531559 3c70f36ad62c9c714db3199fc00e60800ee82bde (diff)
child 531612 d2d67ffc31fcaab22a60feb8394a3d01ab7ce52d
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
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to autoland. a=merge CLOSED TREE
security/nss/coreconf/check_cc_clang.py
toolkit/mozapps/extensions/internal/XPIInstall.jsm
--- 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/apps/AppTrustDomain.cpp
+++ b/security/apps/AppTrustDomain.cpp
@@ -19,16 +19,17 @@
 #include "nsNetUtil.h"
 #include "mozpkix/pkixnss.h"
 #include "prerror.h"
 
 // Generated by gen_cert_header.py, which gets called by the build system.
 #include "xpcshell.inc"
 // Add-on signing Certificates
 #include "addons-public.inc"
+#include "addons-public-intermediate.inc"
 #include "addons-stage.inc"
 // Privileged Package Certificates
 #include "privileged-package-root.inc"
 
 using namespace mozilla::pkix;
 
 extern mozilla::LazyLogModule gPIPNSSLog;
 
@@ -120,16 +121,33 @@ nsresult AppTrustDomain::SetTrustedRoot(
   }
 
   mTrustedRoot.reset(CERT_NewTempCertificate(
       CERT_GetDefaultCertDB(), &trustedDER, nullptr, false, true));
   if (!mTrustedRoot) {
     return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
   }
 
+  // If we're verifying add-ons signed by our production root, we want to make
+  // sure a valid intermediate certificate is available for path building.
+  // Merely holding this alive in memory makes it available for NSS to find in
+  // AppTrustDomain::FindIssuer.
+  if (trustedRoot == nsIX509CertDB::AddonsPublicRoot) {
+    SECItem intermediateDER = {
+        siBuffer,
+        const_cast<uint8_t*>(addonsPublicIntermediate),
+        mozilla::ArrayLength(addonsPublicIntermediate),
+    };
+    mAddonsIntermediate.reset(CERT_NewTempCertificate(
+        CERT_GetDefaultCertDB(), &intermediateDER, nullptr, false, true));
+    if (!mAddonsIntermediate) {
+      return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+    }
+  }
+
   return NS_OK;
 }
 
 Result AppTrustDomain::FindIssuer(Input encodedIssuerName,
                                   IssuerChecker& checker, Time)
 
 {
   MOZ_ASSERT(mTrustedRoot);
--- a/security/apps/AppTrustDomain.h
+++ b/security/apps/AppTrustDomain.h
@@ -72,16 +72,17 @@ class AppTrustDomain final : public mozi
                            mozilla::pkix::DigestAlgorithm digestAlg,
                            /*out*/ uint8_t* digestBuf,
                            size_t digestBufLen) override;
 
  private:
   /*out*/ UniqueCERTCertList& mCertChain;
   void* mPinArg;  // non-owning!
   UniqueCERTCertificate mTrustedRoot;
+  UniqueCERTCertificate mAddonsIntermediate;
 
   static StaticMutex sMutex;
   static UniquePtr<unsigned char[]> sDevImportedDERData;
   static unsigned int sDevImportedDERLen;
 };
 
 }  // namespace psm
 }  // namespace mozilla
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d37979bf3839dd41bb175a3887cc8023507bdde2
GIT binary patch
literal 1841
zc$_n6V%IfjVijG$%*4pV#4NzTVZh7AsnzDu_MMlJk%yJRpw^JvfRl|ml!Z;0DKywn
z#y|?h;Sv`1&9BPL$w^dj&Mzv+FG?)Q%+E8_H_!#iato`$WF39|6#|O#Q%aLT3KW7f
z)AKU((iMVJi^?*SQw`+}WI-C3g~f{U^GkG-6LkyViggon^9|(0c@0esOn?v!qQrTP
zkhunp%W3L9H3Joh`(%qDGRcXCdWk71`FX{9xez1t@{7_96%FL!!N$obCX|+6QJk9W
z08^5jpKH*>q=X!{jI0dIO-%d@KyfanCMHIP{i~uEb@XqrUe(6jef;gg_fcP8mRavr
ztG<1x>UL62;#RMC@AcfdH;zf&{_XOw^=gH?f3wKFe{4HWFO_$+i2Sy6_VM)__r7JB
z;-m73NwqG7W5=$Ld7Za@tc+D{tUnW7@Sx7N&uj9ce}XfnWxh%>&OKDREZO9d6NB6a
zkxIAfH&-T~`_MM&)|;%iYbq}1pWf@MvTruW!+q@MKQ!H3n^BxU@&CykYWzEHWK8Vf
z?Z3vCwY4#P%iS*vkJ#E8#rnUg{j+$cw8+led8@LxSN`$*%5gtfNrm-*dHSw3%sn4G
zR}`?B2<UkQb%|?AOYEt6c$o3YfwtS#OEi5wJy!iOHTW4|ckbry&x~^;H%XeTQkRZc
z<e7cpSW8@yZ*qmXi1}A{=atLz4+{2&wim{H`L%0hlA=_zQRMA;3)Q54qzbMud$Y6i
z$=jJmn}znBlwwtTXL#3?amJyZ>m3&~b{Ot^WE>g!YZc#|!+YlaG2`9&-HuoJlCRnl
zucMt`nm=~1S|6F;#&PCWdaT6Vz`m0kyLbF?o~mA@!E|83an)}ej<>S!vpbg``nN8s
z?9^_Fg{yxxFFW7b7ty<K-=WtlJ3{LBuJdVE)@y5MRXw!t$mb(kho4NSxW2M|u}AVb
zfBmkK<U@=0EdHT>ch9z2rJtGJ$y_@j{Y)yzFD$^U>VMw0YcsZfbqn~U(SGFqbxkH_
zMh3>kO^lreO^mGuJiufpE6l=bz|6?_-+&Lq;|H=>Sb!Oo%|HyqR|WBT47k`hwAmP0
zS=pJH!I?>xk420{M9l8D%zqy3$v<X`Ue{fG=vhVLowo*!D?swfER72c8s|2MY~J%K
zqGa{Myb7&NtLG@otw`3HzI|b1<C6LT&h$-;j0VPRoC$3njBP)h7+F}<#8fg$N(!v>
z^)a)aesZFIa#4<6L29mnAIN)hEba!b3!M!Vn&g1_F0UjtuS6G=c@s-Yi&Bww8pt=v
z;#YuL6f%QyX?*b6Y5Jdq_Ly&3d+CexpUWkok1jr0b8P2v>%w!Pi?dxF0w%RM%iX`O
zY_l(2K>dcLi@%uYIdjFwAI@&j*p=t{K;+E3TRs2(&wISD^=hjE`<CSsOs*~wULHRC
zqP}fVcSq-6U-qESB6}{m9DMp-i~q3c)^}XnC+#(4$mX1=EY+LTwy2Te&+!hYGwGsR
zI(EkS&N5I6yRrV4+DhK%Ug<i*DfV)$Rcbsc%74x~dYmtR|Gdz>>FtXOi=<m`ua-Pc
z)-IbRn(|cRMq7oc$-k&Co1V^h*v)%tMzG;Fu9H?15^BN|X1@6HySID$*6+UO_nfn?
zSx}i1IZ0pV;5C8LgjVl<{~Igp)X!Hs@EFuyV`gOEeesrDw3}g;&YL`|*S@BQ1Lys_
zVz)Y!>(bF%rqPc-sFh5${$v%;cfPT)|Lx74$5;a$cXLg;v3lwDze~2sNf>SV$+uNd
z;k-s{YJiK-#&i2E?khZ#>$khLYolIS%*-sg&ngzzPThRaP#0cf=F=eih$oUkRy=#r
z{JC<>A0=3xoltA!;(FWvu<Uy+lk}DdH;)e<b~DVIsyeO}YaIP~yQ(;g#ZBdzs1a*W
zm$HyifxeQK%<8%dE|;W<)m>@Rie9#wM_l}+sw~_V6x3QMb**0Y=9BuPp$;dKSojyc
f+vmUW*YVXavwRKvnjdaXmOD4Ay_3<_cyT)bbL!^r
--- a/security/apps/gen_cert_header.py
+++ b/security/apps/gen_cert_header.py
@@ -27,16 +27,17 @@ def _create_header(array_name, cert_byte
 # Create functions named the same as the data arrays that we're going to
 # write to the headers, so we don't have to duplicate the names like so:
 #
 #   def arrayName(header, cert_filename):
 #     header.write(_create_header("arrayName", cert_filename))
 array_names = [
     'xpcshellRoot',
     'addonsPublicRoot',
+    'addonsPublicIntermediate',
     'addonsStageRoot',
     'privilegedPackageRoot',
 ]
 
 for n in array_names:
     # Make sure the lambda captures the right string.
     globals()[n] = lambda header, cert_filename, name=n: header.write(
         _create_header(name, _file_byte_generator(cert_filename)))
--- a/security/apps/moz.build
+++ b/security/apps/moz.build
@@ -34,16 +34,17 @@ if CONFIG['CC_TYPE'] in ('clang', 'gcc')
         '-Wno-unused-parameter',
     ]
 
 test_ssl_path = '/security/manager/ssl/tests/unit'
 
 headers_arrays_certs = [
     ('xpcshell.inc', 'xpcshellRoot', test_ssl_path + '/test_signed_apps/xpcshellTestRoot.der'),
     ('addons-public.inc', 'addonsPublicRoot', 'addons-public.crt'),
+    ('addons-public-intermediate.inc', 'addonsPublicIntermediate', 'addons-public-intermediate.crt'),
     ('addons-stage.inc', 'addonsStageRoot', 'addons-stage.crt'),
     ('privileged-package-root.inc', 'privilegedPackageRoot', 'privileged-package-root.der'),
 ]
 
 for header, array_name, cert in headers_arrays_certs:
     GENERATED_FILES += [header]
     h = GENERATED_FILES[header]
     h.script = 'gen_cert_header.py:' + array_name
--- 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
--- a/toolkit/mozapps/extensions/internal/XPIInstall.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIInstall.jsm
@@ -3258,51 +3258,16 @@ class SystemAddonInstaller extends Direc
 
     return newFile;
   }
 
   // old system add-on upgrade dirs get automatically removed
   uninstallAddon(aAddon) {}
 }
 
-// https://bugzilla.mozilla.org/show_bug.cgi?id=1548973
-const MISSING_INTERMEDIATE_CERTIFICATE = "\
-MIIHLTCCBRWgAwIBAgIDEAAIMA0GCSqGSIb3DQEBDAUAMH0xCzAJBgNVBAYTAlVTMRwwGgYDVQQKEx\
-NNb3ppbGxhIENvcnBvcmF0aW9uMS8wLQYDVQQLEyZNb3ppbGxhIEFNTyBQcm9kdWN0aW9uIFNpZ25p\
-bmcgU2VydmljZTEfMB0GA1UEAxMWcm9vdC1jYS1wcm9kdWN0aW9uLWFtbzAeFw0xNTA0MDQwMDAwMD\
-BaFw0yNTA0MDQwMDAwMDBaMIGnMQswCQYDVQQGEwJVUzEcMBoGA1UEChMTTW96aWxsYSBDb3Jwb3Jh\
-dGlvbjEvMC0GA1UECxMmTW96aWxsYSBBTU8gUHJvZHVjdGlvbiBTaWduaW5nIFNlcnZpY2UxJjAkBg\
-NVBAMTHXNpZ25pbmdjYTEuYWRkb25zLm1vemlsbGEub3JnMSEwHwYJKoZIhvcNAQkBFhJmb3hzZWNA\
-bW96aWxsYS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/qluiiI+wO6qGA4vH7c\
-HvWvXpdju9JnvbwnrbYmxhtUpfS68LbdjGGtv7RP6F1XhHT4MU3v4GuMulH0E4Wfalm8evsb3tBJRM\
-JPICJX5UCLi6VJ6J2vipXSWBf8xbcOB+PY5Kk6L+EZiWaepiM23CdaZjNOJCAB6wFHlGe+zUk87whp\
-La7GrtrHjTb8u9TSS+mwjhvgfP8ILZrWhzb5H/ybgmD7jYaJGIDY/WDmq1gVe03fShxD09Ml1P7H38\
-o5kbFLnbbqpqC6n8SfUI31MiJAXAN2e6rAOM8EmocAY0EC5KUooXKRsYvHzhwwHkwIbbe6QpTUlIqv\
-w1MPlQPs7Zu/MBnVmyGTSqJxtYoklr0MaEXnJNY3g3FDf1R0Opp2/BEY9Vh3Fc9Pq6qWIhGoMyWdue\
-oSYa+GURqDbsuYnk7ZkysxK+yRoFJu4x3TUBmMKM14jQKLgxvuIzWVn6qg6cw7ye/DYNufc+DSPSTS\
-akSsWJ9IPxiAU7xJ+GCMzaZ10Y3VGOybGLuPxDlSd6KALAoMcl9ghB2mvfB0N3wv6uWnbKuxihq/qD\
-ps+FjliNvr7C66mIVH+9rkyHIy6GgIUlwr7E88Qqw+SQeNeph6NIY85PL4p0Y8KivKP4J928tpp18w\
-LuHNbIG+YaUk5WUDZ6/2621pi19UZQ8iiHxN/XKQIDAQABo4IBiTCCAYUwDAYDVR0TBAUwAwEB/zAO\
-BgNVHQ8BAf8EBAMCAQYwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwMwHQYDVR0OBBYEFBY++xz/DCuT+J\
-sV1y2jwuZ4YdztMIGoBgNVHSMEgaAwgZ2AFLO86lh0q+FueCqyq5wjHqhjLJe3oYGBpH8wfTELMAkG\
-A1UEBhMCVVMxHDAaBgNVBAoTE01vemlsbGEgQ29ycG9yYXRpb24xLzAtBgNVBAsTJk1vemlsbGEgQU\
-1PIFByb2R1Y3Rpb24gU2lnbmluZyBTZXJ2aWNlMR8wHQYDVQQDExZyb290LWNhLXByb2R1Y3Rpb24t\
-YW1vggEBMDMGCWCGSAGG+EIBBAQmFiRodHRwOi8vYWRkb25zLm1vemlsbGEub3JnL2NhL2NybC5wZW\
-0wTgYDVR0eBEcwRaFDMCCCHi5jb250ZW50LXNpZ25hdHVyZS5tb3ppbGxhLm9yZzAfgh1jb250ZW50\
-LXNpZ25hdHVyZS5tb3ppbGxhLm9yZzANBgkqhkiG9w0BAQwFAAOCAgEAX1PNli/zErw3tK3S9Bv803\
-RV4tHkrMa5xztxzlWja0VAUJKEQx7f1yM8vmcQJ9g5RE8WFc43IePwzbAoum5F4BTM7tqM//+e476F\
-1YUgB7SnkDTVpBOnV5vRLz1Si4iJ/U0HUvMUvNJEweXvKg/DNbXuCreSvTEAawmRIxqNYoaigQD8x4\
-hCzGcVtIi5Xk2aMCJW2K/6JqkN50pnLBNkPx6FeiYMJCP8z0FIz3fv53FHgu3oeDhi2u3VdONjK3aa\
-FWTlKNiGeDU0/lr0suWfQLsNyphTMbYKyTqQYHxXYJno9PuNi7e1903PvM47fKB5bFmSLyzB1hB1YI\
-VLj0/YqD4nz3lADDB91gMBB7vR2h5bRjFqLOxuOutNNcNRnv7UPqtVCtLF2jVb4/AmdJU78jpfDs+B\
-gY/t2bnGBVFBuwqS2Kult/2kth4YMrL5DrURIM8oXWVQRBKxzr843yDmHo8+2rqxLnZcmWoe8yQ41s\
-rZ4IB+V3w2TIAd4gxZAB0Xa6KfnR4D8RgE5sgmgQoK7Y/hdvd9Ahu0WEZI8Eg+mDeCeojWcyjF+dt6\
-c2oERiTmFTIFUoojEjJwLyIqHKt+eApEYpF7imaWcumFN1jR+iUjE4ZSUoVxGtZ/Jdnkf8VVQMhiBA\
-+i7r5PsfrHq+lqTTGOg+GzYx7OmoeJAT0zo4c=";
-
 var XPIInstall = {
   // An array of currently active AddonInstalls
   installs: new Set(),
 
   createLocalInstall,
   flushJarCache,
   newVersionReason,
   recursiveRemove,
@@ -3331,24 +3296,16 @@ var XPIInstall = {
       try {
         c.cancel();
       } catch (e) {
         logger.warn("Cancel failed", e);
       }
     }
   },
 
-  addMissingIntermediateCertificate() {
-    logger.debug("hotfix for addon signing cert has not been applied; applying");
-
-    let certDB = Cc["@mozilla.org/security/x509certdb;1"].getService(Ci.nsIX509CertDB);
-    certDB.addCertFromBase64(MISSING_INTERMEDIATE_CERTIFICATE, ",,");
-    logger.debug("new intermediate certificate added");
-  },
-
   /**
    * @param {string} id
    *        The expected ID of the add-on.
    * @param {nsIFile} file
    *        The XPI file to install the add-on from.
    * @param {XPIStateLocation} location
    *        The install location to install the add-on to.
    * @returns {AddonInternal}
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -101,17 +101,17 @@ const STARTUP_MTIME_SCOPES = [KEY_APP_GL
                               KEY_APP_SYSTEM_SHARE,
                               KEY_APP_SYSTEM_USER];
 
 const NOTIFICATION_FLUSH_PERMISSIONS  = "flush-pending-permissions";
 const XPI_PERMISSION                  = "install";
 
 const XPI_SIGNATURE_CHECK_PERIOD      = 24 * 60 * 60;
 
-const DB_SCHEMA = 30;
+const DB_SCHEMA = 31;
 
 XPCOMUtils.defineLazyPreferenceGetter(this, "enabledScopesPref",
                                       PREF_EM_ENABLED_SCOPES,
                                       AddonManager.SCOPE_ALL);
 
 Object.defineProperty(this, "enabledScopes", {
   get() {
     // The profile location is always enabled
@@ -1982,28 +1982,16 @@ class BootstrapScope {
       await updateCallback();
     }
 
     this.addon = newAddon;
     return this._install(reason, callUpdate, startup, extraArgs);
   }
 }
 
-function addMissingIntermediateCertificate() {
-  const PREF_SIGNER_HOTFIXED = "extensions.signer.hotfixed";
-  if (!Services.prefs.getBoolPref(PREF_SIGNER_HOTFIXED, false)) {
-    try {
-      XPIInstall.addMissingIntermediateCertificate();
-      Services.prefs.setBoolPref(PREF_SIGNER_HOTFIXED, true);
-    } catch (e) {
-      logger.error("failed to add new intermediate certificate:", e);
-    }
-  }
-}
-
 let resolveDBReady;
 let dbReadyPromise = new Promise(resolve => {
   resolveDBReady = resolve;
 });
 let resolveProviderReady;
 let providerReadyPromise = new Promise(resolve => {
   resolveProviderReady = resolve;
 });
@@ -2239,21 +2227,16 @@ var XPIProvider = {
    * @param {string?} [aOldAppVersion]
    *        The version of the application last run with this profile or null
    *        if it is a new profile or the version is unknown
    * @param {string?} [aOldPlatformVersion]
    *        The version of the platform last run with this profile or null
    *        if it is a new profile or the version is unknown
    */
   startup(aAppChanged, aOldAppVersion, aOldPlatformVersion) {
-    // Add missing certificate (bug 1548973). Mistakenly disabled add-ons are
-    // going to be re-enabled because the schema version bump forces a new
-    // signature verification check.
-    addMissingIntermediateCertificate();
-
     try {
       AddonManagerPrivate.recordTimestamp("XPI_startup_begin");
 
       logger.debug("startup");
 
       this.builtInAddons = {};
       try {
         let url = Services.io.newURI(BUILT_IN_ADDONS_URI);