Bug 1286798 - Part 50: Add support for clearing of the archive and shadow database; r=asuth
authorJan Varga <jan.varga@gmail.com>
Thu, 29 Nov 2018 21:49:55 +0100
changeset 505268 351e6a4eed9a44ae376838df10cc3b3d575e48c0
parent 505267 66d175df810660b322fa907b97320d4a972cf9fa
child 505269 e21dc8a8b5b990ba6eac1def6dbc818b6691674a
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth
bugs1286798
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1286798 - Part 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
@@ -4,40 +4,43 @@
  * 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 "LSObject.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
@@ -52,24 +55,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
@@ -149,16 +157,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(
@@ -818,16 +833,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;
@@ -1214,17 +1269,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;
@@ -1234,20 +1289,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);
@@ -1298,17 +1353,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();
@@ -1342,16 +1397,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:
@@ -1399,17 +1455,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();
@@ -2102,17 +2158,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;
@@ -2367,68 +2423,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();
     }
@@ -2442,16 +2684,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;
   }
 
@@ -2469,16 +2719,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;
 
@@ -2494,16 +2748,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")
@@ -2535,16 +2799,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;
 
@@ -2580,18 +2861,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()
 {
@@ -2643,68 +2922,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;
@@ -3263,25 +3558,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);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  nsCString scope = Scheme0Scope(archivedOriginInfo->OriginSuffix(),
-                                 archivedOriginInfo->OriginNoSuffix());
+  ArchivedOriginScope* archivedOriginScope =
+    aConnection->GetArchivedOriginScope();
+
+  rv = archivedOriginScope->BindToStatement(stmt);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  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);
@@ -3337,17 +3633,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;
   }
@@ -3388,17 +3684,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;
   }
@@ -3511,19 +3807,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();
@@ -3801,19 +4097,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);
@@ -3822,17 +4120,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;"),
@@ -3930,26 +4234,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()
 {
@@ -5638,18 +5942,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;
@@ -5905,17 +6209,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;
   }
 
@@ -5927,18 +6231,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,
@@ -6013,17 +6316,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)) {
@@ -6041,17 +6344,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;
     }
@@ -6060,17 +6363,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;
     }
@@ -6081,18 +6384,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);
   }
 
@@ -6260,17 +6563,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.
@@ -6912,67 +7215,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);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aStatement->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
-                                        mOriginSuffix);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
+  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;
+  }
+
+  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()
@@ -7098,17 +7708,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));
   }
@@ -7189,16 +7799,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;
@@ -7211,16 +7992,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();
 
@@ -7354,16 +8138,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()) {
@@ -7398,10 +8276,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