Bug 1286798 - Part 50: Add support for clearing of the archive and shadow database; r=asuth draft
authorJan Varga <jan.varga@gmail.com>
Wed, 24 Oct 2018 06:59:10 +0200
changeset 481728 3f2af2a900c14028bdb1b4e81ea081627ee43cf1
parent 481727 e2301860239fb3e343af4f53d73a06b306d3be7e
child 481729 b1a665f1536be293d4157c4fd06ca6d76d5b3f79
push id10
push userbugmail@asutherland.org
push dateSun, 18 Nov 2018 18:57:42 +0000
reviewersasuth
bugs1286798
milestone65.0a1
Bug 1286798 - Part 50: Add support for clearing of the archive and shadow database; r=asuth
dom/localstorage/ActorsParent.cpp
--- a/dom/localstorage/ActorsParent.cpp
+++ b/dom/localstorage/ActorsParent.cpp
@@ -3,40 +3,43 @@
 /* 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 "ActorsParent.h"
 
 #include "LocalStorageCommon.h"
 #include "mozIStorageConnection.h"
+#include "mozIStorageFunction.h"
 #include "mozIStorageService.h"
 #include "mozStorageCID.h"
 #include "mozStorageHelper.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Unused.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/PBackgroundLSDatabaseParent.h"
 #include "mozilla/dom/PBackgroundLSObserverParent.h"
 #include "mozilla/dom/PBackgroundLSRequestParent.h"
 #include "mozilla/dom/PBackgroundLSSharedTypes.h"
 #include "mozilla/dom/PBackgroundLSSimpleRequestParent.h"
 #include "mozilla/dom/PBackgroundLSSnapshotParent.h"
 #include "mozilla/dom/StorageDBUpdater.h"
 #include "mozilla/dom/StorageUtils.h"
+#include "mozilla/dom/quota/OriginScope.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/dom/quota/QuotaObject.h"
 #include "mozilla/dom/quota/UsageInfo.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/PBackgroundParent.h"
 #include "mozilla/ipc/PBackgroundSharedTypes.h"
 #include "nsClassHashtable.h"
 #include "nsDataHashtable.h"
 #include "nsInterfaceHashtable.h"
 #include "nsISimpleEnumerator.h"
+#include "nsNetUtil.h"
 #include "nsRefPtrHashtable.h"
 #include "ReportInternalError.h"
 
 #define DISABLE_ASSERTS_FOR_FUZZING 0
 
 #if DISABLE_ASSERTS_FOR_FUZZING
 #define ASSERT_UNLESS_FUZZING(...) do { } while (0)
 #else
@@ -51,24 +54,29 @@ namespace mozilla {
 namespace dom {
 
 using namespace mozilla::dom::quota;
 using namespace mozilla::dom::StorageUtils;
 using namespace mozilla::ipc;
 
 namespace {
 
-class ArchivedOriginInfo;
+struct ArchivedOriginInfo;
+class ArchivedOriginScope;
 class Connection;
 class ConnectionThread;
 class Database;
 class PrepareDatastoreOp;
 class PreparedDatastore;
+class QuotaClient;
 class Snapshot;
 
+typedef nsClassHashtable<nsCStringHashKey, ArchivedOriginInfo>
+  ArchivedOriginHashtable;
+
 /*******************************************************************************
  * Constants
  ******************************************************************************/
 
 // Major schema version. Bump for almost everything.
 const uint32_t kMajorSchemaVersion = 1;
 
 // Minor schema version. Should almost always be 0 (maybe bump on release
@@ -148,16 +156,23 @@ AssertIsOnConnectionThread();
 int32_t
 MakeSchemaVersion(uint32_t aMajorSchemaVersion,
                   uint32_t aMinorSchemaVersion)
 {
   return int32_t((aMajorSchemaVersion << 4) + aMinorSchemaVersion);
 }
 #endif
 
+nsCString
+GetArchivedOriginHashKey(const nsACString& aOriginSuffix,
+                         const nsACString& aOriginNoSuffix)
+{
+  return aOriginSuffix + NS_LITERAL_CSTRING(":") + aOriginNoSuffix;
+}
+
 nsresult
 CreateTables(mozIStorageConnection* aConnection)
 {
   AssertIsOnIOThread();
   MOZ_ASSERT(aConnection);
 
   // Table `database`
   nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
@@ -808,16 +823,56 @@ CreateShadowStorageConnection(const nsAS
     return rv;
   }
 
   connection.forget(aConnection);
   return NS_OK;
 }
 
 nsresult
+GetShadowStorageConnection(const nsAString& aBasePath,
+                           mozIStorageConnection** aConnection)
+{
+  AssertIsOnIOThread();
+  MOZ_ASSERT(!aBasePath.IsEmpty());
+  MOZ_ASSERT(aConnection);
+
+  nsCOMPtr<nsIFile> shadowFile;
+  nsresult rv = GetShadowFile(aBasePath, getter_AddRefs(shadowFile));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  bool exists;
+  rv = shadowFile->Exists(&exists);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (NS_WARN_IF(!exists)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<mozIStorageService> ss =
+    do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCOMPtr<mozIStorageConnection> connection;
+  rv = ss->OpenUnsharedDatabase(shadowFile, getter_AddRefs(connection));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  connection.forget(aConnection);
+  return NS_OK;
+}
+
+nsresult
 AttachShadowDatabase(const nsAString& aBasePath,
                      mozIStorageConnection* aConnection)
 {
   AssertIsOnConnectionThread();
   MOZ_ASSERT(!aBasePath.IsEmpty());
   MOZ_ASSERT(aConnection);
 
   nsCOMPtr<nsIFile> shadowFile;
@@ -1195,17 +1250,17 @@ public:
 
 private:
   class FlushOp;
   class CloseOp;
 
   RefPtr<ConnectionThread> mConnectionThread;
   nsCOMPtr<nsITimer> mFlushTimer;
   nsCOMPtr<mozIStorageConnection> mStorageConnection;
-  nsAutoPtr<ArchivedOriginInfo> mArchivedOriginInfo;
+  nsAutoPtr<ArchivedOriginScope> mArchivedOriginScope;
   nsInterfaceHashtable<nsCStringHashKey, mozIStorageStatement>
     mCachedStatements;
   WriteOptimizer mWriteOptimizer;
   const nsCString mOrigin;
   const nsString mFilePath;
   bool mFlushScheduled;
 #ifdef DEBUG
   bool mInUpdateBatch;
@@ -1215,20 +1270,20 @@ public:
   NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Connection)
 
   void
   AssertIsOnOwningThread() const
   {
     NS_ASSERT_OWNINGTHREAD(Connection);
   }
 
-  ArchivedOriginInfo*
-  GetArchivedOriginInfo() const
+  ArchivedOriginScope*
+  GetArchivedOriginScope() const
   {
-    return mArchivedOriginInfo;
+    return mArchivedOriginScope;
   }
 
   // Methods which can only be called on the owning thread.
 
   // This method is used to asynchronously execute a connection datastore
   // operation on the connection thread.
   void
   Dispatch(ConnectionDatastoreOperationBase* aOp);
@@ -1279,17 +1334,17 @@ public:
   GetCachedStatement(const nsACString& aQuery,
                      CachedStatement* aCachedStatement);
 
 private:
   // Only created by ConnectionThread.
   Connection(ConnectionThread* aConnectionThread,
              const nsACString& aOrigin,
              const nsAString& aFilePath,
-             nsAutoPtr<ArchivedOriginInfo>&& aArchivedOriginInfo);
+             nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope);
 
   ~Connection();
 
   void
   ScheduleFlush();
 
   void
   Flush();
