Bug 1286798 - Part 25: Add checks for the group and global limit; r=asuth
authorJan Varga <jan.varga@gmail.com>
Thu, 29 Nov 2018 21:48:34 +0100
changeset 448778 8c37e877e231ee830f377b2945291b1b747293d3
parent 448777 c0d40572c29bc3be2611f6e40bb554a55fcc54a3
child 448779 2620df4a91da32a75123274b2f603e45e78ca3df
push id110253
push userjvarga@mozilla.com
push dateThu, 29 Nov 2018 20:50:49 +0000
treeherdermozilla-inbound@11c709d6c6d9 [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 25: Add checks for the group and global limit; r=asuth
.eslintignore
dom/cache/FileUtils.cpp
dom/localstorage/ActorsParent.cpp
dom/localstorage/moz.build
dom/localstorage/test/unit/head.js
dom/localstorage/test/unit/test_eviction.js
dom/localstorage/test/unit/test_groupLimit.js
dom/localstorage/test/unit/xpcshell.ini
dom/quota/ActorsParent.cpp
dom/quota/QuotaManager.h
--- a/.eslintignore
+++ b/.eslintignore
@@ -185,16 +185,17 @@ dom/events/**
 dom/fetch/**
 dom/file/**
 dom/filesystem/**
 dom/flex/**
 dom/grid/**
 dom/html/**
 dom/ipc/**
 dom/jsurl/**
+dom/localstorage/**
 dom/manifest/**
 dom/media/test/**
 dom/media/tests/**
 dom/media/webaudio/**
 dom/media/webspeech/**
 dom/messagechannel/**
 dom/midi/**
 dom/network/**
--- a/dom/cache/FileUtils.cpp
+++ b/dom/cache/FileUtils.cpp
@@ -289,17 +289,17 @@ BodyMaybeUpdatePaddingSize(const QuotaIn
   MOZ_DIAGNOSTIC_ASSERT(bodyFile);
 
   QuotaManager* quotaManager = QuotaManager::Get();
   MOZ_DIAGNOSTIC_ASSERT(quotaManager);
 
   int64_t fileSize = 0;
   RefPtr<QuotaObject> quotaObject =
     quotaManager->GetQuotaObject(PERSISTENCE_TYPE_DEFAULT, aQuotaInfo.mGroup,
-                                 aQuotaInfo.mOrigin, bodyFile, &fileSize);
+                                 aQuotaInfo.mOrigin, bodyFile, -1, &fileSize);
   MOZ_DIAGNOSTIC_ASSERT(quotaObject);
   MOZ_DIAGNOSTIC_ASSERT(fileSize >= 0);
   // XXXtt: bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1422815
   if (!quotaObject) { return NS_ERROR_UNEXPECTED; }
 
   if (*aPaddingSizeOut == InternalResponse::UNKNOWN_PADDING_SIZE) {
     *aPaddingSizeOut = BodyGeneratePadding(fileSize, aPaddingInfo);
   }
--- a/dom/localstorage/ActorsParent.cpp
+++ b/dom/localstorage/ActorsParent.cpp
@@ -16,16 +16,17 @@
 #include "mozilla/Unused.h"
 #include "mozilla/dom/PBackgroundLSDatabaseParent.h"
 #include "mozilla/dom/PBackgroundLSObjectParent.h"
 #include "mozilla/dom/PBackgroundLSObserverParent.h"
 #include "mozilla/dom/PBackgroundLSRequestParent.h"
 #include "mozilla/dom/PBackgroundLSSharedTypes.h"
 #include "mozilla/dom/PBackgroundLSSimpleRequestParent.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"
@@ -857,16 +858,17 @@ private:
   nsresult
   DoDatastoreWork() override;
 };
 
 class Datastore final
 {
   RefPtr<DirectoryLock> mDirectoryLock;
   RefPtr<Connection> mConnection;
+  RefPtr<QuotaObject> mQuotaObject;
   nsCOMPtr<nsITimer> mAutoCommitTimer;
   nsCOMPtr<nsIRunnable> mCompleteCallback;
   nsTHashtable<nsPtrHashKey<PrepareDatastoreOp>> mPrepareDatastoreOps;
   nsTHashtable<nsPtrHashKey<PreparedDatastore>> mPreparedDatastores;
   nsTHashtable<nsPtrHashKey<Database>> mDatabases;
   nsDataHashtable<nsStringHashKey, nsString> mValues;
   const nsCString mOrigin;
   const uint32_t mPrivateBrowsingId;
@@ -875,16 +877,17 @@ class Datastore final
 
 public:
   // Created by PrepareDatastoreOp.
   Datastore(const nsACString& aOrigin,
             uint32_t aPrivateBrowsingId,
             int64_t aUsage,
             already_AddRefed<DirectoryLock>&& aDirectoryLock,
             already_AddRefed<Connection>&& aConnection,
+            already_AddRefed<QuotaObject>&& aQuotaObject,
             nsDataHashtable<nsStringHashKey, nsString>& aValues);
 
   const nsCString&
   Origin() const
   {
     return mOrigin;
   }
 
@@ -1431,16 +1434,20 @@ class PrepareDatastoreOp
   nsString mDatabaseFilePath;
   uint32_t mPrivateBrowsingId;
   int64_t mUsage;
   NestedState mNestedState;
   bool mDatabaseNotAvailable;
   bool mRequestedDirectoryLock;
   bool mInvalidated;
 
+#ifdef DEBUG
+  int64_t mDEBUGUsage;
+#endif
+
 public:
   PrepareDatastoreOp(nsIEventTarget* aMainEventTarget,
                      const LSRequestParams& aParams);
 
   bool
   OriginIsKnown() const
   {
     AssertIsOnOwningThread();
@@ -1499,17 +1506,19 @@ private:
 
   nsresult
   DatabaseWork();
 
   nsresult
   DatabaseNotAvailable();
 
   nsresult
-  EnsureDirectoryEntry(nsIFile* aEntry, bool aDirectory);
+  EnsureDirectoryEntry(nsIFile* aEntry,
+                       bool aDirectory,
+                       bool* aAlreadyExisted = nullptr);
 
   nsresult
   VerifyDatabaseInformation(mozIStorageConnection* aConnection);
 
   nsresult
   BeginLoadData();
 
   nsresult
@@ -1821,30 +1830,48 @@ StaticAutoPtr<PreparedObserverHashtable>
 
 typedef nsClassHashtable<nsCStringHashKey, nsTArray<Observer*>>
   ObserverHashtable;
 
 StaticAutoPtr<ObserverHashtable> gObservers;
 
 Atomic<uint32_t, Relaxed> gOriginLimitKB(kDefaultOriginLimitKB);
 
+typedef nsDataHashtable<nsCStringHashKey, int64_t> UsageHashtable;
+
+// Can only be touched on the Quota Manager I/O thread.
+StaticAutoPtr<UsageHashtable> gUsages;
+
 bool
 IsOnConnectionThread()
 {
   MOZ_ASSERT(gConnectionThread);
   return gConnectionThread->IsOnConnectionThread();
 }
 
 void
 AssertIsOnConnectionThread()
 {
   MOZ_ASSERT(gConnectionThread);
   gConnectionThread->AssertIsOnConnectionThread();
 }
 
+void
+InitUsageForOrigin(const nsACString& aOrigin, int64_t aUsage)
+{
+  AssertIsOnIOThread();
+
+  if (!gUsages) {
+    gUsages = new UsageHashtable();
+  }
+
+  MOZ_ASSERT(!gUsages->Contains(aOrigin));
+  gUsages->Put(aOrigin, aUsage);
+}
+
 } // namespace
 
 /*******************************************************************************
  * Exported functions
  ******************************************************************************/
 
 PBackgroundLSObjectParent*
 AllocPBackgroundLSObjectParent(const PrincipalInfo& aPrincipalInfo,
@@ -2618,19 +2645,21 @@ ClearOp::DoDatastoreWork()
  * Datastore
  ******************************************************************************/
 
 Datastore::Datastore(const nsACString& aOrigin,
                      uint32_t aPrivateBrowsingId,
                      int64_t aUsage,
                      already_AddRefed<DirectoryLock>&& aDirectoryLock,
                      already_AddRefed<Connection>&& aConnection,
+                     already_AddRefed<QuotaObject>&& aQuotaObject,
                      nsDataHashtable<nsStringHashKey, nsString>& aValues)
   : mDirectoryLock(std::move(aDirectoryLock))
   , mConnection(std::move(aConnection))
+  , mQuotaObject(std::move(aQuotaObject))
   , mOrigin(aOrigin)
   , mPrivateBrowsingId(aPrivateBrowsingId)
   , mUsage(aUsage)
   , mClosed(false)
 {
   AssertIsOnBackgroundThread();
 
   mValues.SwapElements(aValues);
@@ -2649,16 +2678,17 @@ Datastore::Close()
   MOZ_ASSERT(!mClosed);
   MOZ_ASSERT(!mDatabases.Count());
   MOZ_ASSERT(mDirectoryLock);
 
   mClosed = true;
 
   if (IsPersistent()) {
     MOZ_ASSERT(mConnection);
+    MOZ_ASSERT(mQuotaObject);
 
     if (mConnection->InTransaction()) {
       MOZ_ASSERT(mAutoCommitTimer);
       MOZ_ALWAYS_SUCCEEDS(mAutoCommitTimer->Cancel());
 
       mConnection->Commit();
 
       mAutoCommitTimer = nullptr;
@@ -2668,16 +2698,17 @@ Datastore::Close()
     // hashtable until the connection is fully closed.
     nsCOMPtr<nsIRunnable> callback =
       NewRunnableMethod("dom::Datastore::ConnectionClosedCallback",
                         this,
                         &Datastore::ConnectionClosedCallback);
     mConnection->Close(callback);
   } else {
     MOZ_ASSERT(!mConnection);
+    MOZ_ASSERT(!mQuotaObject);
 
     // There's no connection, so it's safe to release the directory lock and
     // unregister itself from the hashtable.
 
     mDirectoryLock = nullptr;
 
     CleanupMetadata();
   }
@@ -2982,18 +3013,22 @@ Datastore::MaybeClose()
 }
 
 void
 Datastore::ConnectionClosedCallback()
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(mDirectoryLock);
   MOZ_ASSERT(mConnection);
+  MOZ_ASSERT(mQuotaObject);
   MOZ_ASSERT(mClosed);
 
+  // Release the quota object first.
+  mQuotaObject = nullptr;
+
   // Now it's safe to release the directory lock and unregister itself from
   // the hashtable.
 
   mDirectoryLock = nullptr;
   mConnection = nullptr;
 
   CleanupMetadata();
 
@@ -3016,23 +3051,52 @@ Datastore::CleanupMetadata()
   }
 }
 
 bool
 Datastore::UpdateUsage(int64_t aDelta)
 {
   AssertIsOnBackgroundThread();
 
+  // Check internal LocalStorage origin limit.
   int64_t newUsage = mUsage + aDelta;
   if (newUsage > gOriginLimitKB * 1024) {
     return false;
   }
 
+  // Check QuotaManager limits (group and global limit).
+  if (IsPersistent()) {
+    MOZ_ASSERT(mQuotaObject);
+
+    if (!mQuotaObject->MaybeUpdateSize(newUsage, /* aTruncate */ true)) {
+      return false;
+    }
+
+  }
+
+  // Quota checks passed, set new usage.
+
   mUsage = newUsage;
 
+  if (IsPersistent()) {
+    RefPtr<Runnable> runnable = NS_NewRunnableFunction(
+      "Datastore::UpdateUsage",
+      [origin = mOrigin, newUsage] () {
+        MOZ_ASSERT(gUsages);
+        MOZ_ASSERT(gUsages->Contains(origin));
+        gUsages->Put(origin, newUsage);
+      });
+
+    QuotaManager* quotaManager = QuotaManager::Get();
+    MOZ_ASSERT(quotaManager);
+
+    MOZ_ALWAYS_SUCCEEDS(
+      quotaManager->IOThread()->Dispatch(runnable, NS_DISPATCH_NORMAL));
+  }
+
   return true;
 }
 
 void
 Datastore::NotifyObservers(Database* aDatabase,
                            const nsString& aKey,
                            const nsString& aOldValue,
                            const nsString& aNewValue)
@@ -3740,16 +3804,19 @@ PrepareDatastoreOp::PrepareDatastoreOp(n
   , mLoadDataOp(nullptr)
   , mParams(aParams.get_LSRequestPrepareDatastoreParams())
   , mPrivateBrowsingId(0)
   , mUsage(0)
   , mNestedState(NestedState::BeforeNesting)
   , mDatabaseNotAvailable(false)
   , mRequestedDirectoryLock(false)
   , mInvalidated(false)
+#ifdef DEBUG
+  , mDEBUGUsage(0)
+#endif
 {
   MOZ_ASSERT(aParams.type() ==
                LSRequestParams::TLSRequestPrepareDatastoreParams);
 }
 
 PrepareDatastoreOp::~PrepareDatastoreOp()
 {
   MOZ_ASSERT(!mDirectoryLock);
@@ -4071,24 +4138,34 @@ PrepareDatastoreOp::DatabaseWork()
     return rv;
   }
 
   rv = directoryEntry->Append(NS_LITERAL_STRING(DATA_FILE_NAME));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  rv = EnsureDirectoryEntry(directoryEntry, /* aIsDirectory */ false);
+  bool alreadyExisted;
+  rv = EnsureDirectoryEntry(directoryEntry,
+                            /* aIsDirectory */ false,
+                            &alreadyExisted);
   if (rv == NS_ERROR_NOT_AVAILABLE) {
     return DatabaseNotAvailable();
   }
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  if (alreadyExisted) {
+    MOZ_ASSERT(gUsages);
+    MOZ_ASSERT(gUsages->Get(mOrigin, &mUsage));
+  } else {
+    InitUsageForOrigin(mOrigin, 0);
+  }
+
   rv = directoryEntry->GetPath(mDatabaseFilePath);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsCOMPtr<mozIStorageConnection> connection;
   rv = CreateStorageConnection(directoryEntry,
                                mOrigin,
@@ -4136,17 +4213,19 @@ PrepareDatastoreOp::DatabaseNotAvailable
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
 nsresult
-PrepareDatastoreOp::EnsureDirectoryEntry(nsIFile* aEntry, bool aIsDirectory)
+PrepareDatastoreOp::EnsureDirectoryEntry(nsIFile* aEntry,
+                                         bool aIsDirectory,
+                                         bool* aAlreadyExisted)
 {
   AssertIsOnIOThread();
   MOZ_ASSERT(aEntry);
 
   bool exists;
   nsresult rv = aEntry->Exists(&exists);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -4167,16 +4246,19 @@ PrepareDatastoreOp::EnsureDirectoryEntry
 #ifdef DEBUG
   else {
     bool isDirectory;
     MOZ_ASSERT(NS_SUCCEEDED(aEntry->IsDirectory(&isDirectory)));
     MOZ_ASSERT(isDirectory == aIsDirectory);
   }
 #endif
 
+  if (aAlreadyExisted) {
+    *aAlreadyExisted = exists;
+  }
   return NS_OK;
 }
 
 nsresult
 PrepareDatastoreOp::VerifyDatabaseInformation(mozIStorageConnection* aConnection)
 {
   AssertIsOnIOThread();
   MOZ_ASSERT(aConnection);
@@ -4311,21 +4393,40 @@ PrepareDatastoreOp::GetResponse(LSReques
     prepareDatastoreResponse.datastoreId() = null_t();
 
     aResponse = prepareDatastoreResponse;
 
     return;
   }
 
   if (!mDatastore) {
+    MOZ_ASSERT(mUsage == mDEBUGUsage);
+
+    RefPtr<QuotaObject> quotaObject;
+
+    if (mPrivateBrowsingId == 0) {
+      MOZ_ASSERT(!mDatabaseFilePath.IsEmpty());
+
+      QuotaManager* quotaManager = QuotaManager::Get();
+      MOZ_ASSERT(quotaManager);
+
+      quotaObject = quotaManager->GetQuotaObject(PERSISTENCE_TYPE_DEFAULT,
+                                                 mGroup,
+                                                 mOrigin,
+                                                 mDatabaseFilePath,
+                                                 mUsage);
+      MOZ_ASSERT(quotaObject);
+    }
+
     mDatastore = new Datastore(mOrigin,
                                mPrivateBrowsingId,
                                mUsage,
                                mDirectoryLock.forget(),
                                mConnection.forget(),
+                               quotaObject.forget(),
                                mValues);
 
     mDatastore->NoteLivePrepareDatastoreOp(this);
 
     if (!gDatastores) {
       gDatastores = new DatastoreHashtable();
     }
 
@@ -4536,17 +4637,19 @@ LoadDataOp::DoDatastoreWork()
 
     nsString value;
     rv = stmt->GetString(1, value);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     mPrepareDatastoreOp->mValues.Put(key, value);
-    mPrepareDatastoreOp->mUsage += key.Length() + value.Length();
+#ifdef DEBUG
+    mPrepareDatastoreOp->mDEBUGUsage += key.Length() + value.Length();
+#endif
   }
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
@@ -4907,38 +5010,17 @@ QuotaClient::RegisterObservers(nsIEventT
 nsresult
 QuotaClient::InitOrigin(PersistenceType aPersistenceType,
                         const nsACString& aGroup,
                         const nsACString& aOrigin,
                         const AtomicBool& aCanceled,
                         UsageInfo* aUsageInfo)
 {
   AssertIsOnIOThread();
-
-  if (!aUsageInfo) {
-    return NS_OK;
-  }
-
-  return GetUsageForOrigin(aPersistenceType,
-                           aGroup,
-                           aOrigin,
-                           aCanceled,
-                           aUsageInfo);
-}
-
-nsresult
-QuotaClient::GetUsageForOrigin(PersistenceType aPersistenceType,
-                               const nsACString& aGroup,
-                               const nsACString& aOrigin,
-                               const AtomicBool& aCanceled,
-                               UsageInfo* aUsageInfo)
-{
-  AssertIsOnIOThread();
   MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_DEFAULT);
-  MOZ_ASSERT(aUsageInfo);
 
   QuotaManager* quotaManager = QuotaManager::Get();
   MOZ_ASSERT(quotaManager);
 
   nsCOMPtr<nsIFile> directory;
   nsresult rv = quotaManager->GetDirectoryForOrigin(aPersistenceType,
                                                     aOrigin,
                                                     getter_AddRefs(directory));
@@ -4986,17 +5068,52 @@ QuotaClient::GetUsageForOrigin(Persisten
       return rv;
     }
 
     if (NS_WARN_IF(isDirectory)) {
       return NS_ERROR_FAILURE;
     }
 
     // TODO: Use a special file that contains logical size of the database.
-    //       For now, don't add to origin usage.
+    //       For now, get the usage from the database.
+
+    nsCOMPtr<mozIStorageConnection> connection;
+    rv = CreateStorageConnection(file, aOrigin, getter_AddRefs(connection));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    nsCOMPtr<mozIStorageStatement> stmt;
+    rv = connection->CreateStatement(NS_LITERAL_CSTRING(
+      "SELECT sum(length(key) + length(value)) "
+      "FROM data"
+    ), getter_AddRefs(stmt));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    bool hasResult;
+    rv = stmt->ExecuteStep(&hasResult);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (NS_WARN_IF(!hasResult)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    int64_t usage;
+    rv = stmt->GetInt64(0, &usage);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    InitUsageForOrigin(aOrigin, usage);
+
+    aUsageInfo->AppendToDatabaseUsage(uint64_t(usage));
   }
 
   // Report unknown files, don't fail, just warn.
 
   nsCOMPtr<nsIDirectoryEnumerator> directoryEntries;
   rv = directory->GetDirectoryEntries(getter_AddRefs(directoryEntries));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -5046,27 +5163,61 @@ QuotaClient::GetUsageForOrigin(Persisten
 
     LS_WARNING("Something (%s) in the directory that doesn't belong!", \
                NS_ConvertUTF16toUTF8(leafName).get());
   }
 
   return NS_OK;
 }
 
+nsresult
+QuotaClient::GetUsageForOrigin(PersistenceType aPersistenceType,
+                               const nsACString& aGroup,
+                               const nsACString& aOrigin,
+                               const AtomicBool& aCanceled,
+                               UsageInfo* aUsageInfo)
+{
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_DEFAULT);
+  MOZ_ASSERT(aUsageInfo);
+
+  // We can't open the database at this point, since it can be already used
+  // by the connection thread. Use the cached value instead.
+
+  if (gUsages) {
+    int64_t usage;
+    if (gUsages->Get(aOrigin, &usage)) {
+      aUsageInfo->AppendToDatabaseUsage(usage);
+    }
+  }
+
+  return NS_OK;
+}
+
 void
 QuotaClient::OnOriginClearCompleted(PersistenceType aPersistenceType,
                                     const nsACString& aOrigin)
 {
   AssertIsOnIOThread();
+
+  if (aPersistenceType != PERSISTENCE_TYPE_DEFAULT) {
+    return;
+  }
+
+  if (gUsages) {
+    gUsages->Remove(aOrigin);
+  }
 }
 
 void
 QuotaClient::ReleaseIOThreadObjects()
 {
   AssertIsOnIOThread();
+
+  gUsages = nullptr;
 }
 
 void
 QuotaClient::AbortOperations(const nsACString& aOrigin)
 {
   AssertIsOnBackgroundThread();
 
   // A PrepareDatastoreOp object could already acquire a directory lock for
--- a/dom/localstorage/moz.build
+++ b/dom/localstorage/moz.build
@@ -1,14 +1,18 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
+XPCSHELL_TESTS_MANIFESTS += [
+    'test/unit/xpcshell.ini'
+]
+
 XPIDL_SOURCES += [
     'nsILocalStorageManager.idl',
 ]
 
 XPIDL_MODULE = 'dom_localstorage'
 
 EXPORTS.mozilla.dom.localstorage += [
     'ActorsParent.h',
new file mode 100644
--- /dev/null
+++ b/dom/localstorage/test/unit/head.js
@@ -0,0 +1,144 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const NS_ERROR_DOM_QUOTA_EXCEEDED_ERR = 22;
+
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+function is(a, b, msg)
+{
+  Assert.equal(a, b, msg);
+}
+
+function ok(cond, msg)
+{
+  Assert.ok(!!cond, msg);
+}
+
+function run_test()
+{
+  runTest();
+};
+
+if (!this.runTest) {
+  this.runTest = function()
+  {
+    do_get_profile();
+
+    enableTesting();
+
+    do_test_pending();
+    testGenerator.next();
+  }
+}
+
+function finishTest()
+{
+  resetTesting();
+
+  executeSoon(function() {
+    do_test_finished();
+  })
+}
+
+function continueToNextStepSync()
+{
+  testGenerator.next();
+}
+
+function enableTesting()
+{
+  Services.prefs.setBoolPref("dom.storage.testing", true);
+  Services.prefs.setBoolPref("dom.quotaManager.testing", true);
+}
+
+function resetTesting()
+{
+  Services.prefs.clearUserPref("dom.quotaManager.testing");
+  Services.prefs.clearUserPref("dom.storage.testing");
+}
+
+function setGlobalLimit(globalLimit)
+{
+  Services.prefs.setIntPref("dom.quotaManager.temporaryStorage.fixedLimit",
+                            globalLimit);
+}
+
+function resetGlobalLimit()
+{
+  Services.prefs.clearUserPref("dom.quotaManager.temporaryStorage.fixedLimit");
+}
+
+function setOriginLimit(originLimit)
+{
+  Services.prefs.setIntPref("dom.storage.default_quota", originLimit);
+}
+
+function resetOriginLimit()
+{
+  Services.prefs.clearUserPref("dom.storage.default_quota");
+}
+
+function getOriginUsage(principal, callback)
+{
+  let request = Services.qms.getUsageForPrincipal(principal, callback);
+  request.callback = callback;
+
+  return request;
+}
+
+function clear(callback)
+{
+  let request = Services.qms.clear();
+  request.callback = callback;
+
+  return request;
+}
+
+function resetOrigin(principal, callback)
+{
+  let request =
+    Services.qms.resetStoragesForPrincipal(principal, "default", "ls");
+  request.callback = callback;
+
+  return request;
+}
+
+function repeatChar(count, ch) {
+  if (count == 0) {
+    return "";
+  }
+
+  let result = ch;
+  let count2 = count / 2;
+
+  // Double the input until it is long enough.
+  while (result.length <= count2) {
+    result += result;
+  }
+
+  // Use substring to hit the precise length target without using extra memory.
+  return result + result.substring(0, count - result.length);
+}
+
+function getPrincipal(url)
+{
+  let uri = Services.io.newURI(url);
+  return Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
+}
+
+function getCurrentPrincipal()
+{
+  return Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal);
+}
+
+function getLocalStorage(principal)
+{
+  if (!principal) {
+    principal = getCurrentPrincipal();
+  }
+
+  return Services.domStorageManager.createStorage(null, principal, "");
+}
new file mode 100644
--- /dev/null
+++ b/dom/localstorage/test/unit/test_eviction.js
@@ -0,0 +1,91 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var testGenerator = testSteps();
+
+function* testSteps()
+{
+  const globalLimitKB = 5 * 1024;
+
+  const data = {};
+  data.sizeKB = 1 * 1024;
+  data.key = "A";
+  data.value = repeatChar(data.sizeKB * 1024 - data.key.length, ".");
+  data.urlCount = globalLimitKB / data.sizeKB;
+
+  function getSpec(index) {
+    return "http://example" + index + ".com";
+  }
+
+  info("Setting pref");
+
+  Services.prefs.setBoolPref("dom.storage.next_gen", true);
+
+  info("Setting limits");
+
+  setGlobalLimit(globalLimitKB);
+
+  clear(continueToNextStepSync);
+  yield undefined;
+
+  info("Getting storages");
+
+  let storages = [];
+  for (let i = 0; i < data.urlCount; i++) {
+    let storage = getLocalStorage(getPrincipal(getSpec(i)));
+    storages.push(storage);
+  }
+
+  info("Filling up entire default storage");
+
+  for (let i = 0; i < data.urlCount; i++) {
+    storages[i].setItem(data.key, data.value);
+  }
+
+  info("Verifying no more data can be written");
+
+  for (let i = 0; i < data.urlCount; i++) {
+    try {
+      storages[i].setItem("B", "");
+      ok(false, "Should have thrown");
+    } catch(ex) {
+      ok(true, "Did throw");
+      ok(ex instanceof DOMException, "Threw DOMException");
+      is(ex.name, "QuotaExceededError", "Threw right DOMException");
+      is(ex.code, NS_ERROR_DOM_QUOTA_EXCEEDED_ERR, "Threw with right code");
+    }
+  }
+
+  info("Closing first origin");
+
+  storages[0].close();
+
+  let principal = getPrincipal("http://example0.com");
+
+  resetOrigin(principal, continueToNextStepSync);
+  yield undefined;
+
+  info("Getting usage for first origin");
+
+  let request = getOriginUsage(principal, continueToNextStepSync);
+  yield undefined;
+
+  is(request.result.usage, data.sizeKB * 1024, "Correct usage");
+
+  info("Verifying more data data can be written");
+
+  for (let i = 1; i < data.urlCount; i++) {
+    storages[i].setItem("B", "");
+  }
+
+  info("Getting usage for first origin");
+
+  request = getOriginUsage(principal, continueToNextStepSync);
+  yield undefined;
+
+  is(request.result.usage, 0, "Zero usage");
+
+  finishTest();
+}
new file mode 100644
--- /dev/null
+++ b/dom/localstorage/test/unit/test_groupLimit.js
@@ -0,0 +1,77 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var testGenerator = testSteps();
+
+function* testSteps()
+{
+  const groupLimitKB = 10 * 1024;
+
+  const globalLimitKB = groupLimitKB * 5;
+
+  const originLimit = 10 * 1024;
+
+  const urls = [
+    "http://example.com",
+    "http://test1.example.com",
+    "https://test2.example.com",
+    "http://test3.example.com:8080"
+  ];
+
+  const data = {};
+  data.sizeKB = 5 * 1024;
+  data.key = "A";
+  data.value = repeatChar(data.sizeKB * 1024 - data.key.length, ".");
+  data.urlCount = groupLimitKB / data.sizeKB;
+
+  info("Setting limits");
+
+  setGlobalLimit(globalLimitKB);
+
+  clear(continueToNextStepSync);
+  yield undefined;
+
+  setOriginLimit(originLimit);
+
+  info("Getting storages");
+
+  let storages = [];
+  for (let i = 0; i < urls.length; i++) {
+    let storage = getLocalStorage(getPrincipal(urls[i]));
+    storages.push(storage);
+  }
+
+  info("Filling up the whole group");
+
+  for (let i = 0; i < data.urlCount; i++) {
+    storages[i].setItem(data.key, data.value);
+  }
+
+  info("Verifying no more data can be written");
+
+  for (let i = 0; i < urls.length; i++) {
+    try {
+      storages[i].setItem("B", "");
+      ok(false, "Should have thrown");
+    } catch(ex) {
+      ok(true, "Did throw");
+      ok(ex instanceof DOMException, "Threw DOMException");
+      is(ex.name, "QuotaExceededError", "Threw right DOMException");
+      is(ex.code, NS_ERROR_DOM_QUOTA_EXCEEDED_ERR, "Threw with right code");
+    }
+  }
+
+  info("Clearing first origin");
+
+  storages[0].clear();
+
+  info("Verifying more data can be written");
+
+  for (let i = 0; i < urls.length; i++) {
+    storages[i].setItem("B", "");
+  }
+
+  finishTest();
+}
new file mode 100644
--- /dev/null
+++ b/dom/localstorage/test/unit/xpcshell.ini
@@ -0,0 +1,9 @@
+# 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/.
+
+[DEFAULT]
+head = head.js
+
+[test_eviction.js]
+[test_groupLimit.js]
--- a/dom/quota/ActorsParent.cpp
+++ b/dom/quota/ActorsParent.cpp
@@ -3111,19 +3111,26 @@ QuotaObject::LockedMaybeUpdateSize(int64
   AssertNoOverflow(quotaManager->mTemporaryStorageUsage, delta);
   uint64_t newTemporaryStorageUsage = quotaManager->mTemporaryStorageUsage +
                                       delta;
 
   if (newTemporaryStorageUsage > quotaManager->mTemporaryStorageLimit) {
     // This will block the thread without holding the lock while waitting.
 
     AutoTArray<RefPtr<DirectoryLockImpl>, 10> locks;
-
-    uint64_t sizeToBeFreed =
-      quotaManager->LockedCollectOriginsForEviction(delta, locks);
+    uint64_t sizeToBeFreed;
+
+    if (IsOnBackgroundThread()) {
+      MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex);
+
+      sizeToBeFreed = quotaManager->CollectOriginsForEviction(delta, locks);
+    } else {
+      sizeToBeFreed = quotaManager->LockedCollectOriginsForEviction(delta,
+                                                                    locks);
+    }
 
     if (!sizeToBeFreed) {
       uint64_t usage = quotaManager->mTemporaryStorageUsage;
 
       MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex);
 
       quotaManager->NotifyStoragePressure(usage);
 
@@ -3871,16 +3878,17 @@ QuotaManager::RemoveQuota()
   NS_ASSERTION(mTemporaryStorageUsage == 0, "Should be zero!");
 }
 
 already_AddRefed<QuotaObject>
 QuotaManager::GetQuotaObject(PersistenceType aPersistenceType,
                              const nsACString& aGroup,
                              const nsACString& aOrigin,
                              nsIFile* aFile,
+                             int64_t aFileSize,
                              int64_t* aFileSizeOut /* = nullptr */)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
   if (aFileSizeOut) {
     *aFileSizeOut = 0;
   }
 
@@ -3889,26 +3897,30 @@ QuotaManager::GetQuotaObject(Persistence
   }
 
   nsString path;
   nsresult rv = aFile->GetPath(path);
   NS_ENSURE_SUCCESS(rv, nullptr);
 
   int64_t fileSize;
 
-  bool exists;
-  rv = aFile->Exists(&exists);
-  NS_ENSURE_SUCCESS(rv, nullptr);
-
-  if (exists) {
-    rv = aFile->GetFileSize(&fileSize);
+  if (aFileSize == -1) {
+    bool exists;
+    rv = aFile->Exists(&exists);
     NS_ENSURE_SUCCESS(rv, nullptr);
-  }
-  else {
-    fileSize = 0;
+
+    if (exists) {
+      rv = aFile->GetFileSize(&fileSize);
+      NS_ENSURE_SUCCESS(rv, nullptr);
+    }
+    else {
+      fileSize = 0;
+    }
+  } else {
+    fileSize = aFileSize;
   }
 
   // Re-escape our parameters above to make sure we get the right quota group.
   nsAutoCString group;
   rv = NS_EscapeURL(aGroup, esc_Query, group, fallible);
   NS_ENSURE_SUCCESS(rv, nullptr);
 
   nsAutoCString origin;
@@ -3964,29 +3976,35 @@ QuotaManager::GetQuotaObject(Persistence
   return result.forget();
 }
 
 already_AddRefed<QuotaObject>
 QuotaManager::GetQuotaObject(PersistenceType aPersistenceType,
                              const nsACString& aGroup,
                              const nsACString& aOrigin,
                              const nsAString& aPath,
+                             int64_t aFileSize,
                              int64_t* aFileSizeOut /* = nullptr */)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
   if (aFileSizeOut) {
     *aFileSizeOut = 0;
   }
 
   nsCOMPtr<nsIFile> file;
   nsresult rv = NS_NewLocalFile(aPath, false, getter_AddRefs(file));
   NS_ENSURE_SUCCESS(rv, nullptr);
 
-  return GetQuotaObject(aPersistenceType, aGroup, aOrigin, file, aFileSizeOut);
+  return GetQuotaObject(aPersistenceType,
+                        aGroup,
+                        aOrigin,
+                        file,
+                        aFileSize,
+                        aFileSizeOut);
 }
 
 Nullable<bool>
 QuotaManager::OriginPersisted(const nsACString& aGroup,
                               const nsACString& aOrigin)
 {
   AssertIsOnIOThread();
 
--- a/dom/quota/QuotaManager.h
+++ b/dom/quota/QuotaManager.h
@@ -174,23 +174,25 @@ public:
     LockedRemoveQuotaForOrigin(aPersistenceType, aGroup, aOrigin);
   }
 
   already_AddRefed<QuotaObject>
   GetQuotaObject(PersistenceType aPersistenceType,
                  const nsACString& aGroup,
                  const nsACString& aOrigin,
                  nsIFile* aFile,
+                 int64_t aFileSize = -1,
                  int64_t* aFileSizeOut = nullptr);
 
   already_AddRefed<QuotaObject>
   GetQuotaObject(PersistenceType aPersistenceType,
                  const nsACString& aGroup,
                  const nsACString& aOrigin,
                  const nsAString& aPath,
+                 int64_t aFileSize = -1,
                  int64_t* aFileSizeOut = nullptr);
 
   Nullable<bool>
   OriginPersisted(const nsACString& aGroup,
                   const nsACString& aOrigin);
 
   void
   PersistOrigin(const nsACString& aGroup,