@@ -1323,16 +1378,17 @@ private:
   // No funny business allowed.
   CachedStatement(const CachedStatement&) = delete;
   CachedStatement& operator=(const CachedStatement&) = delete;
 };
 
 class Connection::FlushOp final
   : public ConnectionDatastoreOperationBase
 {
+  RefPtr<QuotaClient> mQuotaClient;
   WriteOptimizer mWriteOptimizer;
   bool mShadowWrites;
 
 public:
   FlushOp(Connection* aConnection,
           WriteOptimizer&& aWriteOptimizer);
 
 private:
@@ -1380,17 +1436,17 @@ public:
   IsOnConnectionThread();
 
   void
   AssertIsOnConnectionThread();
 
   already_AddRefed<Connection>
   CreateConnection(const nsACString& aOrigin,
                    const nsAString& aFilePath,
-                   nsAutoPtr<ArchivedOriginInfo>&& aArchivedOriginInfo);
+                   nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope);
 
   void
   Shutdown();
 
   NS_INLINE_DECL_REFCOUNTING(ConnectionThread)
 
 private:
   ~ConnectionThread();
@@ -2083,17 +2139,17 @@ class PrepareDatastoreOp
   };
 
   nsCOMPtr<nsIEventTarget> mMainEventTarget;
   RefPtr<ContentParent> mContentParent;
   RefPtr<PrepareDatastoreOp> mDelayedOp;
   RefPtr<DirectoryLock> mDirectoryLock;
   RefPtr<Connection> mConnection;
   RefPtr<Datastore> mDatastore;
-  nsAutoPtr<ArchivedOriginInfo> mArchivedOriginInfo;
+  nsAutoPtr<ArchivedOriginScope> mArchivedOriginScope;
   LoadDataOp* mLoadDataOp;
   nsDataHashtable<nsStringHashKey, nsString> mValues;
   nsTArray<LSItemInfo> mOrderedItems;
   const LSRequestPrepareDatastoreParams mParams;
   Maybe<ContentParentId> mContentParentId;
   nsCString mSuffix;
   nsCString mGroup;
   nsCString mMainThreadOrigin;
@@ -2348,68 +2404,254 @@ private:
   void
   GetResponse(LSSimpleRequestResponse& aResponse) override;
 };
 
 /*******************************************************************************
  * Other class declarations
  ******************************************************************************/
 
-class ArchivedOriginInfo
-{
-  nsCString mOriginSuffix;
+struct ArchivedOriginInfo
+{
+  OriginAttributes mOriginAttributes;
   nsCString mOriginNoSuffix;
 
+  ArchivedOriginInfo(const OriginAttributes& aOriginAttributes,
+                     const nsACString& aOriginNoSuffix)
+    : mOriginAttributes(aOriginAttributes)
+    , mOriginNoSuffix(aOriginNoSuffix)
+  { }
+};
+
+class ArchivedOriginScope
+{
+  struct Origin
+  {
+    nsCString mOriginSuffix;
+    nsCString mOriginNoSuffix;
+
+    Origin(const nsACString& aOriginSuffix,
+           const nsACString& aOriginNoSuffix)
+      : mOriginSuffix(aOriginSuffix)
+      , mOriginNoSuffix(aOriginNoSuffix)
+    { }
+
+    const nsACString&
+    OriginSuffix() const
+    {
+      return mOriginSuffix;
+    }
+
+    const nsACString&
+    OriginNoSuffix() const
+    {
+      return mOriginNoSuffix;
+    }
+  };
+
+  struct Prefix
+  {
+    nsCString mOriginNoSuffix;
+
+    explicit Prefix(const nsACString& aOriginNoSuffix)
+      : mOriginNoSuffix(aOriginNoSuffix)
+    { }
+
+    const nsACString&
+    OriginNoSuffix() const
+    {
+      return mOriginNoSuffix;
+    }
+  };
+
+  struct Pattern
+  {
+    UniquePtr<OriginAttributesPattern> mPattern;
+
+    explicit Pattern(const OriginAttributesPattern& aPattern)
+      : mPattern(MakeUnique<OriginAttributesPattern>(aPattern))
+    { }
+
+    Pattern(const Pattern& aOther)
+      : mPattern(MakeUnique<OriginAttributesPattern>(*aOther.mPattern))
+    { }
+
+    Pattern(Pattern&& aOther) = default;
+
+    const OriginAttributesPattern&
+    GetPattern() const
+    {
+      MOZ_ASSERT(mPattern);
+      return *mPattern;
+    }
+  };
+
+  struct Null
+  { };
+
+  using DataType = Variant<Origin, Pattern, Prefix, Null>;
+
+  DataType mData;
+
 public:
-  static ArchivedOriginInfo*
-  Create(nsIPrincipal* aPrincipal);
-
-  const nsCString&
+  static ArchivedOriginScope*
+  CreateFromOrigin(nsIPrincipal* aPrincipal);
+
+  static ArchivedOriginScope*
+  CreateFromPrefix(nsIPrincipal* aPrincipal);
+
+  static ArchivedOriginScope*
+  CreateFromPattern(const OriginAttributesPattern& aPattern);
+
+  static ArchivedOriginScope*
+  CreateFromNull();
+
+  bool
+  IsOrigin() const
+  {
+    return mData.is<Origin>();
+  }
+
+  bool
+  IsPrefix() const
+  {
+    return mData.is<Prefix>();
+  }
+
+  bool
+  IsPattern() const
+  {
+    return mData.is<Pattern>();
+  }
+
+  bool
+  IsNull() const
+  {
+    return mData.is<Null>();
+  }
+
+  const nsACString&
   OriginSuffix() const
   {
-    return mOriginSuffix;
-  }
-
-  const nsCString&
+    MOZ_ASSERT(IsOrigin());
+
+    return mData.as<Origin>().OriginSuffix();
+  }
+
+  const nsACString&
   OriginNoSuffix() const
   {
-    return mOriginNoSuffix;
-  }
-
-  const nsCString
-  Origin() const
+    MOZ_ASSERT(IsOrigin() || IsPrefix());
+
+    if (IsOrigin()) {
+      return mData.as<Origin>().OriginNoSuffix();
+    }
+    return mData.as<Prefix>().OriginNoSuffix();
+  }
+
+  const OriginAttributesPattern&
+  GetPattern() const
   {
-    return mOriginSuffix + NS_LITERAL_CSTRING(":") + mOriginNoSuffix;
+    MOZ_ASSERT(IsPattern());
+
+    return mData.as<Pattern>().GetPattern();
+  }
+
+  void
+  GetBindingClause(nsACString& aBindingClause) const;
+
+  nsresult
+  BindToStatement(mozIStorageStatement* aStatement) const;
+
+  bool
+  HasMatches(ArchivedOriginHashtable* aHashtable) const;
+
+  void
+  RemoveMatches(ArchivedOriginHashtable* aHashtable) const;
+
+private:
+  // Move constructors
+  explicit ArchivedOriginScope(const Origin&& aOrigin)
+    : mData(aOrigin)
+  { }
+
+  explicit ArchivedOriginScope(const Pattern&& aPattern)
+    : mData(aPattern)
+  { }
+
+  explicit ArchivedOriginScope(const Prefix&& aPrefix)
+    : mData(aPrefix)
+  { }
+
+  explicit ArchivedOriginScope(const Null&& aNull)
+    : mData(aNull)
+  { }
+};
+
+class ArchivedOriginScopeHelper
+  : public Runnable
+{
+  Monitor mMonitor;
+  const OriginAttributes mAttrs;
+  const nsCString mSpec;
+  nsAutoPtr<ArchivedOriginScope> mArchivedOriginScope;
+  nsresult mMainThreadResultCode;
+  bool mWaiting;
+  bool mPrefix;
+
+public:
+  ArchivedOriginScopeHelper(const nsACString& aSpec,
+                            const OriginAttributes& aAttrs,
+                            bool aPrefix)
+    : Runnable("dom::localstorage::ArchivedOriginScopeHelper")
+    , mMonitor("ArchivedOriginScopeHelper::mMonitor")
+    , mAttrs(aAttrs)
+    , mSpec(aSpec)
+    , mMainThreadResultCode(NS_OK)
+    , mWaiting(true)
+    , mPrefix(aPrefix)
+  {
+    AssertIsOnIOThread();
   }
 
   nsresult
-  BindToStatement(mozIStorageStatement* aStatement) const;
+  BlockAndReturnArchivedOriginScope(
+                          nsAutoPtr<ArchivedOriginScope>& aArchivedOriginScope);
 
 private:
-  ArchivedOriginInfo(const nsACString& aOriginSuffix,
-                     const nsACString& aOriginNoSuffix)
-    : mOriginSuffix(aOriginSuffix)
-    , mOriginNoSuffix(aOriginNoSuffix)
-  { }
+  nsresult
+  RunOnMainThread();
+
+  NS_DECL_NSIRUNNABLE
 };
 
 class QuotaClient final
   : public mozilla::dom::quota::Client
 {
   class ClearPrivateBrowsingRunnable;
   class Observer;
+  class MatchFunction;
 
   static QuotaClient* sInstance;
   static bool sObserversRegistered;
 
+  Mutex mShadowDatabaseMutex;
   bool mShutdownRequested;
 
 public:
   QuotaClient();
 
+  static QuotaClient*
+  GetInstance()
+  {
+    AssertIsOnBackgroundThread();
+
+    return sInstance;
+  }
+
   static bool
   IsShuttingDownOnBackgroundThread()
   {
     AssertIsOnBackgroundThread();
 
     if (sInstance) {
       return sInstance->IsShuttingDown();
     }
@@ -2423,16 +2665,24 @@ public:
     MOZ_ASSERT(!IsOnBackgroundThread());
 
     return QuotaManager::IsShuttingDown();
   }
 
   static nsresult
   RegisterObservers(nsIEventTarget* aBackgroundEventTarget);
 
+  mozilla::Mutex&
+  ShadowDatabaseMutex()
+  {
+    MOZ_ASSERT(IsOnIOThread() || IsOnConnectionThread());
+
+    return mShadowDatabaseMutex;
+  }
+
   bool
   IsShuttingDown() const
   {
     AssertIsOnBackgroundThread();
 
     return mShutdownRequested;
   }
 
@@ -2450,16 +2700,20 @@ public:
 
   nsresult
   GetUsageForOrigin(PersistenceType aPersistenceType,
                     const nsACString& aGroup,
                     const nsACString& aOrigin,
                     const AtomicBool& aCanceled,
                     UsageInfo* aUsageInfo) override;
 
+  nsresult
+  AboutToClearOrigins(const Nullable<PersistenceType>& aPersistenceType,
+                      const OriginScope& aOriginScope) override;
+
   void
   OnOriginClearCompleted(PersistenceType aPersistenceType,
                          const nsACString& aOrigin)
                          override;
 
   void
   ReleaseIOThreadObjects() override;
 
@@ -2475,16 +2729,26 @@ public:
   void
   StopIdleMaintenance() override;
 
   void
   ShutdownWorkThreads() override;
 
 private:
   ~QuotaClient() override;
+
+  nsresult
+  CreateArchivedOriginScope(
+                          const OriginScope& aOriginScope,
+                          nsAutoPtr<ArchivedOriginScope>& aArchivedOriginScope);
+
+  nsresult
+  PerformDelete(mozIStorageConnection* aConnection,
+                const nsACString& aSchemaName,
+                ArchivedOriginScope* aArchivedOriginScope) const;
 };
 
 class QuotaClient::ClearPrivateBrowsingRunnable final
   : public Runnable
 {
 public:
   ClearPrivateBrowsingRunnable()
     : Runnable("mozilla::dom::ClearPrivateBrowsingRunnable")
@@ -2516,16 +2780,33 @@ private:
   ~Observer()
   {
     MOZ_ASSERT(NS_IsMainThread());
   }
 
   NS_DECL_NSIOBSERVER
 };
 
+class QuotaClient::MatchFunction final
+  : public mozIStorageFunction
+{
+  OriginAttributesPattern mPattern;
+
+public:
+  explicit MatchFunction(const OriginAttributesPattern& aPattern)
+    : mPattern(aPattern)
+  { }
+
+private:
+  ~MatchFunction() = default;
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_MOZISTORAGEFUNCTION
+};
+
 /*******************************************************************************
  * Globals
  ******************************************************************************/
 
 typedef nsTArray<PrepareDatastoreOp*> PrepareDatastoreOpArray;
 
 StaticAutoPtr<PrepareDatastoreOpArray> gPrepareDatastoreOps;
 
@@ -2561,18 +2842,16 @@ Atomic<uint32_t, Relaxed> gOriginLimitKB
 Atomic<bool> gShadowWrites(kDefaultShadowWrites);
 Atomic<int32_t, Relaxed> gSnapshotPrefill(kDefaultSnapshotPrefill);
 
 typedef nsDataHashtable<nsCStringHashKey, int64_t> UsageHashtable;
 
 // Can only be touched on the Quota Manager I/O thread.
 StaticAutoPtr<UsageHashtable> gUsages;
 
-typedef nsTHashtable<nsCStringHashKey> ArchivedOriginHashtable;
-
 StaticAutoPtr<ArchivedOriginHashtable> gArchivedOrigins;
 
 // Can only be touched on the Quota Manager I/O thread.
 bool gInitializedShadowStorage = false;
 
 bool
 IsOnConnectionThread()
 {
@@ -2624,68 +2903,84 @@ LoadArchivedOrigins()
 
   if (!connection) {
     gArchivedOrigins = new ArchivedOriginHashtable();
     return NS_OK;
   }
 
   nsCOMPtr<mozIStorageStatement> stmt;
   rv = connection->CreateStatement(NS_LITERAL_CSTRING(
-    "SELECT DISTINCT originAttributes || ':' || originKey "
+    "SELECT DISTINCT originAttributes, originKey "
       "FROM webappsstore2;"
   ), getter_AddRefs(stmt));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsAutoPtr<ArchivedOriginHashtable> archivedOrigins(
     new ArchivedOriginHashtable());
 
   bool hasResult;
   while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&hasResult)) && hasResult) {
-    nsCString origin;
-    rv = stmt->GetUTF8String(0, origin);
+    nsCString originSuffix;
+    rv = stmt->GetUTF8String(0, originSuffix);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    nsCString originNoSuffix;
+    rv = stmt->GetUTF8String(1, originNoSuffix);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    archivedOrigins->PutEntry(origin);
+    nsCString hashKey = GetArchivedOriginHashKey(originSuffix, originNoSuffix);
+
+    OriginAttributes originAttributes;
+    if (NS_WARN_IF(!originAttributes.PopulateFromSuffix(originSuffix))) {
+      return NS_ERROR_FAILURE;
+    }
+
+    nsAutoPtr<ArchivedOriginInfo> archivedOriginInfo(
+      new ArchivedOriginInfo(originAttributes, originNoSuffix));
+
+    archivedOrigins->Put(hashKey, archivedOriginInfo.forget());
   }
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   gArchivedOrigins = archivedOrigins.forget();
   return NS_OK;
 }
 
 nsresult
 GetUsage(mozIStorageConnection* aConnection,
-         ArchivedOriginInfo* aArchivedOriginInfo,
+         ArchivedOriginScope* aArchivedOriginScope,
          int64_t* aUsage)
 {
   AssertIsOnIOThread();
   MOZ_ASSERT(aConnection);
   MOZ_ASSERT(aUsage);
 
   nsresult rv;
 
   nsCOMPtr<mozIStorageStatement> stmt;
-  if (aArchivedOriginInfo) {
+  if (aArchivedOriginScope) {
     rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
       "SELECT sum(length(key) + length(value)) "
       "FROM webappsstore2 "
       "WHERE originKey = :originKey "
       "AND originAttributes = :originAttributes;"
     ), getter_AddRefs(stmt));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    rv = aArchivedOriginInfo->BindToStatement(stmt);
+    rv = aArchivedOriginScope->BindToStatement(stmt);
   } else {
     rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
       "SELECT sum(length(key) + length(value)) "
       "FROM data"
     ), getter_AddRefs(stmt));
   }
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -3248,25 +3543,26 @@ AddItemInfo::Perform(Connection* aConnec
     "INSERT OR REPLACE INTO shadow.webappsstore2 "
       "(originAttributes, originKey, scope, key, value) "
       "VALUES (:originAttributes, :originKey, :scope, :key, :value) "),
     &stmt);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  ArchivedOriginInfo* archivedOriginInfo = aConnection->GetArchivedOriginInfo();
-
-  rv = archivedOriginInfo->BindToStatement(stmt);
+  ArchivedOriginScope* archivedOriginScope =
+    aConnection->GetArchivedOriginScope();
+
+  rv = archivedOriginScope->BindToStatement(stmt);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  nsCString scope = Scheme0Scope(archivedOriginInfo->OriginSuffix(),
-                                 archivedOriginInfo->OriginNoSuffix());
+  nsCString scope = Scheme0Scope(archivedOriginScope->OriginSuffix(),
+                                 archivedOriginScope->OriginNoSuffix());
 
   rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
                                   scope);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"), mKey);
@@ -3322,17 +3618,17 @@ RemoveItemInfo::Perform(Connection* aCon
       "WHERE originAttributes = :originAttributes "
       "AND originKey = :originKey "
       "AND key = :key;"),
     &stmt);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  rv = aConnection->GetArchivedOriginInfo()->BindToStatement(stmt);
+  rv = aConnection->GetArchivedOriginScope()->BindToStatement(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;
   }
@@ -3373,17 +3669,17 @@ ClearInfo::Perform(Connection* aConnecti
     "DELETE FROM shadow.webappsstore2 "
       "WHERE originAttributes = :originAttributes "
       "AND originKey = :originKey;"),
     &stmt);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  rv = aConnection->GetArchivedOriginInfo()->BindToStatement(stmt);
+  rv = aConnection->GetArchivedOriginScope()->BindToStatement(stmt);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = stmt->Execute();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -3496,19 +3792,19 @@ ConnectionDatastoreOperationBase::Run()
 
 /*******************************************************************************
  * Connection implementation
  ******************************************************************************/
 
 Connection::Connection(ConnectionThread* aConnectionThread,
                        const nsACString& aOrigin,
                        const nsAString& aFilePath,
-                       nsAutoPtr<ArchivedOriginInfo>&& aArchivedOriginInfo)
+                       nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope)
   : mConnectionThread(aConnectionThread)
-  , mArchivedOriginInfo(std::move(aArchivedOriginInfo))
+  , mArchivedOriginScope(std::move(aArchivedOriginScope))
   , mOrigin(aOrigin)
   , mFilePath(aFilePath)
   , mFlushScheduled(false)
 #ifdef DEBUG
   , mInUpdateBatch(false)
 #endif
 {
   AssertIsOnOwningThread();
@@ -3786,19 +4082,21 @@ CachedStatement::Assign(Connection* aCon
     mScoper.emplace(mStatement);
   }
 }
 
 Connection::
 FlushOp::FlushOp(Connection* aConnection,
                  WriteOptimizer&& aWriteOptimizer)
   : ConnectionDatastoreOperationBase(aConnection)
+  , mQuotaClient(QuotaClient::GetInstance())
   , mWriteOptimizer(std::move(aWriteOptimizer))
   , mShadowWrites(gShadowWrites)
 {
+  MOZ_ASSERT(mQuotaClient);
 }
 
 nsresult
 Connection::
 FlushOp::DoDatastoreWork()
 {
   AssertIsOnConnectionThread();
   MOZ_ASSERT(mConnection);
@@ -3807,17 +4105,23 @@ FlushOp::DoDatastoreWork()
   MOZ_ASSERT(quotaManager);
 
   nsCOMPtr<mozIStorageConnection> storageConnection =
     mConnection->StorageConnection();
   MOZ_ASSERT(storageConnection);
 
   nsresult rv;
 
+  Maybe<MutexAutoLock> shadowDatabaseLock;
+
   if (mShadowWrites) {
+    MOZ_ASSERT(mQuotaClient);
+
+    shadowDatabaseLock.emplace(mQuotaClient->ShadowDatabaseMutex());
+
     rv = AttachShadowDatabase(quotaManager->GetBasePath(), storageConnection);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   CachedStatement stmt;
   rv = mConnection->GetCachedStatement(NS_LITERAL_CSTRING("BEGIN IMMEDIATE;"),
@@ -3915,26 +4219,26 @@ ConnectionThread::IsOnConnectionThread()
 void
 ConnectionThread::AssertIsOnConnectionThread()
 {
   MOZ_ASSERT(IsOnConnectionThread());
 }
 
 already_AddRefed<Connection>
 ConnectionThread::CreateConnection(
-                            const nsACString& aOrigin,
-                            const nsAString& aFilePath,
-                            nsAutoPtr<ArchivedOriginInfo>&& aArchivedOriginInfo)
+                          const nsACString& aOrigin,
+                          const nsAString& aFilePath,
+                          nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(!aOrigin.IsEmpty());
   MOZ_ASSERT(!mConnections.GetWeak(aOrigin));
 
   RefPtr<Connection> connection =
-    new Connection(this, aOrigin, aFilePath, std::move(aArchivedOriginInfo));
+    new Connection(this, aOrigin, aFilePath, std::move(aArchivedOriginScope));
   mConnections.Put(aOrigin, connection);
 
   return connection.forget();
 }
 
 void
 ConnectionThread::Shutdown()
 {
@@ -5623,18 +5927,18 @@ PrepareDatastoreOp::Open()
       return rv;
     }
 
     rv = principal->GetPrivateBrowsingId(&mPrivateBrowsingId);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    mArchivedOriginInfo = ArchivedOriginInfo::Create(principal);
-    if (NS_WARN_IF(!mArchivedOriginInfo)) {
+    mArchivedOriginScope = ArchivedOriginScope::CreateFromOrigin(principal);
+    if (NS_WARN_IF(!mArchivedOriginScope)) {
       return NS_ERROR_FAILURE;
     }
   }
 
   // This service has to be started on the main thread currently.
   nsCOMPtr<mozIStorageService> ss;
   if (NS_WARN_IF(!(ss = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID)))) {
     return NS_ERROR_FAILURE;
@@ -5890,17 +6194,17 @@ PrepareDatastoreOp::SendToIOThread()
   MOZ_ALWAYS_SUCCEEDS(
     quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL));
 }
 
 nsresult
 PrepareDatastoreOp::DatabaseWork()
 {
   AssertIsOnIOThread();
-  MOZ_ASSERT(mArchivedOriginInfo);
+  MOZ_ASSERT(mArchivedOriginScope);
   MOZ_ASSERT(mState == State::Nesting);
   MOZ_ASSERT(mNestedState == NestedState::DatabaseWorkOpen);
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
       !MayProceedOnNonOwningThread()) {
     return NS_ERROR_FAILURE;
   }
 
@@ -5912,18 +6216,17 @@ PrepareDatastoreOp::DatabaseWork()
   if (!gArchivedOrigins) {
     rv = LoadArchivedOrigins();
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     MOZ_ASSERT(gArchivedOrigins);
   }
 
-  bool hasDataForMigration =
-    gArchivedOrigins->GetEntry(mArchivedOriginInfo->Origin());
+  bool hasDataForMigration = mArchivedOriginScope->HasMatches(gArchivedOrigins);
 
   bool createIfNotExists = mParams.createIfNotExists() || hasDataForMigration;
 
   nsCOMPtr<nsIFile> directoryEntry;
   rv = quotaManager->EnsureOriginIsInitialized(PERSISTENCE_TYPE_DEFAULT,
                                                mSuffix,
                                                mGroup,
                                                mOrigin,
@@ -5998,17 +6301,17 @@ PrepareDatastoreOp::DatabaseWork()
     MOZ_ASSERT(mUsage == 0);
 
     rv = AttachArchiveDatabase(quotaManager->GetStoragePath(), connection);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     int64_t newUsage;
-    rv = GetUsage(connection, mArchivedOriginInfo, &newUsage);
+    rv = GetUsage(connection, mArchivedOriginScope, &newUsage);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     RefPtr<QuotaObject> quotaObject = GetQuotaObject();
     MOZ_ASSERT(quotaObject);
 
     if (!quotaObject->MaybeUpdateSize(newUsage, /* aTruncate */ true)) {
@@ -6026,17 +6329,17 @@ PrepareDatastoreOp::DatabaseWork()
         "WHERE originKey = :originKey "
         "AND originAttributes = :originAttributes;"
 
     ), getter_AddRefs(stmt));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    rv = mArchivedOriginInfo->BindToStatement(stmt);
+    rv = mArchivedOriginScope->BindToStatement(stmt);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     rv = stmt->Execute();
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
@@ -6045,17 +6348,17 @@ PrepareDatastoreOp::DatabaseWork()
       "DELETE FROM webappsstore2 "
         "WHERE originKey = :originKey "
         "AND originAttributes = :originAttributes;"
     ), getter_AddRefs(stmt));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    rv = mArchivedOriginInfo->BindToStatement(stmt);
+    rv = mArchivedOriginScope->BindToStatement(stmt);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     rv = stmt->Execute();
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
@@ -6066,18 +6369,18 @@ PrepareDatastoreOp::DatabaseWork()
     }
 
     rv = DetachArchiveDatabase(connection);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     MOZ_ASSERT(gArchivedOrigins);
-    MOZ_ASSERT(gArchivedOrigins->GetEntry(mArchivedOriginInfo->Origin()));
-    gArchivedOrigins->RemoveEntry(mArchivedOriginInfo->Origin());
+    MOZ_ASSERT(mArchivedOriginScope->HasMatches(gArchivedOrigins));
+    mArchivedOriginScope->RemoveMatches(gArchivedOrigins);
 
     mUsage = newUsage;
 
     MOZ_ASSERT(gUsages);
     MOZ_ASSERT(gUsages->Contains(mOrigin));
     gUsages->Put(mOrigin, newUsage);
   }
 
@@ -6245,17 +6548,17 @@ PrepareDatastoreOp::BeginLoadData()
 
   if (!gConnectionThread) {
     gConnectionThread = new ConnectionThread();
   }
 
   mConnection =
     gConnectionThread->CreateConnection(mOrigin,
                                         mDatabaseFilePath,
-                                        std::move(mArchivedOriginInfo));
+                                        std::move(mArchivedOriginScope));
   MOZ_ASSERT(mConnection);
 
   // Must set this before dispatching otherwise we will race with the
   // connection thread.
   mNestedState = NestedState::DatabaseWorkLoadData;
 
   // Can't assign to mLoadDataOp directly since that's a weak reference and
   // LoadDataOp is reference counted.
@@ -6897,67 +7200,374 @@ PreloadedOp::GetResponse(LSSimpleRequest
 
   LSSimpleRequestPreloadedResponse preloadedResponse;
   preloadedResponse.preloaded() = preloaded;
 
   aResponse = preloadedResponse;
 }
 
 /*******************************************************************************
- * ArchivedOriginInfo
+ * ArchivedOriginScope
  ******************************************************************************/
 
 // static
-ArchivedOriginInfo*
-ArchivedOriginInfo::Create(nsIPrincipal* aPrincipal)
+ArchivedOriginScope*
+ArchivedOriginScope::CreateFromOrigin(nsIPrincipal* aPrincipal)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aPrincipal);
+
+  nsCString originAttrSuffix;
+  nsCString originKey;
+  nsresult rv = GenerateOriginKey(aPrincipal, originAttrSuffix, originKey);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return nullptr;
+  }
+
+  return new ArchivedOriginScope(std::move(Origin(originAttrSuffix,
+                                                  originKey)));
+}
+
+// static
+ArchivedOriginScope*
+ArchivedOriginScope::CreateFromPrefix(nsIPrincipal* aPrincipal)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aPrincipal);
 
   nsCString originAttrSuffix;
   nsCString originKey;
   nsresult rv = GenerateOriginKey(aPrincipal, originAttrSuffix, originKey);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return nullptr;
   }
 
-  return new ArchivedOriginInfo(originAttrSuffix, originKey);
+  return new ArchivedOriginScope(std::move(Prefix(originKey)));
+}
+
+// static
+ArchivedOriginScope*
+ArchivedOriginScope::CreateFromPattern(const OriginAttributesPattern& aPattern)
+{
+  return new ArchivedOriginScope(std::move(Pattern(aPattern)));
+}
+
+// static
+ArchivedOriginScope*
+ArchivedOriginScope::CreateFromNull()
+{
+  return new ArchivedOriginScope(std::move(Null()));
+}
+
+void
+ArchivedOriginScope::GetBindingClause(nsACString& aBindingClause) const
+{
+  struct Matcher
+  {
+    nsACString* mBindingClause;
+
+    explicit Matcher(nsACString* aBindingClause)
+      : mBindingClause(aBindingClause)
+    { }
+
+    void
+    match(const Origin& aOrigin) {
+      *mBindingClause = NS_LITERAL_CSTRING(
+        " WHERE originKey = :originKey "
+        "AND originAttributes = :originAttributes"
+      );
+    }
+
+    void
+    match(const Prefix& aPrefix) {
+      *mBindingClause = NS_LITERAL_CSTRING(
+        " WHERE originKey = :originKey"
+      );
+    }
+
+    void
+    match(const Pattern& aPattern) {
+      *mBindingClause = NS_LITERAL_CSTRING(
+        " WHERE originAttributes MATCH :originAttributesPattern"
+      );
+    }
+
+    void
+    match(const Null& aNull) {
+      *mBindingClause = EmptyCString();
+    }
+  };
+
+  mData.match(Matcher(&aBindingClause));
 }
 
 nsresult
-ArchivedOriginInfo::BindToStatement(mozIStorageStatement* aStatement) const
+ArchivedOriginScope::BindToStatement(mozIStorageStatement* aStmt) const
 {
   MOZ_ASSERT(IsOnIOThread() || IsOnConnectionThread());
-  MOZ_ASSERT(aStatement);
-
-  nsresult rv =
-    aStatement->BindUTF8StringByName(NS_LITERAL_CSTRING("originKey"),
-                                     mOriginNoSuffix);
+  MOZ_ASSERT(aStmt);
+
+  struct Matcher
+  {
+    mozIStorageStatement* mStmt;
+
+    explicit Matcher(mozIStorageStatement* aStmt)
+      : mStmt(aStmt)
+    { }
+
+    nsresult
+    match(const Origin& aOrigin) {
+      nsresult rv = mStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originKey"),
+                                                aOrigin.OriginNoSuffix());
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      rv = mStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
+                                       aOrigin.OriginSuffix());
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      return NS_OK;
+    }
+
+    nsresult
+    match(const Prefix& aPrefix) {
+      nsresult rv = mStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originKey"),
+                                                aPrefix.OriginNoSuffix());
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      return NS_OK;
+    }
+
+    nsresult
+    match(const Pattern& aPattern) {
+      nsresult rv = mStmt->BindUTF8StringByName(
+                                  NS_LITERAL_CSTRING("originAttributesPattern"),
+                                  NS_LITERAL_CSTRING("pattern1"));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      return NS_OK;
+    }
+
+    nsresult
+    match(const Null& aNull) {
+      return NS_OK;
+    }
+  };
+
+  nsresult rv = mData.match(Matcher(aStmt));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  rv = aStatement->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
-                                        mOriginSuffix);
+  return NS_OK;
+}
+
+bool
+ArchivedOriginScope::HasMatches(ArchivedOriginHashtable* aHashtable) const
+{
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aHashtable);
+
+  struct Matcher
+  {
+    ArchivedOriginHashtable* mHashtable;
+
+    explicit Matcher(ArchivedOriginHashtable* aHashtable)
+      : mHashtable(aHashtable)
+    { }
+
+    bool
+    match(const Origin& aOrigin) {
+      nsCString hashKey = GetArchivedOriginHashKey(aOrigin.OriginSuffix(),
+                                                   aOrigin.OriginNoSuffix());
+
+      ArchivedOriginInfo* archivedOriginInfo;
+      return mHashtable->Get(hashKey, &archivedOriginInfo);
+    }
+
+    bool
+    match(const Prefix& aPrefix) {
+      for (auto iter = mHashtable->ConstIter(); !iter.Done(); iter.Next()) {
+        ArchivedOriginInfo* archivedOriginInfo = iter.Data();
+
+        if (archivedOriginInfo->mOriginNoSuffix == aPrefix.OriginNoSuffix()) {
+          return true;
+        }
+      }
+
+      return false;
+    }
+
+    bool
+    match(const Pattern& aPattern) {
+      for (auto iter = mHashtable->ConstIter(); !iter.Done(); iter.Next()) {
+        ArchivedOriginInfo* archivedOriginInfo = iter.Data();
+
+        if (aPattern.GetPattern().Matches(
+                                       archivedOriginInfo->mOriginAttributes)) {
+          return true;
+        }
+      }
+
+      return false;
+    }
+
+    bool
+    match(const Null& aNull) {
+      return mHashtable->Count();
+    }
+  };
+
+  return mData.match(Matcher(aHashtable));
+}
+
+void
+ArchivedOriginScope::RemoveMatches(ArchivedOriginHashtable* aHashtable) const
+{
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aHashtable);
+
+  struct Matcher
+  {
+    ArchivedOriginHashtable* mHashtable;
+
+    explicit Matcher(ArchivedOriginHashtable* aHashtable)
+      : mHashtable(aHashtable)
+    { }
+
+    void
+    match(const Origin& aOrigin) {
+      nsCString hashKey = GetArchivedOriginHashKey(aOrigin.OriginSuffix(),
+                                                   aOrigin.OriginNoSuffix());
+
+      mHashtable->Remove(hashKey);
+    }
+
+    void
+    match(const Prefix& aPrefix) {
+      for (auto iter = mHashtable->Iter(); !iter.Done(); iter.Next()) {
+        ArchivedOriginInfo* archivedOriginInfo = iter.Data();
+
+        if (archivedOriginInfo->mOriginNoSuffix == aPrefix.OriginNoSuffix()) {
+          iter.Remove();
+        }
+      }
+    }
+
+    void
+    match(const Pattern& aPattern) {
+      for (auto iter = mHashtable->Iter(); !iter.Done(); iter.Next()) {
+        ArchivedOriginInfo* archivedOriginInfo = iter.Data();
+
+        if (aPattern.GetPattern().Matches(
+                                       archivedOriginInfo->mOriginAttributes)) {
+          iter.Remove();
+        }
+      }
+    }
+
+    void
+    match(const Null& aNull) {
+      mHashtable->Clear();
+    }
+  };
+
+  mData.match(Matcher(aHashtable));
+}
+
+/*******************************************************************************
+ * ArchivedOriginScopeHelper
+ ******************************************************************************/
+
+nsresult
+ArchivedOriginScopeHelper::BlockAndReturnArchivedOriginScope(
+                           nsAutoPtr<ArchivedOriginScope>& aArchivedOriginScope)
+{
+  AssertIsOnIOThread();
+
+  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
+
+  mozilla::MonitorAutoLock lock(mMonitor);
+  while (mWaiting) {
+    lock.Wait();
+  }
+
+  if (NS_WARN_IF(NS_FAILED(mMainThreadResultCode))) {
+    return mMainThreadResultCode;
+  }
+
+  aArchivedOriginScope = std::move(mArchivedOriginScope);
+  return NS_OK;
+}
+
+nsresult
+ArchivedOriginScopeHelper::RunOnMainThread()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsCOMPtr<nsIURI> uri;
+  nsresult rv = NS_NewURI(getter_AddRefs(uri), mSpec);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  nsCOMPtr<nsIPrincipal> principal =
+    BasePrincipal::CreateCodebasePrincipal(uri, mAttrs);
+  if (NS_WARN_IF(!principal)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (mPrefix) {
+    mArchivedOriginScope = ArchivedOriginScope::CreateFromPrefix(principal);
+  } else {
+    mArchivedOriginScope = ArchivedOriginScope::CreateFromOrigin(principal);
+  }
+  if (NS_WARN_IF(!mArchivedOriginScope)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ArchivedOriginScopeHelper::Run()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsresult rv = RunOnMainThread();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    mMainThreadResultCode = rv;
+  }
+
+  mozilla::MonitorAutoLock lock(mMonitor);
+  MOZ_ASSERT(mWaiting);
+
+  mWaiting = false;
+  lock.Notify();
+
   return NS_OK;
 }
 
 /*******************************************************************************
  * QuotaClient
  ******************************************************************************/
 
 QuotaClient* QuotaClient::sInstance = nullptr;
 bool QuotaClient::sObserversRegistered = false;
 
 QuotaClient::QuotaClient()
-  : mShutdownRequested(false)
+  : mShadowDatabaseMutex("LocalStorage mShadowDatabaseMutex")
+  , mShutdownRequested(false)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(!sInstance, "We expect this to be a singleton!");
 
   sInstance = this;
 }
 
 QuotaClient::~QuotaClient()
@@ -7083,17 +7693,17 @@ QuotaClient::InitOrigin(PersistenceType 
 
     nsCOMPtr<mozIStorageConnection> connection;
     rv = CreateStorageConnection(file, aOrigin, getter_AddRefs(connection));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     int64_t usage;
-    rv = GetUsage(connection, /* aArchivedOriginInfo */ nullptr, &usage);
+    rv = GetUsage(connection, /* aArchivedOriginScope */ nullptr, &usage);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     InitUsageForOrigin(aOrigin, usage);
 
     aUsageInfo->AppendToDatabaseUsage(uint64_t(usage));
   }
@@ -7174,16 +7784,187 @@ QuotaClient::GetUsageForOrigin(Persisten
     if (gUsages->Get(aOrigin, &usage)) {
       aUsageInfo->AppendToDatabaseUsage(usage);
     }
   }
 
   return NS_OK;
 }
 
+nsresult
+QuotaClient::AboutToClearOrigins(
+                              const Nullable<PersistenceType>& aPersistenceType,
+                              const OriginScope& aOriginScope)
+{
+  AssertIsOnIOThread();
+
+  // This method is not called when the clearing is triggered by the eviction
+  // process. It's on purpose to avoid a problem with the origin access time
+  // which can be described as follows:
+  // When there's a storage pressure condition and quota manager starts
+  // collecting origins for eviction, there can be an origin that hasn't been
+  // touched for long time. However, the old implementation of local storage
+  // could have touched the origin only recently and the new implementation
+  // hasn't had a chance to create a new per origin database for it yet (the
+  // data is still in the archive database), so the origin access time hasn't
+  // been updated either. In the end, the origin would be evicted despite the
+  // fact that there was recent local storage activity.
+  // So this method clears the archived data and shadow database entries for
+  // given origin scope, but only if it's a privacy-related origin clearing.
+
+  if (!aPersistenceType.IsNull() &&
+      aPersistenceType.Value() != PERSISTENCE_TYPE_DEFAULT) {
+    return NS_OK;
+  }
+
+  bool shadowWrites = gShadowWrites;
+
+  nsAutoPtr<ArchivedOriginScope> archivedOriginScope;
+  nsresult rv = CreateArchivedOriginScope(aOriginScope, archivedOriginScope);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (!gArchivedOrigins) {
+    rv = LoadArchivedOrigins();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+    MOZ_ASSERT(gArchivedOrigins);
+  }
+
+  bool hasDataForRemoval = archivedOriginScope->HasMatches(gArchivedOrigins);
+
+  QuotaManager* quotaManager = QuotaManager::Get();
+  MOZ_ASSERT(quotaManager);
+
+  nsString basePath = quotaManager->GetBasePath();
+
+  {
+    MutexAutoLock shadowDatabaseLock(mShadowDatabaseMutex);
+
+    nsCOMPtr<mozIStorageConnection> connection;
+    if (gInitializedShadowStorage) {
+      rv = GetShadowStorageConnection(basePath, getter_AddRefs(connection));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+    } else {
+      rv = CreateShadowStorageConnection(basePath, getter_AddRefs(connection));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      gInitializedShadowStorage = true;
+    }
+
+    if (hasDataForRemoval) {
+      rv = AttachArchiveDatabase(quotaManager->GetStoragePath(), connection);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+    }
+
+    if (archivedOriginScope->IsPattern()) {
+      nsCOMPtr<mozIStorageFunction> function(
+        new MatchFunction(archivedOriginScope->GetPattern()));
+
+      rv = connection->CreateFunction(NS_LITERAL_CSTRING("match"), 2, function);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+    }
+
+    nsCOMPtr<mozIStorageStatement> stmt;
+    rv = connection->CreateStatement(NS_LITERAL_CSTRING(
+      "BEGIN IMMEDIATE;"
+    ), getter_AddRefs(stmt));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    rv = stmt->Execute();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (shadowWrites) {
+      rv = PerformDelete(connection,
+                         NS_LITERAL_CSTRING("main"),
+                         archivedOriginScope);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+    }
+
+    if (hasDataForRemoval) {
+      rv = PerformDelete(connection,
+                         NS_LITERAL_CSTRING("archive"),
+                         archivedOriginScope);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+    }
+
+    rv = connection->CreateStatement(NS_LITERAL_CSTRING(
+      "COMMIT;"
+    ), getter_AddRefs(stmt));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    rv = stmt->Execute();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    stmt = nullptr;
+
+    if (archivedOriginScope->IsPattern()) {
+      rv = connection->RemoveFunction(NS_LITERAL_CSTRING("match"));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+    }
+
+    if (hasDataForRemoval) {
+      rv = DetachArchiveDatabase(connection);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      MOZ_ASSERT(gArchivedOrigins);
+      MOZ_ASSERT(archivedOriginScope->HasMatches(gArchivedOrigins));
+      archivedOriginScope->RemoveMatches(gArchivedOrigins);
+    }
+
+    rv = connection->Close();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  if (aOriginScope.IsNull()) {
+    nsCOMPtr<nsIFile> shadowFile;
+    rv = GetShadowFile(basePath, getter_AddRefs(shadowFile));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    rv = shadowFile->Remove(false);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    gInitializedShadowStorage = false;
+  }
+
+  return NS_OK;
+}
+
 void
 QuotaClient::OnOriginClearCompleted(PersistenceType aPersistenceType,
                                     const nsACString& aOrigin)
 {
   AssertIsOnIOThread();
 
   if (aPersistenceType != PERSISTENCE_TYPE_DEFAULT) {
     return;
@@ -7196,16 +7977,19 @@ QuotaClient::OnOriginClearCompleted(Pers
 
 void
 QuotaClient::ReleaseIOThreadObjects()
 {
   AssertIsOnIOThread();
 
   gUsages = nullptr;
 
+  // Delete archived origins hashtable since QuotaManager clears the whole
+  // storage directory including ls-archive.sqlite.
+
   gArchivedOrigins = nullptr;
 }
 
 void
 QuotaClient::AbortOperations(const nsACString& aOrigin)
 {
   AssertIsOnBackgroundThread();
 
@@ -7339,16 +8123,110 @@ QuotaClient::ShutdownWorkThreads()
   // And finally, shutdown the connection thread.
   if (gConnectionThread) {
     gConnectionThread->Shutdown();
 
     gConnectionThread = nullptr;
   }
 }
 
+nsresult
+QuotaClient::CreateArchivedOriginScope(
+                           const OriginScope& aOriginScope,
+                           nsAutoPtr<ArchivedOriginScope>& aArchivedOriginScope)
+{
+  AssertIsOnIOThread();
+
+  nsresult rv;
+
+  nsAutoPtr<ArchivedOriginScope> archivedOriginScope;
+
+  if (aOriginScope.IsOrigin()) {
+    nsCString spec;
+    OriginAttributes attrs;
+    if (NS_WARN_IF(!QuotaManager::ParseOrigin(aOriginScope.GetOrigin(),
+                                              spec,
+                                              &attrs))) {
+      return NS_ERROR_FAILURE;
+    }
+
+    RefPtr<ArchivedOriginScopeHelper> helper =
+      new ArchivedOriginScopeHelper(spec, attrs, /* aPrefix */ false);
+
+    rv = helper->BlockAndReturnArchivedOriginScope(archivedOriginScope);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  } else if (aOriginScope.IsPrefix()) {
+    nsCString spec;
+    OriginAttributes attrs;
+    if (NS_WARN_IF(!QuotaManager::ParseOrigin(aOriginScope.GetOriginNoSuffix(),
+                                              spec,
+                                              &attrs))) {
+      return NS_ERROR_FAILURE;
+    }
+
+    RefPtr<ArchivedOriginScopeHelper> helper =
+      new ArchivedOriginScopeHelper(spec, attrs, /* aPrefix */ true);
+
+    rv = helper->BlockAndReturnArchivedOriginScope(archivedOriginScope);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  } else if (aOriginScope.IsPattern()) {
+    archivedOriginScope =
+      ArchivedOriginScope::CreateFromPattern(aOriginScope.GetPattern());
+  } else {
+    MOZ_ASSERT(aOriginScope.IsNull());
+
+    archivedOriginScope = ArchivedOriginScope::CreateFromNull();
+  }
+
+  MOZ_ASSERT(archivedOriginScope);
+
+  aArchivedOriginScope = std::move(archivedOriginScope);
+  return NS_OK;
+}
+
+nsresult
+QuotaClient::PerformDelete(mozIStorageConnection* aConnection,
+                           const nsACString& aSchemaName,
+                           ArchivedOriginScope* aArchivedOriginScope) const
+{
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aConnection);
+  MOZ_ASSERT(aArchivedOriginScope);
+
+  nsresult rv;
+
+  nsCString bindingClause;
+  aArchivedOriginScope->GetBindingClause(bindingClause);
+
+  nsCOMPtr<mozIStorageStatement> stmt;
+  rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
+    "DELETE FROM ") + aSchemaName + NS_LITERAL_CSTRING(".webappsstore2") +
+      bindingClause + NS_LITERAL_CSTRING(";"),
+    getter_AddRefs(stmt));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aArchivedOriginScope->BindToStatement(stmt);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->Execute();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 QuotaClient::
 ClearPrivateBrowsingRunnable::Run()
 {
   AssertIsOnBackgroundThread();
 
   if (gDatastores) {
     for (auto iter = gDatastores->ConstIter(); !iter.Done(); iter.Next()) {
@@ -7383,10 +8261,44 @@ Observer::Observe(nsISupports* aSubject,
 
     return NS_OK;
   }
 
   NS_WARNING("Unknown observer topic!");
   return NS_OK;
 }
 
+NS_IMPL_ISUPPORTS(QuotaClient::MatchFunction, mozIStorageFunction)
+
+NS_IMETHODIMP
+QuotaClient::
+MatchFunction::OnFunctionCall(mozIStorageValueArray* aFunctionArguments,
+                              nsIVariant** aResult)
+{
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aFunctionArguments);
+  MOZ_ASSERT(aResult);
+
+  nsCString suffix;
+  nsresult rv = aFunctionArguments->GetUTF8String(1, suffix);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  OriginAttributes oa;
+  if (NS_WARN_IF(!oa.PopulateFromSuffix(suffix))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  bool result = mPattern.Matches(oa);
+
+  RefPtr<nsVariant> outVar(new nsVariant());
+  rv = outVar->SetAsBool(result);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  outVar.forget(aResult);
+  return NS_OK;
+}
+
 } // namespace dom
 } // namespace mozilla