Bug 1089764 - Treat persistent storage as temporary storage; r=bent, a=lsblakk
authorJan Varga <jan.varga@gmail.com>
Tue, 04 Nov 2014 21:44:56 +0100
changeset 233954 b4cd2a209317c33c5e0851a26e5ba43f67290b79
parent 233953 c8ebeea28f1998ab512216aa7f853d50fe9cb104
child 233955 44cfd6a0019ac6459d1d9c0905bfd970551781a1
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbent, lsblakk
bugs1089764
milestone35.0a2
Bug 1089764 - Treat persistent storage as temporary storage; r=bent, a=lsblakk
dom/asmjscache/AsmJSCache.cpp
dom/base/nsDOMWindowUtils.cpp
dom/indexedDB/ActorsParent.cpp
dom/indexedDB/IDBDatabase.cpp
dom/indexedDB/IDBFactory.cpp
dom/indexedDB/IDBMutableFile.cpp
dom/indexedDB/moz.build
dom/indexedDB/test/mochitest.ini
dom/indexedDB/test/unit/test_temporary_storage.js
dom/quota/OriginCollection.h
dom/quota/PersistenceType.h
dom/quota/QuotaManager.cpp
dom/quota/QuotaManager.h
dom/quota/QuotaObject.cpp
dom/quota/QuotaObject.h
--- a/dom/asmjscache/AsmJSCache.cpp
+++ b/dom/asmjscache/AsmJSCache.cpp
@@ -487,17 +487,20 @@ public:
   MainProcessRunnable(nsIPrincipal* aPrincipal,
                       OpenMode aOpenMode,
                       WriteParams aWriteParams)
   : mPrincipal(aPrincipal),
     mOpenMode(aOpenMode),
     mWriteParams(aWriteParams),
     mNeedAllowNextSynchronizedOp(false),
     mPersistence(quota::PERSISTENCE_TYPE_INVALID),
-    mState(eInitial)
+    mState(eInitial),
+    mIsApp(false),
+    mHasUnlimStoragePerm(false),
+    mEnforcingQuota(true)
   {
     MOZ_ASSERT(IsMainProcess());
   }
 
   virtual ~MainProcessRunnable()
   {
     MOZ_ASSERT(mState == eFinished);
     MOZ_ASSERT(!mNeedAllowNextSynchronizedOp);
@@ -588,16 +591,19 @@ protected:
   // Called by MainProcessRunnable on the main thread after a call to Close():
   virtual void
   OnClose()
   {
     FinishOnMainThread();
   }
 
 private:
+  void
+  InitPersistenceType();
+
   nsresult
   InitOnMainThread();
 
   nsresult
   ReadMetadata();
 
   nsresult
   OpenCacheFileForWrite();
@@ -654,101 +660,120 @@ private:
     eReadyToOpenCacheFileForRead, // Waiting to open cache file for read
     eSendingCacheFile, // Waiting to send OnOpenCacheFile on the main thread
     eOpened, // Finished calling OnOpen, waiting to be closed
     eClosing, // Waiting to be dispatched to main thread again
     eFailing, // Just failed, waiting to be dispatched to the main thread
     eFinished, // Terminal state
   };
   State mState;
+
+  bool mIsApp;
+  bool mHasUnlimStoragePerm;
+  bool mEnforcingQuota;
 };
 
+void
+MainProcessRunnable::InitPersistenceType()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == eInitial);
+
+  if (mOpenMode == eOpenForWrite) {
+    MOZ_ASSERT(mPersistence == quota::PERSISTENCE_TYPE_INVALID);
+
+    // If we are performing install-time caching of an app, we'd like to store
+    // the cache entry in persistent storage so the entry is never evicted,
+    // but we need to check that quota is not enforced for the app.
+    // That justifies us in skipping all quota checks when storing the cache
+    // entry and avoids all the issues around the persistent quota prompt.
+    // If quota is enforced for the app, then we can still cache in temporary
+    // for a likely good first-run experience.
+
+    MOZ_ASSERT_IF(mWriteParams.mInstalled, mIsApp);
+
+    if (mWriteParams.mInstalled &&
+        !QuotaManager::IsQuotaEnforced(quota::PERSISTENCE_TYPE_PERSISTENT,
+                                       mOrigin, mIsApp, mHasUnlimStoragePerm)) {
+      mPersistence = quota::PERSISTENCE_TYPE_PERSISTENT;
+    } else {
+      mPersistence = quota::PERSISTENCE_TYPE_TEMPORARY;
+    }
+
+    return;
+  }
+
+  // For the reasons described above, apps may have cache entries in both
+  // persistent and temporary storage. At lookup time we don't know how and
+  // where the given script was cached, so start the search in persistent
+  // storage and, if that fails, search in temporary storage. (Non-apps can
+  // only be stored in temporary storage.)
+
+  MOZ_ASSERT_IF(mPersistence != quota::PERSISTENCE_TYPE_INVALID,
+                mIsApp && mPersistence == quota::PERSISTENCE_TYPE_PERSISTENT);
+
+  if (mPersistence == quota::PERSISTENCE_TYPE_INVALID && mIsApp) {
+    mPersistence = quota::PERSISTENCE_TYPE_PERSISTENT;
+  } else {
+    mPersistence = quota::PERSISTENCE_TYPE_TEMPORARY;
+  }
+}
+
 nsresult
 MainProcessRunnable::InitOnMainThread()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mState == eInitial);
 
   QuotaManager* qm = QuotaManager::GetOrCreate();
   NS_ENSURE_STATE(qm);
 
-  nsresult rv = QuotaManager::GetInfoFromPrincipal(mPrincipal, &mGroup,
-                                                   &mOrigin, nullptr, nullptr);
+  nsresult rv =
+    QuotaManager::GetInfoFromPrincipal(mPrincipal,
+                                       quota::PERSISTENCE_TYPE_INVALID,
+                                       &mGroup, &mOrigin, nullptr, &mIsApp,
+                                       &mHasUnlimStoragePerm);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  bool isApp = mPrincipal->GetAppStatus() !=
-               nsIPrincipal::APP_STATUS_NOT_INSTALLED;
+  // XXX Don't use mGroup yet! We might need to update it right after we
+  //     initialize persistence type.
 
-  if (mOpenMode == eOpenForWrite) {
-    MOZ_ASSERT(mPersistence == quota::PERSISTENCE_TYPE_INVALID);
-    if (mWriteParams.mInstalled) {
-      // If we are performing install-time caching of an app, we'd like to store
-      // the cache entry in persistent storage so the entry is never evicted,
-      // but we need to verify that the app has unlimited storage permissions
-      // first. Unlimited storage permissions justify us in skipping all quota
-      // checks when storing the cache entry and avoids all the issues around
-      // the persistent quota prompt.
-      MOZ_ASSERT(isApp);
-
-      nsCOMPtr<nsIPermissionManager> pm =
-        services::GetPermissionManager();
-      NS_ENSURE_TRUE(pm, NS_ERROR_UNEXPECTED);
-
-      uint32_t permission;
-      rv = pm->TestPermissionFromPrincipal(mPrincipal,
-                                           PERMISSION_STORAGE_UNLIMITED,
-                                           &permission);
-      NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
+  InitPersistenceType();
 
-      // If app doens't have the unlimited storage permission, we can still
-      // cache in temporary for a likely good first-run experience.
-      mPersistence = permission == nsIPermissionManager::ALLOW_ACTION
-                     ? quota::PERSISTENCE_TYPE_PERSISTENT
-                     : quota::PERSISTENCE_TYPE_TEMPORARY;
-    } else {
-      mPersistence = quota::PERSISTENCE_TYPE_TEMPORARY;
-    }
-  } else {
-    // For the reasons described above, apps may have cache entries in both
-    // persistent and temporary storage. At lookup time we don't know how and
-    // where the given script was cached, so start the search in persistent
-    // storage and, if that fails, search in temporary storage. (Non-apps can
-    // only be stored in temporary storage.)
-    if (mPersistence == quota::PERSISTENCE_TYPE_INVALID) {
-      mPersistence = isApp ? quota::PERSISTENCE_TYPE_PERSISTENT
-                           : quota::PERSISTENCE_TYPE_TEMPORARY;
-    } else {
-      MOZ_ASSERT(isApp);
-      MOZ_ASSERT(mPersistence == quota::PERSISTENCE_TYPE_PERSISTENT);
-      mPersistence = quota::PERSISTENCE_TYPE_TEMPORARY;
-    }
+  // XXX Since we couldn't pass persistence type to GetInfoFromPrincipal(),
+  //     we need to do this manually.
+  //     This hack is only temporary, it will go away once we have regular
+  //     metadata files for persistent storge.
+  if (mPersistence == quota::PERSISTENCE_TYPE_PERSISTENT) {
+    mGroup = mOrigin;
   }
 
+  mEnforcingQuota =
+    QuotaManager::IsQuotaEnforced(mPersistence, mOrigin, mIsApp,
+                                  mHasUnlimStoragePerm);
+
   QuotaManager::GetStorageId(mPersistence, mOrigin, quota::Client::ASMJS,
                              NS_LITERAL_STRING("asmjs"), mStorageId);
 
   return NS_OK;
 }
 
 nsresult
 MainProcessRunnable::ReadMetadata()
 {
   AssertIsOnIOThread();
   MOZ_ASSERT(mState == eReadyToReadMetadata);
 
   QuotaManager* qm = QuotaManager::Get();
   MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
 
-  // Only track quota for temporary storage. For persistent storage, we've
-  // already checked that we have unlimited-storage permissions.
-  bool trackQuota = mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY;
-
-  nsresult rv = qm->EnsureOriginIsInitialized(mPersistence, mGroup, mOrigin,
-                                              trackQuota,
-                                              getter_AddRefs(mDirectory));
+  nsresult rv =
+    qm->EnsureOriginIsInitialized(mPersistence, mGroup, mOrigin, mIsApp,
+                                  mHasUnlimStoragePerm,
+                                  getter_AddRefs(mDirectory));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mDirectory->Append(NS_LITERAL_STRING(ASMJSCACHE_DIRECTORY_NAME));
   NS_ENSURE_SUCCESS(rv, rv);
 
   bool exists;
   rv = mDirectory->Exists(&exists);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -806,21 +831,17 @@ MainProcessRunnable::OpenCacheFileForWri
 
   nsCOMPtr<nsIFile> file;
   nsresult rv = GetCacheFile(mDirectory, mModuleIndex, getter_AddRefs(file));
   NS_ENSURE_SUCCESS(rv, rv);
 
   QuotaManager* qm = QuotaManager::Get();
   MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
 
-  // If we are allocating in temporary storage, ask the QuotaManager if we're
-  // within the quota. If we are allocating in persistent storage, we've already
-  // checked that we have the unlimited-storage permission, so there is nothing
-  // to check.
-  if (mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY) {
+  if (mEnforcingQuota) {
     // Create the QuotaObject before all file IO and keep it alive until caching
     // completes to get maximum assertion coverage in QuotaManager against
     // concurrent removal, etc.
     mQuotaObject = qm->GetQuotaObject(mPersistence, mGroup, mOrigin, file);
     NS_ENSURE_STATE(mQuotaObject);
 
     if (!mQuotaObject->MaybeAllocateMoreSpace(0, mWriteParams.mSize)) {
       // If the request fails, it might be because mOrigin is using too much
@@ -861,17 +882,17 @@ MainProcessRunnable::OpenCacheFileForRea
 
   nsCOMPtr<nsIFile> file;
   nsresult rv = GetCacheFile(mDirectory, mModuleIndex, getter_AddRefs(file));
   NS_ENSURE_SUCCESS(rv, rv);
 
   QuotaManager* qm = QuotaManager::Get();
   MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
 
-  if (mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY) {
+  if (mEnforcingQuota) {
     // Even though it's not strictly necessary, create the QuotaObject before
     // all file IO and keep it alive until caching completes to get maximum
     // assertion coverage in QuotaManager against concurrent removal, etc.
     mQuotaObject = qm->GetQuotaObject(mPersistence, mGroup, mOrigin, file);
     NS_ENSURE_STATE(mQuotaObject);
   }
 
   rv = file->GetFileSize(&mFileSize);
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -3088,31 +3088,30 @@ nsDOMWindowUtils::GetFileReferences(cons
                                     int32_t* aSliceRefCnt, JSContext* aCx,
                                     bool* aResult)
 {
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
 
   nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
   NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
 
-  nsCString origin;
-  quota::PersistenceType defaultPersistenceType;
-  nsresult rv =
-    quota::QuotaManager::GetInfoFromWindow(window, nullptr, &origin, nullptr,
-                                           &defaultPersistenceType);
-  NS_ENSURE_SUCCESS(rv, rv);
-
   IDBOpenDBOptions options;
   JS::Rooted<JS::Value> optionsVal(aCx, aOptions);
   if (!options.Init(aCx, optionsVal)) {
     return NS_ERROR_TYPE_ERR;
   }
 
   quota::PersistenceType persistenceType =
-    quota::PersistenceTypeFromStorage(options.mStorage, defaultPersistenceType);
+    quota::PersistenceTypeFromStorage(options.mStorage);
+
+  nsCString origin;
+  nsresult rv =
+    quota::QuotaManager::GetInfoFromWindow(window, persistenceType, nullptr,
+                                           &origin, nullptr, nullptr, nullptr);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   nsRefPtr<indexedDB::IndexedDatabaseManager> mgr =
     indexedDB::IndexedDatabaseManager::Get();
 
   if (mgr) {
     rv = mgr->BlockAndGetFileReferences(persistenceType, origin, aDatabaseName,
                                         aId, aRefCnt, aDBRefCnt, aSliceRefCnt,
                                         aResult);
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -1,16 +1,15 @@
 /* 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 <algorithm>
-#include "CheckQuotaHelper.h"
 #include "FileInfo.h"
 #include "FileManager.h"
 #include "IDBObjectStore.h"
 #include "IDBTransaction.h"
 #include "IndexedDatabase.h"
 #include "IndexedDatabaseInlines.h"
 #include "IndexedDatabaseManager.h"
 #include "js/StructuredClone.h"
@@ -3880,16 +3879,18 @@ protected:
   nsTArray<MaybeBlockedDatabaseInfo> mMaybeBlockedDatabases;
 
   const CommonFactoryRequestParams mCommonParams;
   nsCString mGroup;
   nsCString mOrigin;
   nsCString mDatabaseId;
   State mState;
   StoragePrivilege mStoragePrivilege;
+  bool mIsApp;
+  bool mHasUnlimStoragePerm;
   bool mEnforcingQuota;
   const bool mDeleting;
   bool mBlockedQuotaManager;
   bool mChromeWriteAccessAllowed;
 
 public:
   void
   NoteDatabaseBlocked(Database* aDatabase);
@@ -5922,16 +5923,22 @@ Factory::AllocPBackgroundIDBFactoryReque
   }
 
   const PrincipalInfo& principalInfo = commonParams->principalInfo();
   if (NS_WARN_IF(principalInfo.type() == PrincipalInfo::TNullPrincipalInfo)) {
     ASSERT_UNLESS_FUZZING();
     return nullptr;
   }
 
+  if (NS_WARN_IF(principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo &&
+                 metadata.persistenceType() != PERSISTENCE_TYPE_PERSISTENT)) {
+    ASSERT_UNLESS_FUZZING();
+    return nullptr;
+  }
+
   nsRefPtr<ContentParent> contentParent =
     BackgroundParent::GetContentParent(Manager());
 
   nsRefPtr<FactoryOp> actor;
   if (aParams.type() == FactoryRequestParams::TOpenDatabaseRequestParams) {
     actor = new OpenDatabaseOp(this,
                                contentParent.forget(),
                                mOptionalWindowId,
@@ -10325,16 +10332,18 @@ FactoryOp::FactoryOp(Factory* aFactory,
                      already_AddRefed<ContentParent> aContentParent,
                      const CommonFactoryRequestParams& aCommonParams,
                      bool aDeleting)
   : mFactory(aFactory)
   , mContentParent(Move(aContentParent))
   , mCommonParams(aCommonParams)
   , mState(State_Initial)
   , mStoragePrivilege(mozilla::dom::quota::Content)
+  , mIsApp(false)
+  , mHasUnlimStoragePerm(false)
   , mEnforcingQuota(true)
   , mDeleting(aDeleting)
   , mBlockedQuotaManager(false)
   , mChromeWriteAccessAllowed(false)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aFactory);
   MOZ_ASSERT(!QuotaClient::IsShuttingDownOnNonMainThread());
@@ -10570,21 +10579,24 @@ FactoryOp::CheckPermission(ContentParent
     return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
   }
 
   if (NS_WARN_IF(mCommonParams.privateBrowsingMode())) {
     // XXX This is only temporary.
     return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
   }
 
+  PersistenceType persistenceType = mCommonParams.metadata().persistenceType();
+
   const PrincipalInfo& principalInfo = mCommonParams.principalInfo();
   MOZ_ASSERT(principalInfo.type() != PrincipalInfo::TNullPrincipalInfo);
 
   if (principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
     MOZ_ASSERT(mState == State_Initial);
+    MOZ_ASSERT(persistenceType == PERSISTENCE_TYPE_PERSISTENT);
 
     if (aContentParent) {
       // Check to make sure that the child process has access to the database it
       // is accessing.
       NS_NAMED_LITERAL_CSTRING(permissionStringBase,
                                kPermissionStringChromeBase);
       NS_ConvertUTF16toUTF8 databaseName(mCommonParams.metadata().name());
       NS_NAMED_LITERAL_CSTRING(readSuffix, kPermissionStringChromeReadSuffix);
@@ -10624,19 +10636,21 @@ FactoryOp::CheckPermission(ContentParent
 
       mChromeWriteAccessAllowed = canWrite;
     } else {
       mChromeWriteAccessAllowed = true;
     }
 
     if (State_Initial == mState) {
       QuotaManager::GetInfoForChrome(&mGroup, &mOrigin, &mStoragePrivilege,
-                                     nullptr);
+                                     &mIsApp, &mHasUnlimStoragePerm);
       MOZ_ASSERT(mStoragePrivilege == mozilla::dom::quota::Chrome);
-      mEnforcingQuota = false;
+      mEnforcingQuota =
+        QuotaManager::IsQuotaEnforced(persistenceType, mOrigin, mIsApp,
+                                      mHasUnlimStoragePerm);
     }
 
     *aPermission = PermissionRequestBase::kPermissionAllowed;
     return NS_OK;
   }
 
   MOZ_ASSERT(principalInfo.type() == PrincipalInfo::TContentPrincipalInfo);
 
@@ -10644,23 +10658,21 @@ FactoryOp::CheckPermission(ContentParent
   nsCOMPtr<nsIPrincipal> principal =
     PrincipalInfoToPrincipal(principalInfo, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   PermissionRequestBase::PermissionValue permission;
 
-  if (mCommonParams.metadata().persistenceType() ==
-        PERSISTENCE_TYPE_TEMPORARY) {
+  if (persistenceType == PERSISTENCE_TYPE_TEMPORARY) {
     // Temporary storage doesn't need to check the permission.
     permission = PermissionRequestBase::kPermissionAllowed;
   } else {
-    MOZ_ASSERT(mCommonParams.metadata().persistenceType() ==
-                 PERSISTENCE_TYPE_PERSISTENT);
+    MOZ_ASSERT(persistenceType == PERSISTENCE_TYPE_PERSISTENT);
 
 #ifdef MOZ_CHILD_PERMISSIONS
     if (aContentParent) {
       if (NS_WARN_IF(!AssertAppPrincipal(aContentParent, principal))) {
         IDB_REPORT_INTERNAL_ERR();
         return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
       }
 
@@ -10676,34 +10688,28 @@ FactoryOp::CheckPermission(ContentParent
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     }
   }
 
   if (permission != PermissionRequestBase::kPermissionDenied &&
       State_Initial == mState) {
-    rv = QuotaManager::GetInfoFromPrincipal(principal, &mGroup, &mOrigin,
-                                            &mStoragePrivilege, nullptr);
+    rv = QuotaManager::GetInfoFromPrincipal(principal, persistenceType, &mGroup,
+                                            &mOrigin, &mStoragePrivilege,
+                                            &mIsApp, &mHasUnlimStoragePerm);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     MOZ_ASSERT(mStoragePrivilege != mozilla::dom::quota::Chrome);
-  }
-
-  if (permission == PermissionRequestBase::kPermissionAllowed &&
-      mEnforcingQuota)
-  {
-    // If we're running from a window then we should check the quota permission
-    // as well.
-    uint32_t quotaPermission = CheckQuotaHelper::GetQuotaPermission(principal);
-    if (quotaPermission == nsIPermissionManager::ALLOW_ACTION) {
-      mEnforcingQuota = false;
-    }
+
+    mEnforcingQuota =
+      QuotaManager::IsQuotaEnforced(persistenceType, mOrigin, mIsApp,
+                                    mHasUnlimStoragePerm);
   }
 
   *aPermission = permission;
   return NS_OK;
 }
 
 nsresult
 FactoryOp::SendVersionChangeMessages(DatabaseActorInfo* aDatabaseActorInfo,
@@ -11098,17 +11104,18 @@ OpenDatabaseOp::DoDatabaseWork()
   MOZ_ASSERT(quotaManager);
 
   nsCOMPtr<nsIFile> dbDirectory;
 
   nsresult rv =
     quotaManager->EnsureOriginIsInitialized(persistenceType,
                                             mGroup,
                                             mOrigin,
-                                            mEnforcingQuota,
+                                            mIsApp,
+                                            mHasUnlimStoragePerm,
                                             getter_AddRefs(dbDirectory));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = dbDirectory->Append(NS_LITERAL_STRING(IDB_DIRECTORY_NAME));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
--- a/dom/indexedDB/IDBDatabase.cpp
+++ b/dom/indexedDB/IDBDatabase.cpp
@@ -968,44 +968,49 @@ nsresult
 IDBDatabase::GetQuotaInfo(nsACString& aOrigin,
                           PersistenceType* aPersistenceType)
 {
   using mozilla::dom::quota::QuotaManager;
 
   MOZ_ASSERT(IndexedDatabaseManager::IsMainProcess());
   MOZ_ASSERT(NS_IsMainThread());
 
+  PersistenceType persistenceType = mSpec->metadata().persistenceType();
+
   if (aPersistenceType) {
-    *aPersistenceType = mSpec->metadata().persistenceType();
+    *aPersistenceType = persistenceType;
     MOZ_ASSERT(*aPersistenceType != PERSISTENCE_TYPE_INVALID);
   }
 
   PrincipalInfo* principalInfo = mFactory->GetPrincipalInfo();
   MOZ_ASSERT(principalInfo);
 
   switch (principalInfo->type()) {
     case PrincipalInfo::TNullPrincipalInfo:
       MOZ_CRASH("Is this needed?!");
 
     case PrincipalInfo::TSystemPrincipalInfo:
-      QuotaManager::GetInfoForChrome(nullptr, &aOrigin, nullptr, nullptr);
+      QuotaManager::GetInfoForChrome(nullptr, &aOrigin, nullptr, nullptr,
+                                     nullptr);
       return NS_OK;
 
     case PrincipalInfo::TContentPrincipalInfo: {
       nsresult rv;
       nsCOMPtr<nsIPrincipal> principal =
         PrincipalInfoToPrincipal(*principalInfo, &rv);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
       rv = QuotaManager::GetInfoFromPrincipal(principal,
+                                              persistenceType,
                                               nullptr,
                                               &aOrigin,
                                               nullptr,
+                                              nullptr,
                                               nullptr);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
       return NS_OK;
     }
 
--- a/dom/indexedDB/IDBFactory.cpp
+++ b/dom/indexedDB/IDBFactory.cpp
@@ -478,18 +478,17 @@ IDBFactory::OpenInternal(nsIPrincipal* a
   PersistenceType persistenceType;
   bool persistenceTypeIsExplicit;
 
   if (principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
     // Chrome privilege always gets persistent storage.
     persistenceType = PERSISTENCE_TYPE_PERSISTENT;
     persistenceTypeIsExplicit = false;
   } else {
-    persistenceType =
-      PersistenceTypeFromStorage(aStorageType, PERSISTENCE_TYPE_PERSISTENT);
+    persistenceType = PersistenceTypeFromStorage(aStorageType);
     persistenceTypeIsExplicit = aStorageType.WasPassed();
   }
 
   DatabaseMetadata& metadata = commonParams.metadata();
   metadata.name() = aName;
   metadata.persistenceType() = persistenceType;
   metadata.persistenceTypeIsExplicit() = persistenceTypeIsExplicit;
 
--- a/dom/indexedDB/IDBMutableFile.cpp
+++ b/dom/indexedDB/IDBMutableFile.cpp
@@ -152,31 +152,33 @@ IDBMutableFile::Create(IDBDatabase* aDat
   PrincipalInfo* principalInfo = aDatabase->Factory()->GetPrincipalInfo();
   MOZ_ASSERT(principalInfo);
 
   nsCOMPtr<nsIPrincipal> principal = PrincipalInfoToPrincipal(*principalInfo);
   if (NS_WARN_IF(!principal)) {
     return nullptr;
   }
 
+  const DatabaseSpec* spec = aDatabase->Spec();
+  MOZ_ASSERT(spec);
+
+  PersistenceType persistenceType = spec->metadata().persistenceType();
+
   nsCString group;
   nsCString origin;
   if (NS_WARN_IF(NS_FAILED(QuotaManager::GetInfoFromPrincipal(principal,
+                                                              persistenceType,
                                                               &group,
                                                               &origin,
                                                               nullptr,
+                                                              nullptr,
                                                               nullptr)))) {
     return nullptr;
   }
 
-  const DatabaseSpec* spec = aDatabase->Spec();
-  MOZ_ASSERT(spec);
-
-  PersistenceType persistenceType = spec->metadata().persistenceType();
-
   nsCString storageId;
   QuotaManager::GetStorageId(persistenceType,
                              origin,
                              Client::IDB,
                              aDatabase->Name(),
                              storageId);
 
   nsCOMPtr<nsIFile> file = GetFileFor(fileInfo);
--- a/dom/indexedDB/moz.build
+++ b/dom/indexedDB/moz.build
@@ -89,13 +89,12 @@ include('/ipc/chromium/chromium-config.m
 FINAL_LIBRARY = 'xul'
 
 FAIL_ON_WARNINGS = True
 
 LOCAL_INCLUDES += [
     '/content/base/src',
     '/db/sqlite3/src',
     '/dom/base',
-    '/dom/quota',
     '/dom/storage',
     '/ipc/glue',
     '/xpcom/build',
 ]
--- a/dom/indexedDB/test/mochitest.ini
+++ b/dom/indexedDB/test/mochitest.ini
@@ -140,17 +140,19 @@ skip-if = (buildapp == 'b2g' && toolkit 
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_cursors.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_deleteDatabase.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_deleteDatabase_interactions.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_disabled_quota_prompt.html]
-skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
+# Test temporarily disabled.
+skip-if = true
+# skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_error_events_abort_transactions.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_event_propagation.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_event_source.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_exceptions_in_events.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
--- a/dom/indexedDB/test/unit/test_temporary_storage.js
+++ b/dom/indexedDB/test/unit/test_temporary_storage.js
@@ -32,27 +32,30 @@ function testSteps()
     { url: "http://www.marie.org",        flags: [true, true, true, true] },
     { url: "http://www.john.org",         flags: [true, true, true, true] },
     { url: "http://www.ema.org",          flags: [true, true, true, true] },
     { url: "http://www.trigger.com",      flags: [false, true, true, true] }
   ];
   const lastIndex = urls.length - 1;
   const lastUrl = urls[lastIndex].url;
 
+  const openDBOptions = [
+    { version: 1, storage: "temporary" },
+    { version: 1 }
+  ];
+
   let quotaManager =
     Components.classes["@mozilla.org/dom/quota/manager;1"]
               .getService(Components.interfaces.nsIQuotaManager);
 
   let ioService = Components.classes["@mozilla.org/network/io-service;1"]
                             .getService(Components.interfaces.nsIIOService);
 
   let dbSize = 0;
 
-  let databases = [];
-
   function setLimit(limit) {
     if (limit) {
       SpecialPowers.setIntPref("dom.quotaManager.temporaryStorage.fixedLimit",
                                limit);
       return;
     }
     SpecialPowers.clearUserPref("dom.quotaManager.temporaryStorage.fixedLimit");
   }
@@ -107,127 +110,127 @@ function testSteps()
                                            { storage: "temporary" });
   request.onerror = errorHandler;
   request.onsuccess = grabEventAndContinueHandler;
   let event = yield undefined;
 
   getUsageForUrl(lastUrl, grabUsageAndContinueHandler);
   dbSize = yield undefined;
 
-  setLimit(lastIndex * dbSize / 1024);
-  quotaManager.clear();
+  for (let options of openDBOptions) {
+    setLimit(lastIndex * dbSize / 1024);
+    quotaManager.clear();
+
+    info("Stage 1");
+
+    let databases = [];
+    for (let i = 0; i < lastIndex; i++) {
+      let data = urls[i];
+
+      info("Opening database for " + data.url);
 
-  info("Stage 1");
+      request = indexedDB.openForPrincipal(getPrincipal(data.url), name,
+                                           options);
+      request.onerror = errorHandler;
+      request.onupgradeneeded = grabEventAndContinueHandler;
+      request.onsuccess = grabEventAndContinueHandler;
+      event = yield undefined;
+
+      is(event.type, "upgradeneeded", "Got correct event type");
+
+      let db = event.target.result;
+      db.createObjectStore("foo", { });
+
+      event = yield undefined;
+
+      is(event.type, "success", "Got correct event type");
 
-  for (let i = 0; i < lastIndex; i++) {
-    let data = urls[i];
+      databases.push(event.target.result);
+    }
+
+    request = indexedDB.openForPrincipal(getPrincipal(lastUrl), name, options);
+    request.addEventListener("error", new ExpectError("QuotaExceededError"));
+    request.onsuccess = unexpectedSuccessHandler;
+    event = yield undefined;
+
+    checkUsage(1);
+    yield undefined;
+
+    info("Stage 2");
+
+    for (let i = 1; i < urls.length; i++) {
+      databases[i] = null;
 
-    info("Opening database for " + data.url);
+      scheduleGC();
+      yield undefined;
 
-    request = indexedDB.openForPrincipal(getPrincipal(data.url), name,
-                                         { storage: "temporary" });
+      // The origin access time is set to the current system time when the first
+      // database for an origin is registered or the last one is unregistered.
+      // The registration happens when the database object is being created and
+      // the unregistration when it is unlinked/garbage collected.
+      // Some older windows systems have the system time limited to a maximum
+      // resolution of 10 or 15 milliseconds, so without a pause here we would
+      // end up with origins with the same access time which would cause random
+      // failures.
+      setTimeout(function() { testGenerator.next(); }, 20);
+      yield undefined;
+    }
+
+    request = indexedDB.openForPrincipal(getPrincipal(lastUrl), name, options);
     request.onerror = errorHandler;
     request.onupgradeneeded = grabEventAndContinueHandler;
     request.onsuccess = grabEventAndContinueHandler;
     event = yield undefined;
 
     is(event.type, "upgradeneeded", "Got correct event type");
 
     let db = event.target.result;
     db.createObjectStore("foo", { });
 
     event = yield undefined;
 
     is(event.type, "success", "Got correct event type");
 
-    databases.push(event.target.result);
-  }
-
-  request = indexedDB.openForPrincipal(getPrincipal(lastUrl), name,
-                                       { storage: "temporary" });
-  request.addEventListener("error", new ExpectError("QuotaExceededError"));
-  request.onsuccess = unexpectedSuccessHandler;
-  event = yield undefined;
-
-  checkUsage(1);
-  yield undefined;
-
-  info("Stage 2");
-
-  for (let i = 1; i < urls.length; i++) {
-    databases[i] = null;
-
-    scheduleGC();
+    checkUsage(2);
     yield undefined;
 
-    // The origin access time is set to the current system time when the first
-    // database for an origin is registered or the last one is unregistered.
-    // The registration happens when the database object is being created and
-    // the unregistration when it is unlinked/garbage collected.
-    // Some older windows systems have the system time limited to a maximum
-    // resolution of 10 or 15 milliseconds, so without a pause here we would
-    // end up with origins with the same access time which would cause random
-    // failures.
-    setTimeout(function() { testGenerator.next(); }, 20);
+    info("Stage 3");
+
+    setLimit(14 * dbSize / 1024);
+    quotaManager.reset();
+
+    request = indexedDB.openForPrincipal(getPrincipal(lastUrl), name, options);
+    request.onerror = errorHandler;
+    request.onsuccess = grabEventAndContinueHandler;
+    event = yield undefined;
+
+    is(event.type, "success", "Got correct event type");
+
+    db = event.target.result;
+
+    checkUsage(3);
+    yield undefined;
+
+    info("Stage 4");
+
+    let trans = db.transaction(["foo"], "readwrite");
+
+    let blob = new Blob(["bar"]);
+    request = trans.objectStore("foo").add(blob, 42);
+    request.onerror = errorHandler;
+    request.onsuccess = grabEventAndContinueHandler;
+    event = yield undefined;
+
+    trans.oncomplete = grabEventAndContinueHandler;
+    event = yield undefined;
+
+    checkUsage(4);
     yield undefined;
   }
 
-  request = indexedDB.openForPrincipal(getPrincipal(lastUrl), name,
-                                       { storage: "temporary" });
-  request.onerror = errorHandler;
-  request.onupgradeneeded = grabEventAndContinueHandler;
-  request.onsuccess = grabEventAndContinueHandler;
-  event = yield undefined;
-
-  is(event.type, "upgradeneeded", "Got correct event type");
-
-  let db = event.target.result;
-  db.createObjectStore("foo", { });
-
-  event = yield undefined;
-
-  is(event.type, "success", "Got correct event type");
-
-  checkUsage(2);
-  yield undefined;
-
-  info("Stage 3");
-
-  setLimit(14 * dbSize / 1024);
-  quotaManager.reset();
-
-  request = indexedDB.openForPrincipal(getPrincipal(lastUrl), name,
-                                       { storage: "temporary" });
-  request.onerror = errorHandler;
-  request.onsuccess = grabEventAndContinueHandler;
-  event = yield undefined;
-
-  is(event.type, "success", "Got correct event type");
-
-  db = event.target.result;
-
-  checkUsage(3);
-  yield undefined;
-
-  info("Stage 4");
-
-  let trans = db.transaction(["foo"], "readwrite");
-
-  let blob = new Blob(["bar"]);
-  request = trans.objectStore("foo").add(blob, 42);
-  request.onerror = errorHandler;
-  request.onsuccess = grabEventAndContinueHandler;
-  event = yield undefined;
-
-  trans.oncomplete = grabEventAndContinueHandler;
-  event = yield undefined;
-
-  checkUsage(4);
-  yield undefined;
-
   info("Cleanup");
 
   setLimit();
   quotaManager.reset();
 
   SpecialPowers.setBoolPref("dom.quotaManager.testing", testingEnabled);
 
   finishTest();
--- a/dom/quota/OriginCollection.h
+++ b/dom/quota/OriginCollection.h
@@ -24,18 +24,19 @@ public:
   {
     return mPatterns.Contains(aPattern);
   }
 
   void
   AddPattern(const nsACString& aPattern)
   {
     MOZ_ASSERT(!mOrigins.Count());
-    MOZ_ASSERT(!ContainsPattern(aPattern));
-    mPatterns.AppendElement(aPattern);
+    if (!ContainsPattern(aPattern)) {
+      mPatterns.AppendElement(aPattern);
+    }
   }
 
   bool
   ContainsOrigin(const nsACString& aOrigin)
   {
     for (uint32_t index = 0; index < mPatterns.Length(); index++) {
       if (PatternMatchesOrigin(mPatterns[index], aOrigin)) {
         return true;
@@ -43,18 +44,19 @@ public:
     }
 
     return mOrigins.GetEntry(aOrigin);
   }
 
   void
   AddOrigin(const nsACString& aOrigin)
   {
-    MOZ_ASSERT(!ContainsOrigin(aOrigin));
-    mOrigins.PutEntry(aOrigin);
+    if (!ContainsOrigin(aOrigin)) {
+      mOrigins.PutEntry(aOrigin);
+    }
   }
 
 private:
   nsTArray<nsCString> mPatterns;
   nsTHashtable<nsCStringHashKey> mOrigins;
 };
 
 END_QUOTA_NAMESPACE
--- a/dom/quota/PersistenceType.h
+++ b/dom/quota/PersistenceType.h
@@ -77,21 +77,31 @@ NullablePersistenceTypeFromText(const ns
 
 inline mozilla::dom::StorageType
 PersistenceTypeToStorage(PersistenceType aPersistenceType)
 {
   return mozilla::dom::StorageType(static_cast<int>(aPersistenceType));
 }
 
 inline PersistenceType
-PersistenceTypeFromStorage(const Optional<mozilla::dom::StorageType>& aStorage,
-                           PersistenceType aDefaultPersistenceType)
+PersistenceTypeFromStorage(const Optional<mozilla::dom::StorageType>& aStorage)
 {
   if (aStorage.WasPassed()) {
     return PersistenceType(static_cast<int>(aStorage.Value()));
   }
 
-  return aDefaultPersistenceType;
+  return PERSISTENCE_TYPE_PERSISTENT;
+}
+
+inline PersistenceType
+ComplementaryPersistenceType(PersistenceType aPersistenceType)
+{
+  if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
+    return PERSISTENCE_TYPE_TEMPORARY;
+  }
+
+  MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_TEMPORARY);
+  return PERSISTENCE_TYPE_PERSISTENT;
 }
 
 END_QUOTA_NAMESPACE
 
 #endif // mozilla_dom_quota_persistencetype_h__
--- a/dom/quota/QuotaManager.cpp
+++ b/dom/quota/QuotaManager.cpp
@@ -7,16 +7,17 @@
 #include "QuotaManager.h"
 
 #include "mozIApplicationClearPrivateDataParams.h"
 #include "nsIBinaryInputStream.h"
 #include "nsIBinaryOutputStream.h"
 #include "nsIFile.h"
 #include "nsIObserverService.h"
 #include "nsIOfflineStorage.h"
+#include "nsIPermissionManager.h"
 #include "nsIPrincipal.h"
 #include "nsIQuotaRequest.h"
 #include "nsIRunnable.h"
 #include "nsISimpleEnumerator.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsITimer.h"
 #include "nsIURI.h"
@@ -36,16 +37,17 @@
 #include "mozilla/Services.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsComponentManagerUtils.h"
 #include "nsAboutProtocolUtils.h"
 #include "nsContentUtils.h"
 #include "nsCRTGlue.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsNetUtil.h"
+#include "nsPrintfCString.h"
 #include "nsScriptSecurityManager.h"
 #include "nsThreadUtils.h"
 #include "nsXULAppAPI.h"
 #include "xpcpublic.h"
 
 #include "AcquireListener.h"
 #include "CheckQuotaHelper.h"
 #include "OriginCollection.h"
@@ -83,16 +85,17 @@
 
 #define PERMISSION_DEFAUT_PERSISTENT_STORAGE "default-persistent-storage"
 
 #define KB * 1024ULL
 #define MB * 1024ULL KB
 #define GB * 1024ULL MB
 
 USING_QUOTA_NAMESPACE
+using namespace mozilla;
 using namespace mozilla::dom;
 using mozilla::dom::FileService;
 
 static_assert(
   static_cast<uint32_t>(StorageType::Persistent) ==
   static_cast<uint32_t>(PERSISTENCE_TYPE_PERSISTENT),
   "Enum values should match.");
 
@@ -272,18 +275,20 @@ class AsyncUsageRunnable MOZ_FINAL : pub
   };
 
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIQUOTAREQUEST
 
   AsyncUsageRunnable(uint32_t aAppId,
                      bool aInMozBrowserOnly,
-                     const nsACString& aGroup,
+                     const nsACString& aPersistentGroup,
+                     const nsACString& aTemporaryGroup,
                      const OriginOrPatternString& aOrigin,
+                     bool aIsApp,
                      nsIURI* aURI,
                      nsIUsageCallback* aCallback);
 
   NS_IMETHOD
   Run();
 
   void
   AdvanceState()
@@ -316,20 +321,22 @@ private:
 
   nsresult
   AddToUsage(QuotaManager* aQuotaManager,
              PersistenceType aPersistenceType);
 
   nsCOMPtr<nsIURI> mURI;
   nsCOMPtr<nsIUsageCallback> mCallback;
   uint32_t mAppId;
-  nsCString mGroup;
+  nsCString mPersistentGroup;
+  nsCString mTemporaryGroup;
   OriginOrPatternString mOrigin;
   CallbackState mCallbackState;
   bool mInMozBrowserOnly;
+  const bool mIsApp;
 };
 
 class ResetOrClearRunnable MOZ_FINAL : public nsRunnable,
                                        public AcquireListener
 {
   enum CallbackState {
     // Not yet run.
     Pending = 0,
@@ -413,17 +420,17 @@ class FinalizeOriginEvictionRunnable MOZ
     // Running on the IO thread.
     IO,
 
     // Running on the main thread after IO work is done.
     Complete
   };
 
 public:
-  explicit FinalizeOriginEvictionRunnable(nsTArray<nsCString>& aOrigins)
+  explicit FinalizeOriginEvictionRunnable(nsTArray<OriginParams>& aOrigins)
   : mCallbackState(Pending)
   {
     mOrigins.SwapElements(aOrigins);
   }
 
   NS_IMETHOD
   Run();
 
@@ -448,17 +455,17 @@ public:
   nsresult
   Dispatch();
 
   nsresult
   RunImmediately();
 
 private:
   CallbackState mCallbackState;
-  nsTArray<nsCString> mOrigins;
+  nsTArray<OriginParams> mOrigins;
 };
 
 bool
 IsOnIOThread()
 {
   QuotaManager* quotaManager = QuotaManager::Get();
   NS_ASSERTION(quotaManager, "Must have a manager here!");
 
@@ -553,46 +560,53 @@ public:
 
 private:
   bool mBusy;
 };
 
 class SaveOriginAccessTimeRunnable MOZ_FINAL : public nsRunnable
 {
 public:
-  SaveOriginAccessTimeRunnable(const nsACString& aOrigin, int64_t aTimestamp)
-  : mOrigin(aOrigin), mTimestamp(aTimestamp)
+  SaveOriginAccessTimeRunnable(PersistenceType aPersistenceType,
+                               const nsACString& aOrigin,
+                               int64_t aTimestamp)
+  : mPersistenceType(aPersistenceType), mOrigin(aOrigin), mTimestamp(aTimestamp)
   { }
 
   NS_IMETHOD
   Run();
 
 private:
+  PersistenceType mPersistenceType;
   nsCString mOrigin;
   int64_t mTimestamp;
 };
 
 struct MOZ_STACK_CLASS RemoveQuotaInfo
 {
   RemoveQuotaInfo(PersistenceType aPersistenceType, const nsACString& aPattern)
   : persistenceType(aPersistenceType), pattern(aPattern)
   { }
 
   PersistenceType persistenceType;
   nsCString pattern;
 };
 
 struct MOZ_STACK_CLASS InactiveOriginsInfo
 {
-  InactiveOriginsInfo(OriginCollection& aCollection,
+  InactiveOriginsInfo(OriginCollection& aPersistentCollection,
+                      OriginCollection& aTemporaryCollection,
                       nsTArray<OriginInfo*>& aOrigins)
-  : collection(aCollection), origins(aOrigins)
+  : persistentCollection(aPersistentCollection),
+    temporaryCollection(aTemporaryCollection),
+    origins(aOrigins)
   { }
 
-  OriginCollection& collection;
+  OriginCollection& persistentCollection;
+  OriginCollection& temporaryCollection;
   nsTArray<OriginInfo*>& origins;
 };
 
 bool
 IsMainProcess()
 {
   return XRE_GetProcessType() == GeckoProcessType_Default;
 }
@@ -610,16 +624,39 @@ SanitizeOriginString(nsCString& aOrigin)
   NS_ASSERTION(!strcmp(kReplaceChars,
                        FILE_ILLEGAL_CHARACTERS FILE_PATH_SEPARATOR),
                "Illegal file characters have changed!");
 #endif
 
   aOrigin.ReplaceChar(kReplaceChars, '+');
 }
 
+bool
+IsApp(const nsCString& aOrigin)
+{
+  MOZ_ASSERT(!aOrigin.IsEmpty());
+
+  if (!NS_IsAsciiDigit(aOrigin.First())) {
+    return false;
+  }
+
+  int32_t pos = aOrigin.Find("+f+");
+  if (pos != kNotFound) {
+    nsCString appIdStr(Substring(aOrigin, 0, pos));
+
+    nsresult rv;
+    int32_t appId = appIdStr.ToInteger(&rv);
+    if (NS_SUCCEEDED(rv) && appId != 0) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
 nsresult
 EnsureDirectory(nsIFile* aDirectory, bool* aCreated)
 {
   AssertIsOnIOThread();
 
   nsresult rv = aDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
   if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
     bool isDirectory;
@@ -634,36 +671,18 @@ EnsureDirectory(nsIFile* aDirectory, boo
 
     *aCreated = true;
   }
 
   return NS_OK;
 }
 
 nsresult
-CreateDirectoryUpgradeStamp(nsIFile* aDirectory)
-{
-  AssertIsOnIOThread();
-
-  nsCOMPtr<nsIFile> metadataFile;
-  nsresult rv = aDirectory->Clone(getter_AddRefs(metadataFile));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = metadataFile->Append(NS_LITERAL_STRING(METADATA_FILE_NAME));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = metadataFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return NS_OK;
-}
-
-nsresult
-GetDirectoryMetadataStream(nsIFile* aDirectory, bool aUpdate,
-                           nsIBinaryOutputStream** aStream)
+GetDirectoryMetadataOutputStream(nsIFile* aDirectory, bool aUpdate,
+                                 nsIBinaryOutputStream** aStream)
 {
   AssertIsOnIOThread();
 
   nsCOMPtr<nsIFile> metadataFile;
   nsresult rv = aDirectory->Clone(getter_AddRefs(metadataFile));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = metadataFile->Append(NS_LITERAL_STRING(METADATA_FILE_NAME));
@@ -707,17 +726,17 @@ GetDirectoryMetadataStream(nsIFile* aDir
 nsresult
 CreateDirectoryMetadata(nsIFile* aDirectory, int64_t aTimestamp,
                         const nsACString& aGroup, const nsACString& aOrigin)
 {
   AssertIsOnIOThread();
 
   nsCOMPtr<nsIBinaryOutputStream> stream;
   nsresult rv =
-    GetDirectoryMetadataStream(aDirectory, false, getter_AddRefs(stream));
+    GetDirectoryMetadataOutputStream(aDirectory, false, getter_AddRefs(stream));
   NS_ENSURE_SUCCESS(rv, rv);
 
   NS_ASSERTION(stream, "This shouldn't be null!");
 
   rv = stream->Write64(aTimestamp);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = stream->WriteStringZ(PromiseFlatCString(aGroup).get());
@@ -725,43 +744,94 @@ CreateDirectoryMetadata(nsIFile* aDirect
 
   rv = stream->WriteStringZ(PromiseFlatCString(aOrigin).get());
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
-GetDirectoryMetadata(nsIFile* aDirectory, int64_t* aTimestamp,
-                     nsACString& aGroup, nsACString& aOrigin)
+CreateDirectoryMetadataLastModifiedTime(nsIFile* aDirectory, int64_t aTimestamp)
 {
   AssertIsOnIOThread();
 
+  nsCOMPtr<nsIBinaryOutputStream> stream;
+  nsresult rv =
+    GetDirectoryMetadataOutputStream(aDirectory, false, getter_AddRefs(stream));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  MOZ_ASSERT(stream);
+
+  rv = stream->Write64(aTimestamp);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+GetDirectoryMetadataInputStream(nsIFile* aDirectory,
+                                bool aClone,
+                                nsIBinaryInputStream** aStream)
+{
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aDirectory);
+  MOZ_ASSERT(aStream);
+
+  nsresult rv;
+
   nsCOMPtr<nsIFile> metadataFile;
-  nsresult rv = aDirectory->Clone(getter_AddRefs(metadataFile));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = metadataFile->Append(NS_LITERAL_STRING(METADATA_FILE_NAME));
-  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (aClone) {
+    rv = aDirectory->Clone(getter_AddRefs(metadataFile));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = metadataFile->Append(NS_LITERAL_STRING(METADATA_FILE_NAME));
+    NS_ENSURE_SUCCESS(rv, rv);
+  } else {
+    metadataFile = aDirectory;
+  }
 
   nsCOMPtr<nsIInputStream> stream;
   rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), metadataFile);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIInputStream> bufferedStream;
   rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), stream, 512);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIBinaryInputStream> binaryStream =
     do_CreateInstance("@mozilla.org/binaryinputstream;1");
   NS_ENSURE_TRUE(binaryStream, NS_ERROR_FAILURE);
 
   rv = binaryStream->SetInputStream(bufferedStream);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  binaryStream.forget(aStream);
+  return NS_OK;
+}
+
+nsresult
+GetDirectoryMetadata(nsIFile* aDirectory,
+                     int64_t* aTimestamp,
+                     nsACString& aGroup,
+                     nsACString& aOrigin)
+{
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aDirectory);
+  MOZ_ASSERT(aTimestamp);
+
+  nsCOMPtr<nsIBinaryInputStream> binaryStream;
+  nsresult rv = GetDirectoryMetadataInputStream(aDirectory, true,
+                                                getter_AddRefs(binaryStream));
+  NS_ENSURE_SUCCESS(rv, rv);
+
   uint64_t timestamp;
   rv = binaryStream->Read64(&timestamp);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCString group;
   rv = binaryStream->ReadCString(group);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -771,16 +841,113 @@ GetDirectoryMetadata(nsIFile* aDirectory
 
   *aTimestamp = timestamp;
   aGroup = group;
   aOrigin = origin;
   return NS_OK;
 }
 
 nsresult
+GetDirectoryMetadataLastModifiedTime(nsIFile* aDirectory, int64_t* aTimestamp)
+{
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aDirectory);
+  MOZ_ASSERT(aTimestamp);
+
+  nsresult rv;
+
+#ifdef DEBUG
+  nsCOMPtr<nsIFile> parentDirectory;
+  rv = aDirectory->GetParent(getter_AddRefs(parentDirectory));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsString path;
+  rv = parentDirectory->GetPath(path);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  QuotaManager* quotaManager = QuotaManager::Get();
+  MOZ_ASSERT(quotaManager);
+
+  MOZ_ASSERT(path == quotaManager->GetStoragePath(PERSISTENCE_TYPE_PERSISTENT));
+#endif
+
+  nsCOMPtr<nsIFile> metadataFile;
+  rv = aDirectory->Clone(getter_AddRefs(metadataFile));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = metadataFile->Append(NS_LITERAL_STRING(METADATA_FILE_NAME));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  bool exists;
+  rv = metadataFile->Exists(&exists);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  if (NS_WARN_IF(!exists)) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  bool isDirectory;
+  rv = metadataFile->IsDirectory(&isDirectory);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  if (NS_WARN_IF(isDirectory)) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  int64_t fileSize;
+  rv = metadataFile->GetFileSize(&fileSize);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // XXX Since we don't have regular metadata files for persistent storage yet,
+  //     we have to use a temporary hack.
+  //     Load origin access time from the metadata file, if we alredy saved it
+  //     to it. If the file size doesn't match a metadata file with saved origin
+  //     access time, get the last modiefied time of the metadata file and use
+  //     it as origin access time.
+  if (fileSize == sizeof(uint64_t)) {
+    nsCOMPtr<nsIBinaryInputStream> binaryStream;
+    rv = GetDirectoryMetadataInputStream(metadataFile, false,
+                                         getter_AddRefs(binaryStream));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    uint64_t timestamp;
+    rv = binaryStream->Read64(&timestamp);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    *aTimestamp = timestamp;
+    return NS_OK;
+  }
+
+  int64_t timestamp;
+  rv = metadataFile->GetLastModifiedTime(&timestamp);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  *aTimestamp = timestamp;
+  return NS_OK;
+}
+
+nsresult
 MaybeUpgradeOriginDirectory(nsIFile* aDirectory)
 {
   AssertIsOnIOThread();
   NS_ASSERTION(aDirectory, "Null pointer!");
 
   nsCOMPtr<nsIFile> metadataFile;
   nsresult rv = aDirectory->Clone(getter_AddRefs(metadataFile));
   NS_ENSURE_SUCCESS(rv, rv);
@@ -988,16 +1155,19 @@ QuotaManager::Init()
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = indexedDBDir->GetPath(mIndexedDBPath);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = baseDir->Append(NS_LITERAL_STRING("storage"));
     NS_ENSURE_SUCCESS(rv, rv);
 
+    rv = baseDir->GetPath(mStoragePath);
+    NS_ENSURE_SUCCESS(rv, rv);
+
     nsCOMPtr<nsIFile> persistentStorageDir;
     rv = baseDir->Clone(getter_AddRefs(persistentStorageDir));
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = persistentStorageDir->Append(NS_LITERAL_STRING("persistent"));
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = persistentStorageDir->GetPath(mPersistentStoragePath);
@@ -1058,43 +1228,45 @@ QuotaManager::Init()
 
   return NS_OK;
 }
 
 void
 QuotaManager::InitQuotaForOrigin(PersistenceType aPersistenceType,
                                  const nsACString& aGroup,
                                  const nsACString& aOrigin,
+                                 bool aIsApp,
                                  uint64_t aLimitBytes,
                                  uint64_t aUsageBytes,
                                  int64_t aAccessTime)
 {
   AssertIsOnIOThread();
-  MOZ_ASSERT(aLimitBytes > 0 ||
-             aPersistenceType == PERSISTENCE_TYPE_TEMPORARY);
-  MOZ_ASSERT(aUsageBytes <= aLimitBytes ||
-             aPersistenceType == PERSISTENCE_TYPE_TEMPORARY);
+  MOZ_ASSERT_IF(IsTreatedAsPersistent(aPersistenceType, aOrigin, aIsApp),
+                aLimitBytes > 0);
+  MOZ_ASSERT_IF(IsTreatedAsPersistent(aPersistenceType, aOrigin, aIsApp),
+                aUsageBytes <= aLimitBytes);
 
   MutexAutoLock lock(mQuotaMutex);
 
   GroupInfoPair* pair;
   if (!mGroupInfoPairs.Get(aGroup, &pair)) {
     pair = new GroupInfoPair();
     mGroupInfoPairs.Put(aGroup, pair);
     // The hashtable is now responsible to delete the GroupInfoPair.
   }
 
   nsRefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
   if (!groupInfo) {
-    groupInfo = new GroupInfo(aPersistenceType, aGroup);
+    groupInfo = new GroupInfo(pair, aPersistenceType, aGroup);
     pair->LockedSetGroupInfo(groupInfo);
   }
 
   nsRefPtr<OriginInfo> originInfo =
-    new OriginInfo(groupInfo, aOrigin, aLimitBytes, aUsageBytes, aAccessTime);
+    new OriginInfo(groupInfo, aOrigin, aIsApp, aLimitBytes, aUsageBytes,
+                   aAccessTime);
   groupInfo->LockedAddOriginInfo(originInfo);
 }
 
 void
 QuotaManager::DecreaseUsageForOrigin(PersistenceType aPersistenceType,
                                      const nsACString& aGroup,
                                      const nsACString& aOrigin,
                                      int64_t aSize)
@@ -1128,48 +1300,48 @@ QuotaManager::UpdateOriginAccessTime(Per
 
   MutexAutoLock lock(mQuotaMutex);
 
   GroupInfoPair* pair;
   if (!mGroupInfoPairs.Get(aGroup, &pair)) {
     return;
   }
 
-  nsRefPtr<GroupInfo> groupInfo =
-    pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
+  nsRefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
   if (!groupInfo) {
     return;
   }
 
   nsRefPtr<OriginInfo> originInfo = groupInfo->LockedGetOriginInfo(aOrigin);
   if (originInfo) {
     int64_t timestamp = PR_Now();
     originInfo->LockedUpdateAccessTime(timestamp);
 
-    if (!groupInfo->IsForTemporaryStorage()) {
-      return;
-    }
-
     MutexAutoUnlock autoUnlock(mQuotaMutex);
 
-    SaveOriginAccessTime(aOrigin, timestamp);
+    SaveOriginAccessTime(aPersistenceType, aOrigin, timestamp);
   }
 }
 
 // static
 PLDHashOperator
 QuotaManager::RemoveQuotaCallback(const nsACString& aKey,
                                   nsAutoPtr<GroupInfoPair>& aValue,
                                   void* aUserArg)
 {
   NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
   NS_ASSERTION(aValue, "Null pointer!");
 
   nsRefPtr<GroupInfo> groupInfo =
-    aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
+    aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_PERSISTENT);
+  if (groupInfo) {
+    groupInfo->LockedRemoveOriginInfos();
+  }
+
+  groupInfo = aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
   if (groupInfo) {
     groupInfo->LockedRemoveOriginInfos();
   }
 
   return PL_DHASH_REMOVE;
 }
 
 void
@@ -1179,50 +1351,54 @@ QuotaManager::RemoveQuota()
 
   mGroupInfoPairs.Enumerate(RemoveQuotaCallback, nullptr);
 
   NS_ASSERTION(mTemporaryStorageUsage == 0, "Should be zero!");
 }
 
 // static
 PLDHashOperator
-QuotaManager::RemoveQuotaForPersistenceTypeCallback(
+QuotaManager::RemoveQuotaForTemporaryStorageCallback(
                                                const nsACString& aKey,
                                                nsAutoPtr<GroupInfoPair>& aValue,
                                                void* aUserArg)
 {
   NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
   NS_ASSERTION(aValue, "Null pointer!");
-  NS_ASSERTION(aUserArg, "Null pointer!");
-
-  PersistenceType& persistenceType = *static_cast<PersistenceType*>(aUserArg);
-
-  if (persistenceType == PERSISTENCE_TYPE_TEMPORARY) {
-    nsRefPtr<GroupInfo> groupInfo =
-      aValue->LockedGetGroupInfo(persistenceType);
-    if (groupInfo) {
-      groupInfo->LockedRemoveOriginInfos();
+
+  nsRefPtr<GroupInfo> groupInfo =
+    aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_PERSISTENT);
+  if (groupInfo) {
+    groupInfo->LockedRemoveTemporaryOriginInfos();
+
+    if (!groupInfo->LockedHasOriginInfos()) {
+      aValue->LockedClearGroupInfo(PERSISTENCE_TYPE_PERSISTENT);
     }
   }
 
-  aValue->LockedClearGroupInfo(persistenceType);
+  groupInfo = aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
+  if (groupInfo) {
+    groupInfo->LockedRemoveTemporaryOriginInfos();
+
+    if (!groupInfo->LockedHasOriginInfos()) {
+      aValue->LockedClearGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
+    }
+  }
 
   return aValue->LockedHasGroupInfos() ? PL_DHASH_NEXT : PL_DHASH_REMOVE;
 }
 
 void
-QuotaManager::RemoveQuotaForPersistenceType(PersistenceType aPersistenceType)
+QuotaManager::RemoveQuotaForTemporaryStorage()
 {
   MutexAutoLock lock(mQuotaMutex);
 
-  mGroupInfoPairs.Enumerate(RemoveQuotaForPersistenceTypeCallback,
-                            &aPersistenceType);
-
-  NS_ASSERTION(aPersistenceType == PERSISTENCE_TYPE_PERSISTENT ||
-               mTemporaryStorageUsage == 0, "Should be zero!");
+  mGroupInfoPairs.Enumerate(RemoveQuotaForTemporaryStorageCallback, nullptr);
+
+  NS_ASSERTION(mTemporaryStorageUsage == 0, "Should be zero!");
 }
 
 // static
 PLDHashOperator
 QuotaManager::RemoveQuotaForPatternCallback(const nsACString& aKey,
                                             nsAutoPtr<GroupInfoPair>& aValue,
                                             void* aUserArg)
 {
@@ -1360,44 +1536,63 @@ QuotaManager::RegisterStorage(nsIOffline
   }
 
   // Add this storage to its origin info if it exists, create it otherwise.
   const nsACString& origin = aStorage->Origin();
   ArrayCluster<nsIOfflineStorage*>* cluster;
   if (!mLiveStorages.Get(origin, &cluster)) {
     cluster = new ArrayCluster<nsIOfflineStorage*>();
     mLiveStorages.Put(origin, cluster);
+  }
+  (*cluster)[aStorage->GetClient()->GetType()].AppendElement(aStorage);
+
+  LiveStorageTable& liveStorageTable = GetLiveStorageTable(aStorage->Type());
+
+  nsTArray<nsIOfflineStorage*>* array;
+  if (!liveStorageTable.Get(origin, &array)) {
+    array = new nsTArray<nsIOfflineStorage*>();
+    liveStorageTable.Put(origin, array);
 
     UpdateOriginAccessTime(aStorage->Type(), aStorage->Group(), origin);
   }
-  (*cluster)[aStorage->GetClient()->GetType()].AppendElement(aStorage);
+  array->AppendElement(aStorage);
 
   return true;
 }
 
 void
 QuotaManager::UnregisterStorage(nsIOfflineStorage* aStorage)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(aStorage, "Null pointer!");
 
   // Remove this storage from its origin array, maybe remove the array if it
   // is then empty.
   const nsACString& origin = aStorage->Origin();
+
   ArrayCluster<nsIOfflineStorage*>* cluster;
-  if (mLiveStorages.Get(origin, &cluster) &&
-      (*cluster)[aStorage->GetClient()->GetType()].RemoveElement(aStorage)) {
-    if (cluster->IsEmpty()) {
-      mLiveStorages.Remove(origin);
-
-      UpdateOriginAccessTime(aStorage->Type(), aStorage->Group(), origin);
-    }
-    return;
+  MOZ_ALWAYS_TRUE(mLiveStorages.Get(origin, &cluster));
+
+  MOZ_ALWAYS_TRUE(
+    (*cluster)[aStorage->GetClient()->GetType()].RemoveElement(aStorage));
+  if (cluster->IsEmpty()) {
+    mLiveStorages.Remove(origin);
   }
-  NS_ERROR("Didn't know anything about this storage!");
+
+  LiveStorageTable& liveStorageTable = GetLiveStorageTable(aStorage->Type());
+
+  nsTArray<nsIOfflineStorage*>* array;
+  MOZ_ALWAYS_TRUE(liveStorageTable.Get(origin, &array));
+
+  MOZ_ALWAYS_TRUE(array->RemoveElement(aStorage));
+  if (array->IsEmpty()) {
+    liveStorageTable.Remove(origin);
+
+    UpdateOriginAccessTime(aStorage->Type(), aStorage->Group(), origin);
+  }
 }
 
 void
 QuotaManager::OnStorageClosed(nsIOfflineStorage* aStorage)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(aStorage, "Null pointer!");
 
@@ -1649,37 +1844,149 @@ QuotaManager::GetDirectoryForOrigin(Pers
   rv = directory->Append(NS_ConvertASCIItoUTF16(originSanitized));
   NS_ENSURE_SUCCESS(rv, rv);
 
   directory.forget(aDirectory);
   return NS_OK;
 }
 
 nsresult
+QuotaManager::InitializeRepository(PersistenceType aPersistenceType)
+{
+  nsresult rv;
+
+  nsCOMPtr<nsIFile> directory =
+    do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = directory->InitWithPath(GetStoragePath(aPersistenceType));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  bool created;
+  rv = EnsureDirectory(directory, &created);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCOMPtr<nsISimpleEnumerator> entries;
+  rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  bool hasMore;
+  while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) {
+    nsCOMPtr<nsISupports> entry;
+    rv = entries->GetNext(getter_AddRefs(entry));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    nsCOMPtr<nsIFile> childDirectory = do_QueryInterface(entry);
+    MOZ_ASSERT(childDirectory);
+
+    nsString leafName;
+    rv = childDirectory->GetLeafName(leafName);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    bool isDirectory;
+    rv = childDirectory->IsDirectory(&isDirectory);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (!isDirectory) {
+      if (leafName.EqualsLiteral(DSSTORE_FILE_NAME)) {
+        continue;
+      }
+
+      nsPrintfCString message("Something (%s) in the repository that doesn't "
+                              "belong!", NS_ConvertUTF16toUTF8(leafName).get());
+      NS_WARNING(message.get());
+      return NS_ERROR_UNEXPECTED;
+    }
+
+    int64_t timestamp;
+    nsCString group;
+    nsCString origin;
+
+    // XXX This is temporary, we don't have real .metadata files for persistent
+    //     storage yet.
+    if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
+      rv = MaybeUpgradeOriginDirectory(childDirectory);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      rv = GetDirectoryMetadataLastModifiedTime(childDirectory, &timestamp);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      origin = NS_ConvertUTF16toUTF8(leafName);
+      group = origin;
+    } else {
+      rv = GetDirectoryMetadata(childDirectory, &timestamp, group, origin);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      SanitizeOriginString(origin);
+    }
+
+    bool isApp = IsApp(origin);
+
+    if (IsTreatedAsPersistent(aPersistenceType, origin, isApp)) {
+      continue;
+    }
+
+    rv = InitializeOrigin(aPersistenceType, group, origin, isApp, false,
+                          timestamp, childDirectory);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
 QuotaManager::InitializeOrigin(PersistenceType aPersistenceType,
                                const nsACString& aGroup,
                                const nsACString& aOrigin,
-                               bool aTrackQuota,
+                               bool aIsApp,
+                               bool aHasUnlimStoragePerm,
                                int64_t aAccessTime,
                                nsIFile* aDirectory)
 {
   AssertIsOnIOThread();
 
   nsresult rv;
 
-  bool temporaryStorage = aPersistenceType == PERSISTENCE_TYPE_TEMPORARY;
-  if (!temporaryStorage) {
+  if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
     rv = MaybeUpgradeOriginDirectory(aDirectory);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
+  bool trackQuota =
+    IsQuotaEnforced(aPersistenceType, aOrigin, aIsApp, aHasUnlimStoragePerm);
+
   // We need to initialize directories of all clients if they exists and also
   // get the total usage to initialize the quota.
   nsAutoPtr<UsageInfo> usageInfo;
-  if (aTrackQuota) {
+  if (trackQuota) {
     usageInfo = new UsageInfo();
   }
 
   nsCOMPtr<nsISimpleEnumerator> entries;
   rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
   NS_ENSURE_SUCCESS(rv, rv);
 
   bool hasMore;
@@ -1716,34 +2023,34 @@ QuotaManager::InitializeOrigin(Persisten
       return NS_ERROR_UNEXPECTED;
     }
 
     rv = mClients[clientType]->InitOrigin(aPersistenceType, aGroup, aOrigin,
                                           usageInfo);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  if (aTrackQuota) {
+  if (trackQuota) {
     uint64_t quotaMaxBytes;
     uint64_t totalUsageBytes = usageInfo->TotalUsage();
 
-    if (temporaryStorage) {
+    if (IsTreatedAsTemporary(aPersistenceType, aOrigin, aIsApp)) {
       // Temporary storage has no limit for origin usage (there's a group and
       // the global limit though).
       quotaMaxBytes = 0;
     }
     else {
       quotaMaxBytes = GetStorageQuotaMB() * 1024 * 1024;
       if (totalUsageBytes > quotaMaxBytes) {
         NS_WARNING("Origin is already using more storage than allowed!");
         return NS_ERROR_FILE_NO_DEVICE_SPACE;
       }
     }
 
-    InitQuotaForOrigin(aPersistenceType, aGroup, aOrigin, quotaMaxBytes,
+    InitQuotaForOrigin(aPersistenceType, aGroup, aOrigin, aIsApp, quotaMaxBytes,
                        totalUsageBytes, aAccessTime);
   }
 
   return NS_OK;
 }
 
 nsresult
 QuotaManager::MaybeUpgradeIndexedDBDirectory()
@@ -1819,151 +2126,162 @@ QuotaManager::MaybeUpgradeIndexedDBDirec
 
   return NS_OK;
 }
 
 nsresult
 QuotaManager::EnsureOriginIsInitialized(PersistenceType aPersistenceType,
                                         const nsACString& aGroup,
                                         const nsACString& aOrigin,
-                                        bool aTrackQuota,
+                                        bool aIsApp,
+                                        bool aHasUnlimStoragePerm,
                                         nsIFile** aDirectory)
 {
   AssertIsOnIOThread();
 
   nsresult rv = MaybeUpgradeIndexedDBDirectory();
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Get directory for this origin and persistence type.
   nsCOMPtr<nsIFile> directory;
   rv = GetDirectoryForOrigin(aPersistenceType, aOrigin,
                              getter_AddRefs(directory));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
+  if (IsTreatedAsPersistent(aPersistenceType, aOrigin, aIsApp)) {
     if (mInitializedOrigins.Contains(aOrigin)) {
       NS_ADDREF(*aDirectory = directory);
       return NS_OK;
     }
-
-    bool created;
-    rv = EnsureDirectory(directory, &created);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    if (created) {
-      rv = CreateDirectoryUpgradeStamp(directory);
-      NS_ENSURE_SUCCESS(rv, rv);
+  } else if (!mTemporaryStorageInitialized) {
+    rv = InitializeRepository(aPersistenceType);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      // We have to cleanup partially initialized quota for temporary storage.
+      RemoveQuotaForTemporaryStorage();
+
+      return rv;
     }
 
-    rv = InitializeOrigin(aPersistenceType, aGroup, aOrigin, aTrackQuota, 0,
-                          directory);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    mInitializedOrigins.AppendElement(aOrigin);
-
-    directory.forget(aDirectory);
-    return NS_OK;
-  }
-
-  NS_ASSERTION(aPersistenceType == PERSISTENCE_TYPE_TEMPORARY, "Huh?");
-  NS_ASSERTION(aTrackQuota, "Huh?");
-
-  if (!mTemporaryStorageInitialized) {
-    nsCOMPtr<nsIFile> parentDirectory;
-    rv = directory->GetParent(getter_AddRefs(parentDirectory));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    bool created;
-    rv = EnsureDirectory(parentDirectory, &created);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    nsCOMPtr<nsISimpleEnumerator> entries;
-    rv = parentDirectory->GetDirectoryEntries(getter_AddRefs(entries));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    bool hasMore;
-    while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) {
-      nsCOMPtr<nsISupports> entry;
-      rv = entries->GetNext(getter_AddRefs(entry));
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      nsCOMPtr<nsIFile> childDirectory = do_QueryInterface(entry);
-      NS_ENSURE_TRUE(childDirectory, NS_NOINTERFACE);
-
-      bool isDirectory;
-      rv = childDirectory->IsDirectory(&isDirectory);
-      NS_ENSURE_SUCCESS(rv, rv);
-      NS_ENSURE_TRUE(isDirectory, NS_ERROR_UNEXPECTED);
-
-      int64_t timestamp;
-      nsCString group;
-      nsCString origin;
-      rv = GetDirectoryMetadata(childDirectory, &timestamp, group, origin);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      rv = InitializeOrigin(aPersistenceType, group, origin, aTrackQuota,
-                            timestamp, childDirectory);
-      if (NS_FAILED(rv)) {
-        NS_WARNING("Failed to initialize origin!");
-
-        // We have to cleanup partially initialized quota for temporary storage.
-        RemoveQuotaForPersistenceType(aPersistenceType);
-
-        return rv;
-      }
+    rv = InitializeRepository(ComplementaryPersistenceType(aPersistenceType));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      // We have to cleanup partially initialized quota for temporary storage.
+      RemoveQuotaForTemporaryStorage();
+
+      return rv;
     }
 
     if (gFixedLimitKB >= 0) {
       mTemporaryStorageLimit = gFixedLimitKB * 1024;
     }
     else {
-      rv = GetTemporaryStorageLimit(parentDirectory, mTemporaryStorageUsage,
+      nsCOMPtr<nsIFile> storageDir =
+        do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      rv = storageDir->InitWithPath(GetStoragePath());
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      rv = GetTemporaryStorageLimit(storageDir, mTemporaryStorageUsage,
                                     &mTemporaryStorageLimit);
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     mTemporaryStorageInitialized = true;
 
     CheckTemporaryStorageLimits();
   }
 
+  nsCOMPtr<nsIFile> parentDirectory;
+  rv = directory->GetParent(getter_AddRefs(parentDirectory));
+  NS_ENSURE_SUCCESS(rv, rv);
+
   bool created;
+  rv = EnsureDirectory(parentDirectory, &created);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  int64_t timestamp;
+
   rv = EnsureDirectory(directory, &created);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (created) {
-    int64_t timestamp = PR_Now();
-
-    rv = CreateDirectoryMetadata(directory, timestamp, aGroup, aOrigin);
+  if (IsTreatedAsPersistent(aPersistenceType, aOrigin, aIsApp)) {
+    if (created) {
+      timestamp = PR_Now();
+
+      if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
+        rv = CreateDirectoryMetadataLastModifiedTime(directory, timestamp);
+      } else {
+        rv = CreateDirectoryMetadata(directory, timestamp, aGroup, aOrigin);
+      }
+      NS_ENSURE_SUCCESS(rv, rv);
+    } else {
+      if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
+        rv = GetDirectoryMetadataLastModifiedTime(directory, &timestamp);
+        NS_ENSURE_SUCCESS(rv, rv);
+      } else {
+        nsCOMPtr<nsIBinaryInputStream> stream;
+        rv = GetDirectoryMetadataInputStream(directory, true,
+                                             getter_AddRefs(stream));
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        uint64_t ts;
+        rv = stream->Read64(&ts);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        timestamp = ts;
+      }
+    }
+
+    rv = InitializeOrigin(aPersistenceType, aGroup, aOrigin, aIsApp,
+                          aHasUnlimStoragePerm, timestamp, directory);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = InitializeOrigin(aPersistenceType, aGroup, aOrigin, aTrackQuota,
-                          timestamp, directory);
+    mInitializedOrigins.AppendElement(aOrigin);
+  } else if (created) {
+    timestamp = PR_Now();
+
+    if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
+      rv = CreateDirectoryMetadataLastModifiedTime(directory, timestamp);
+    } else {
+      rv = CreateDirectoryMetadata(directory, timestamp, aGroup, aOrigin);
+    }
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = InitializeOrigin(aPersistenceType, aGroup, aOrigin, aIsApp,
+                          aHasUnlimStoragePerm, timestamp, directory);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   directory.forget(aDirectory);
   return NS_OK;
 }
 
 void
 QuotaManager::OriginClearCompleted(
                                   PersistenceType aPersistenceType,
                                   const OriginOrPatternString& aOriginOrPattern)
 {
   AssertIsOnIOThread();
 
-  if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
-    if (aOriginOrPattern.IsOrigin()) {
+  if (aOriginOrPattern.IsOrigin()) {
+    bool isApp = IsApp(aOriginOrPattern);
+    if (IsTreatedAsPersistent(aPersistenceType, aOriginOrPattern, isApp)) {
       mInitializedOrigins.RemoveElement(aOriginOrPattern);
     }
-    else {
-      for (uint32_t index = mInitializedOrigins.Length(); index > 0; index--) {
-        if (PatternMatchesOrigin(aOriginOrPattern,
-                                 mInitializedOrigins[index - 1])) {
+  }
+  else {
+    for (uint32_t index = mInitializedOrigins.Length(); index > 0; index--) {
+      nsCString& initializedOrigin = mInitializedOrigins[index - 1];
+      if (PatternMatchesOrigin(aOriginOrPattern, initializedOrigin)) {
+        bool isApp = IsApp(initializedOrigin);
+        if (IsTreatedAsPersistent(aPersistenceType, initializedOrigin, isApp)) {
           mInitializedOrigins.RemoveElementAt(index - 1);
         }
       }
     }
   }
 
   for (uint32_t index = 0; index < Client::TYPE_MAX; index++) {
     mClients[index]->OnOriginClearCompleted(aPersistenceType, aOriginOrPattern);
@@ -2031,45 +2349,48 @@ QuotaManager::GetStorageId(PersistenceTy
   aDatabaseId = str;
 }
 
 // static
 nsresult
 QuotaManager::GetInfoFromURI(nsIURI* aURI,
                              uint32_t aAppId,
                              bool aInMozBrowser,
+                             PersistenceType aPersistenceType,
                              nsACString* aGroup,
                              nsACString* aOrigin,
                              StoragePrivilege* aPrivilege,
-                             PersistenceType* aDefaultPersistenceType)
+                             bool* aIsApp,
+                             bool* aHasUnlimStoragePerm)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aURI);
 
   nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
   NS_ENSURE_TRUE(secMan, NS_ERROR_FAILURE);
 
   nsCOMPtr<nsIPrincipal> principal;
   nsresult rv = secMan->GetAppCodebasePrincipal(aURI, aAppId, aInMozBrowser,
                                                 getter_AddRefs(principal));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = GetInfoFromPrincipal(principal, aGroup, aOrigin, aPrivilege,
-                            aDefaultPersistenceType);
+  rv = GetInfoFromPrincipal(principal, aPersistenceType, aGroup, aOrigin,
+                            aPrivilege, aIsApp, aHasUnlimStoragePerm);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 static nsresult
 TryGetInfoForAboutURI(nsIPrincipal* aPrincipal,
                       nsACString& aGroup,
                       nsACString& aASCIIOrigin,
                       StoragePrivilege* aPrivilege,
-                      PersistenceType* aDefaultPersistenceType)
+                      bool* aIsApp,
+                      bool* aHasUnlimStoragePerm)
 {
   NS_ASSERTION(aPrincipal, "Don't hand me a null principal!");
 
   nsCOMPtr<nsIURI> uri;
   nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
   NS_ENSURE_SUCCESS(rv, rv);
   if (!uri) {
     return NS_ERROR_NOT_AVAILABLE;
@@ -2109,44 +2430,60 @@ TryGetInfoForAboutURI(nsIPrincipal* aPri
   ToLowerCase(origin);
   aGroup.Assign(origin);
   aASCIIOrigin.Assign(origin);
 
   if (aPrivilege) {
     *aPrivilege = Content;
   }
 
-  if (aDefaultPersistenceType) {
-    *aDefaultPersistenceType = PERSISTENCE_TYPE_PERSISTENT;
+  if (aIsApp) {
+    *aIsApp = false;
+  }
+
+  if (aHasUnlimStoragePerm) {
+    *aHasUnlimStoragePerm = false;
   }
 
+  // XXX This is temporary, we don't have regular .metadata files for persistent
+  //     storage yet, so we have to use sanitized origin strings and create
+  //     separate group for each origin.
+  nsCString originSanitized(aASCIIOrigin);
+  SanitizeOriginString(originSanitized);
+  aASCIIOrigin = originSanitized;
+
+  // XXX The group is already the same as the origin.
+
   return NS_OK;
 }
 
 // static
 nsresult
 QuotaManager::GetInfoFromPrincipal(nsIPrincipal* aPrincipal,
+                                   PersistenceType aPersistenceType,
                                    nsACString* aGroup,
                                    nsACString* aOrigin,
                                    StoragePrivilege* aPrivilege,
-                                   PersistenceType* aDefaultPersistenceType)
+                                   bool* aIsApp,
+                                   bool* aHasUnlimStoragePerm)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aPrincipal);
 
   if (aGroup && aOrigin) {
     nsresult rv = TryGetInfoForAboutURI(aPrincipal, *aGroup, *aOrigin,
-                                        aPrivilege, aDefaultPersistenceType);
+                                        aPrivilege, aIsApp,
+                                        aHasUnlimStoragePerm);
     if (NS_SUCCEEDED(rv)) {
       return NS_OK;
     }
   }
 
   if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
-    GetInfoForChrome(aGroup, aOrigin, aPrivilege, aDefaultPersistenceType);
+    GetInfoForChrome(aGroup, aOrigin, aPrivilege, aIsApp, aHasUnlimStoragePerm);
     return NS_OK;
   }
 
   bool isNullPrincipal;
   nsresult rv = aPrincipal->GetIsNullPrincipal(&isNullPrincipal);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (isNullPrincipal) {
@@ -2200,69 +2537,134 @@ QuotaManager::GetInfoFromPrincipal(nsIPr
   if (aOrigin) {
     aOrigin->Assign(jarPrefix + origin);
   }
 
   if (aPrivilege) {
     *aPrivilege = Content;
   }
 
-  if (aDefaultPersistenceType) {
-    *aDefaultPersistenceType = PERSISTENCE_TYPE_PERSISTENT;
+  if (aIsApp) {
+    *aIsApp = aPrincipal->GetAppStatus() !=
+                nsIPrincipal::APP_STATUS_NOT_INSTALLED;
+  }
+
+  if (aHasUnlimStoragePerm) {
+    *aHasUnlimStoragePerm = CheckQuotaHelper::GetQuotaPermission(aPrincipal) ==
+                              nsIPermissionManager::ALLOW_ACTION;
+  }
+
+  // XXX This is temporary, we don't have regular .metadata files for persistent
+  //     storage yet, so we have to use sanitized origin strings and create
+  //     separate group for each origin.
+  if (aOrigin) {
+    nsCString originSanitized(*aOrigin);
+    SanitizeOriginString(originSanitized);
+    *aOrigin = originSanitized;
+  }
+  if (aGroup && aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
+    *aGroup = *aOrigin;
   }
 
   return NS_OK;
 }
 
 // static
 nsresult
 QuotaManager::GetInfoFromWindow(nsPIDOMWindow* aWindow,
+                                PersistenceType aPersistenceType,
                                 nsACString* aGroup,
                                 nsACString* aOrigin,
                                 StoragePrivilege* aPrivilege,
-                                PersistenceType* aDefaultPersistenceType)
+                                bool* aIsApp,
+                                bool* aHasUnlimStoragePerm)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aWindow);
 
   nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow);
   NS_ENSURE_TRUE(sop, NS_ERROR_FAILURE);
 
   nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
   NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE);
 
-  nsresult rv = GetInfoFromPrincipal(principal, aGroup, aOrigin, aPrivilege,
-                                     aDefaultPersistenceType);
+  nsresult rv = GetInfoFromPrincipal(principal, aPersistenceType, aGroup,
+                                     aOrigin, aPrivilege, aIsApp,
+                                     aHasUnlimStoragePerm);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 // static
 void
 QuotaManager::GetInfoForChrome(nsACString* aGroup,
                                nsACString* aOrigin,
                                StoragePrivilege* aPrivilege,
-                               PersistenceType* aDefaultPersistenceType)
+                               bool* aIsApp,
+                               bool* aHasUnlimStoragePerm)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(nsContentUtils::IsCallerChrome());
 
   if (aGroup) {
     ChromeOrigin(*aGroup);
   }
   if (aOrigin) {
     ChromeOrigin(*aOrigin);
   }
   if (aPrivilege) {
     *aPrivilege = Chrome;
   }
-  if (aDefaultPersistenceType) {
-    *aDefaultPersistenceType = PERSISTENCE_TYPE_PERSISTENT;
+  if (aIsApp) {
+    *aIsApp = false;
+  }
+  if (aHasUnlimStoragePerm) {
+    *aHasUnlimStoragePerm = false;
+  }
+
+  // XXX The chrome origin doesn't have to be sanitized and the group is already
+  //     the same as the origin.
+}
+
+bool
+QuotaManager::IsTreatedAsPersistent(PersistenceType aPersistenceType,
+                                    const nsACString& aOrigin,
+                                    bool aIsApp)
+{
+  if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT &&
+      (aIsApp ||
+       aOrigin.Equals("chrome") ||
+       aOrigin.Equals("moz-safe-about+home"))) {
+    return true;
   }
+
+  return false;
+}
+
+// static
+bool
+QuotaManager::IsQuotaEnforced(PersistenceType aPersistenceType,
+                              const nsACString& aOrigin,
+                              bool aIsApp,
+                              bool aHasUnlimStoragePerm)
+{
+  bool result;
+
+  if (IsTreatedAsTemporary(aPersistenceType, aOrigin, aIsApp)) {
+    result = true;
+  } else {
+    if (aOrigin.Equals("chrome")) {
+      result = false;
+    } else {
+      result = !aHasUnlimStoragePerm;
+    }
+  }
+
+  return result;
 }
 
 // static
 void
 QuotaManager::ChromeOrigin(nsACString& aOrigin)
 {
   aOrigin.AssignLiteral(kChromeOrigin);
 }
@@ -2285,27 +2687,35 @@ QuotaManager::GetUsageForURI(nsIURI* aUR
   // This only works from the main process.
   NS_ENSURE_TRUE(IsMainProcess(), NS_ERROR_NOT_AVAILABLE);
 
   if (!aOptionalArgCount) {
     aAppId = nsIScriptSecurityManager::NO_APP_ID;
   }
 
   // Figure out which origin we're dealing with.
-  nsCString group;
+  nsCString persistentGroup;
   nsCString origin;
-  nsresult rv = GetInfoFromURI(aURI, aAppId, aInMozBrowserOnly, &group, &origin,
-                               nullptr, nullptr);
+  bool isApp;
+  nsresult rv = GetInfoFromURI(aURI, aAppId, aInMozBrowserOnly,
+                               PERSISTENCE_TYPE_PERSISTENT, &persistentGroup,
+                               &origin, nullptr, &isApp, nullptr);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCString temporaryGroup;
+  rv = GetInfoFromURI(aURI, aAppId, aInMozBrowserOnly,
+                      PERSISTENCE_TYPE_TEMPORARY, &temporaryGroup,
+                      nullptr, nullptr, nullptr, nullptr);
   NS_ENSURE_SUCCESS(rv, rv);
 
   OriginOrPatternString oops = OriginOrPatternString::FromOrigin(origin);
 
   nsRefPtr<AsyncUsageRunnable> runnable =
-    new AsyncUsageRunnable(aAppId, aInMozBrowserOnly, group, oops, aURI,
-                           aCallback);
+    new AsyncUsageRunnable(aAppId, aInMozBrowserOnly, persistentGroup,
+                           temporaryGroup, oops, isApp, aURI, aCallback);
 
   // Put the computation runnable in the queue.
   rv = WaitForOpenAllowed(oops, Nullable<PersistenceType>(), EmptyCString(),
                           runnable);
   NS_ENSURE_SUCCESS(rv, rv);
 
   runnable->AdvanceState();
 
@@ -2373,20 +2783,23 @@ QuotaManager::ClearStoragesForURI(nsIURI
   NS_ENSURE_TRUE(IsMainProcess(), NS_ERROR_NOT_AVAILABLE);
 
   if (!aOptionalArgCount) {
     aAppId = nsIScriptSecurityManager::NO_APP_ID;
   }
 
   // Figure out which origin we're dealing with.
   nsCString origin;
-  rv = GetInfoFromURI(aURI, aAppId, aInMozBrowserOnly, nullptr, &origin,
-                      nullptr, nullptr);
+  rv = GetInfoFromURI(aURI, aAppId, aInMozBrowserOnly, PERSISTENCE_TYPE_INVALID,
+                      nullptr, &origin, nullptr, nullptr, nullptr);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  // XXX We don't care about the group, no need to update it for persistence
+  //     storage.
+
   nsAutoCString pattern;
   GetOriginPatternString(aAppId, aInMozBrowserOnly, origin, pattern);
 
   // If there is a pending or running clear operation for this origin, return
   // immediately.
   if (IsClearOriginPending(pattern, persistenceType)) {
     return NS_OK;
   }
@@ -2973,37 +3386,54 @@ QuotaManager::ClearStoragesForApp(uint32
 PLDHashOperator
 QuotaManager::GetOriginsExceedingGroupLimit(const nsACString& aKey,
                                             GroupInfoPair* aValue,
                                             void* aUserArg)
 {
   NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
   NS_ASSERTION(aValue, "Null pointer!");
 
-  nsRefPtr<GroupInfo> groupInfo =
+  uint64_t groupUsage = 0;
+
+  nsRefPtr<GroupInfo> persistentGroupInfo =
+    aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_PERSISTENT);
+  if (persistentGroupInfo) {
+    groupUsage += persistentGroupInfo->LockedGetTemporaryUsage();
+  }
+
+  nsRefPtr<GroupInfo> temporaryGroupInfo =
     aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
-  if (groupInfo) {
+  if (temporaryGroupInfo) {
+    groupUsage += temporaryGroupInfo->LockedGetTemporaryUsage();
+  }
+
+  if (groupUsage > 0) {
     QuotaManager* quotaManager = QuotaManager::Get();
     NS_ASSERTION(quotaManager, "Shouldn't be null!");
 
-    if (groupInfo->mUsage > quotaManager->GetGroupLimit()) {
+    if (groupUsage > quotaManager->GetGroupLimit()) {
       nsTArray<OriginInfo*>* doomedOriginInfos =
         static_cast<nsTArray<OriginInfo*>*>(aUserArg);
 
-      nsTArray<nsRefPtr<OriginInfo> >& originInfos = groupInfo->mOriginInfos;
+      nsTArray<OriginInfo*> originInfos;
+      if (persistentGroupInfo) {
+        persistentGroupInfo->LockedGetTemporaryOriginInfos(&originInfos);
+      }
+      if (temporaryGroupInfo) {
+        temporaryGroupInfo->LockedGetTemporaryOriginInfos(&originInfos);
+      }
       originInfos.Sort(OriginInfoLRUComparator());
 
-      uint64_t usage = groupInfo->mUsage;
       for (uint32_t i = 0; i < originInfos.Length(); i++) {
         OriginInfo* originInfo = originInfos[i];
 
         doomedOriginInfos->AppendElement(originInfo);
-        usage -= originInfo->mUsage;
-
-        if (usage <= quotaManager->GetGroupLimit()) {
+        groupUsage -= originInfo->mUsage;
+
+        if (groupUsage <= quotaManager->GetGroupLimit()) {
           break;
         }
       }
     }
   }
 
   return PL_DHASH_NEXT;
 }
@@ -3012,23 +3442,28 @@ QuotaManager::GetOriginsExceedingGroupLi
 PLDHashOperator
 QuotaManager::GetAllTemporaryStorageOrigins(const nsACString& aKey,
                                             GroupInfoPair* aValue,
                                             void* aUserArg)
 {
   NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
   NS_ASSERTION(aValue, "Null pointer!");
 
+  nsTArray<OriginInfo*>* originInfos =
+    static_cast<nsTArray<OriginInfo*>*>(aUserArg);
+
   nsRefPtr<GroupInfo> groupInfo =
-    aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
+    aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_PERSISTENT);
   if (groupInfo) {
-    nsTArray<OriginInfo*>* originInfos =
-      static_cast<nsTArray<OriginInfo*>*>(aUserArg);
-
-    originInfos->AppendElements(groupInfo->mOriginInfos);
+    groupInfo->LockedGetTemporaryOriginInfos(originInfos);
+  }
+
+  groupInfo = aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
+  if (groupInfo) {
+    groupInfo->LockedGetTemporaryOriginInfos(originInfos);
   }
 
   return PL_DHASH_NEXT;
 }
 
 void
 QuotaManager::CheckTemporaryStorageLimits()
 {
@@ -3069,98 +3504,116 @@ QuotaManager::CheckTemporaryStorageLimit
         usage += originInfos[i]->mUsage;
       }
 
       doomedOriginInfos.AppendElements(originInfos);
     }
   }
 
   for (uint32_t index = 0; index < doomedOriginInfos.Length(); index++) {
-    DeleteTemporaryFilesForOrigin(doomedOriginInfos[index]->mOrigin);
+    OriginInfo* doomedOriginInfo = doomedOriginInfos[index];
+
+    DeleteFilesForOrigin(doomedOriginInfo->mGroupInfo->mPersistenceType,
+                         doomedOriginInfo->mOrigin);
   }
 
-  nsTArray<nsCString> doomedOrigins;
+  nsTArray<OriginParams> doomedOrigins;
   {
     MutexAutoLock lock(mQuotaMutex);
 
     for (uint32_t index = 0; index < doomedOriginInfos.Length(); index++) {
       OriginInfo* doomedOriginInfo = doomedOriginInfos[index];
 
+      PersistenceType persistenceType =
+        doomedOriginInfo->mGroupInfo->mPersistenceType;
       nsCString group = doomedOriginInfo->mGroupInfo->mGroup;
       nsCString origin = doomedOriginInfo->mOrigin;
-      LockedRemoveQuotaForOrigin(PERSISTENCE_TYPE_TEMPORARY, group, origin);
+      LockedRemoveQuotaForOrigin(persistenceType, group, origin);
 
 #ifdef DEBUG
       doomedOriginInfos[index] = nullptr;
 #endif
 
-      doomedOrigins.AppendElement(origin);
+      doomedOrigins.AppendElement(OriginParams(persistenceType, origin));
     }
   }
 
   for (uint32_t index = 0; index < doomedOrigins.Length(); index++) {
+    const OriginParams& doomedOrigin = doomedOrigins[index];
+
     OriginClearCompleted(
-                       PERSISTENCE_TYPE_TEMPORARY,
-                       OriginOrPatternString::FromOrigin(doomedOrigins[index]));
+                       doomedOrigin.mPersistenceType,
+                       OriginOrPatternString::FromOrigin(doomedOrigin.mOrigin));
   }
 }
 
 // static
 PLDHashOperator
-QuotaManager::AddTemporaryStorageOrigins(
-                                       const nsACString& aKey,
-                                       ArrayCluster<nsIOfflineStorage*>* aValue,
-                                       void* aUserArg)
+QuotaManager::AddLiveStorageOrigins(const nsACString& aKey,
+                                    nsTArray<nsIOfflineStorage*>* aValue,
+                                    void* aUserArg)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
   NS_ASSERTION(aValue, "Null pointer!");
   NS_ASSERTION(aUserArg, "Null pointer!");
 
   OriginCollection& collection = *static_cast<OriginCollection*>(aUserArg);
 
-  if (collection.ContainsOrigin(aKey)) {
-    return PL_DHASH_NEXT;
-  }
-
-  for (uint32_t i = 0; i < Client::TYPE_MAX; i++) {
-    nsTArray<nsIOfflineStorage*>& array = (*aValue)[i];
-    for (uint32_t j = 0; j < array.Length(); j++) {
-      nsIOfflineStorage*& storage = array[j];
-      if (storage->Type() == PERSISTENCE_TYPE_TEMPORARY) {
-        collection.AddOrigin(aKey);
-        return PL_DHASH_NEXT;
-      }
-    }
+  if (!collection.ContainsOrigin(aKey)) {
+    collection.AddOrigin(aKey);
   }
 
   return PL_DHASH_NEXT;
 }
 
 // static
 PLDHashOperator
 QuotaManager::GetInactiveTemporaryStorageOrigins(const nsACString& aKey,
                                                  GroupInfoPair* aValue,
                                                  void* aUserArg)
 {
   NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
   NS_ASSERTION(aValue, "Null pointer!");
   NS_ASSERTION(aUserArg, "Null pointer!");
 
+  InactiveOriginsInfo* info = static_cast<InactiveOriginsInfo*>(aUserArg);
+
   nsRefPtr<GroupInfo> groupInfo =
-    aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
+    aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_PERSISTENT);
   if (groupInfo) {
-    InactiveOriginsInfo* info = static_cast<InactiveOriginsInfo*>(aUserArg);
-
     nsTArray<nsRefPtr<OriginInfo> >& originInfos = groupInfo->mOriginInfos;
 
     for (uint32_t i = 0; i < originInfos.Length(); i++) {
       OriginInfo* originInfo = originInfos[i];
 
-      if (!info->collection.ContainsOrigin(originInfo->mOrigin)) {
+      if (originInfo->IsTreatedAsPersistent()) {
+        continue;
+      }
+
+      if (!info->persistentCollection.ContainsOrigin(originInfo->mOrigin)) {
+        NS_ASSERTION(!originInfo->mQuotaObjects.Count(),
+                     "Inactive origin shouldn't have open files!");
+        info->origins.AppendElement(originInfo);
+      }
+    }
+  }
+
+  groupInfo = aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
+  if (groupInfo) {
+    nsTArray<nsRefPtr<OriginInfo> >& originInfos = groupInfo->mOriginInfos;
+
+    for (uint32_t i = 0; i < originInfos.Length(); i++) {
+      OriginInfo* originInfo = originInfos[i];
+
+      if (originInfo->IsTreatedAsPersistent()) {
+        continue;
+      }
+
+      if (!info->temporaryCollection.ContainsOrigin(originInfo->mOrigin)) {
         NS_ASSERTION(!originInfo->mQuotaObjects.Count(),
                      "Inactive origin shouldn't have open files!");
         info->origins.AppendElement(originInfo);
       }
     }
   }
 
   return PL_DHASH_NEXT;
@@ -3168,50 +3621,80 @@ QuotaManager::GetInactiveTemporaryStorag
 
 uint64_t
 QuotaManager::CollectOriginsForEviction(uint64_t aMinSizeToBeFreed,
                                         nsTArray<OriginInfo*>& aOriginInfos)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   // Collect active origins first.
-  OriginCollection originCollection;
+  OriginCollection persistentOriginCollection;
+  OriginCollection temporaryOriginCollection;
 
   // Add patterns and origins that have running or pending synchronized ops.
   // (add patterns first to reduce redundancy in the origin collection).
   uint32_t index;
   for (index = 0; index < mSynchronizedOps.Length(); index++) {
     nsAutoPtr<SynchronizedOp>& op = mSynchronizedOps[index];
-    if (op->mPersistenceType.IsNull() ||
-        op->mPersistenceType.Value() == PERSISTENCE_TYPE_TEMPORARY) {
-      if (op->mOriginOrPattern.IsPattern() &&
-          !originCollection.ContainsPattern(op->mOriginOrPattern)) {
-        originCollection.AddPattern(op->mOriginOrPattern);
-      }
+
+    const OriginOrPatternString& originOrPattern = op->mOriginOrPattern;
+
+    if (!originOrPattern.IsPattern()) {
+      continue;
+    }
+
+    Nullable<PersistenceType>& persistenceType = op->mPersistenceType;
+
+    if (persistenceType.IsNull()) {
+      persistentOriginCollection.AddPattern(originOrPattern);
+      temporaryOriginCollection.AddPattern(originOrPattern);
+    } else if (persistenceType.Value() == PERSISTENCE_TYPE_PERSISTENT) {
+      persistentOriginCollection.AddPattern(originOrPattern);
+    } else {
+      MOZ_ASSERT(persistenceType.Value() == PERSISTENCE_TYPE_TEMPORARY);
+      temporaryOriginCollection.AddPattern(originOrPattern);
     }
   }
 
   for (index = 0; index < mSynchronizedOps.Length(); index++) {
     nsAutoPtr<SynchronizedOp>& op = mSynchronizedOps[index];
-    if (op->mPersistenceType.IsNull() ||
-        op->mPersistenceType.Value() == PERSISTENCE_TYPE_TEMPORARY) {
-      if (op->mOriginOrPattern.IsOrigin() &&
-          !originCollection.ContainsOrigin(op->mOriginOrPattern)) {
-        originCollection.AddOrigin(op->mOriginOrPattern);
-      }
+
+    const OriginOrPatternString& originOrPattern = op->mOriginOrPattern;
+
+    if (!originOrPattern.IsOrigin()) {
+      continue;
+    }
+
+    Nullable<PersistenceType>& persistenceType = op->mPersistenceType;
+
+    if (persistenceType.IsNull()) {
+      persistentOriginCollection.AddOrigin(originOrPattern);
+      temporaryOriginCollection.AddOrigin(originOrPattern);
+    } else if (persistenceType.Value() == PERSISTENCE_TYPE_PERSISTENT) {
+      persistentOriginCollection.AddOrigin(originOrPattern);
+    } else {
+      MOZ_ASSERT(persistenceType.Value() == PERSISTENCE_TYPE_TEMPORARY);
+      temporaryOriginCollection.AddOrigin(originOrPattern);
     }
   }
 
+  // Add origins that have live persistent storages.
+  mPersistentLiveStorageTable.EnumerateRead(AddLiveStorageOrigins,
+                                            &persistentOriginCollection);
+
   // Add origins that have live temporary storages.
-  mLiveStorages.EnumerateRead(AddTemporaryStorageOrigins, &originCollection);
+  mTemporaryLiveStorageTable.EnumerateRead(AddLiveStorageOrigins,
+                                           &temporaryOriginCollection);
 
   // Enumerate inactive origins. This must be protected by the mutex.
   nsTArray<OriginInfo*> inactiveOrigins;
   {
-    InactiveOriginsInfo info(originCollection, inactiveOrigins);
+    InactiveOriginsInfo info(persistentOriginCollection,
+                             temporaryOriginCollection,
+                             inactiveOrigins);
     MutexAutoLock lock(mQuotaMutex);
     mGroupInfoPairs.EnumerateRead(GetInactiveTemporaryStorageOrigins, &info);
   }
 
   // We now have a list of all inactive origins. So it's safe to sort the list
   // and calculate available size without holding the lock.
 
   // Sort by the origin access time.
@@ -3229,72 +3712,78 @@ QuotaManager::CollectOriginsForEviction(
     sizeToBeFreed += inactiveOrigins[index]->mUsage;
   }
 
   if (sizeToBeFreed >= aMinSizeToBeFreed) {
     // Success, add synchronized ops for these origins, so any other
     // operations for them will be delayed (until origin eviction is finalized).
 
     for(index = 0; index < inactiveOrigins.Length(); index++) {
+      OriginInfo* inactiveOrigin = inactiveOrigins[index];
+
       OriginOrPatternString oops =
-        OriginOrPatternString::FromOrigin(inactiveOrigins[index]->mOrigin);
-
-      AddSynchronizedOp(oops,
-                        Nullable<PersistenceType>(PERSISTENCE_TYPE_TEMPORARY));
+        OriginOrPatternString::FromOrigin(inactiveOrigin->mOrigin);
+
+      Nullable<PersistenceType> persistenceType =
+        Nullable<PersistenceType>(inactiveOrigin->mGroupInfo->mPersistenceType);
+
+      AddSynchronizedOp(oops, persistenceType);
     }
 
     inactiveOrigins.SwapElements(aOriginInfos);
     return sizeToBeFreed;
   }
 
   return 0;
 }
 
 void
-QuotaManager::DeleteTemporaryFilesForOrigin(const nsACString& aOrigin)
+QuotaManager::DeleteFilesForOrigin(PersistenceType aPersistenceType,
+                                   const nsACString& aOrigin)
 {
   nsCOMPtr<nsIFile> directory;
-  nsresult rv = GetDirectoryForOrigin(PERSISTENCE_TYPE_TEMPORARY, aOrigin,
+  nsresult rv = GetDirectoryForOrigin(aPersistenceType, aOrigin,
                                       getter_AddRefs(directory));
   NS_ENSURE_SUCCESS_VOID(rv);
 
   rv = directory->Remove(true);
   if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
       rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
     // This should never fail if we've closed all storage connections
     // correctly...
     NS_ERROR("Failed to remove directory!");
   }
 }
 
 void
-QuotaManager::FinalizeOriginEviction(nsTArray<nsCString>& aOrigins)
+QuotaManager::FinalizeOriginEviction(nsTArray<OriginParams>& aOrigins)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
   nsRefPtr<FinalizeOriginEvictionRunnable> runnable =
     new FinalizeOriginEvictionRunnable(aOrigins);
 
   nsresult rv = IsOnIOThread() ? runnable->RunImmediately()
                                : runnable->Dispatch();
   NS_ENSURE_SUCCESS_VOID(rv);
 }
 
 void
-QuotaManager::SaveOriginAccessTime(const nsACString& aOrigin,
+QuotaManager::SaveOriginAccessTime(PersistenceType aPersistenceType,
+                                   const nsACString& aOrigin,
                                    int64_t aTimestamp)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   if (QuotaManager::IsShuttingDown()) {
     return;
   }
 
   nsRefPtr<SaveOriginAccessTimeRunnable> runnable =
-    new SaveOriginAccessTimeRunnable(aOrigin, aTimestamp);
+    new SaveOriginAccessTimeRunnable(aPersistenceType, aOrigin, aTimestamp);
 
   if (NS_FAILED(mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL))) {
     NS_WARNING("Failed to dispatch runnable!");
   }
 }
 
 void
 QuotaManager::GetOriginPatternString(uint32_t aAppId,
@@ -3334,16 +3823,32 @@ QuotaManager::GetOriginPatternString(uin
     NS_ASSERTION(PatternMatchesOrigin(pattern, aOrigin),
                  "Origin doesn't match parameters!");
   }
 #endif
 
   _retval = aOrigin;
 }
 
+auto
+QuotaManager::GetLiveStorageTable(PersistenceType aPersistenceType)
+  -> LiveStorageTable&
+{
+  switch (aPersistenceType) {
+    case PERSISTENCE_TYPE_PERSISTENT:
+      return mPersistentLiveStorageTable;
+    case PERSISTENCE_TYPE_TEMPORARY:
+      return mTemporaryLiveStorageTable;
+
+    case PERSISTENCE_TYPE_INVALID:
+    default:
+      MOZ_CRASH("Bad persistence type value!");
+  }
+}
+
 SynchronizedOp::SynchronizedOp(const OriginOrPatternString& aOriginOrPattern,
                                Nullable<PersistenceType> aPersistenceType,
                                const nsACString& aId)
 : mOriginOrPattern(aOriginOrPattern), mPersistenceType(aPersistenceType),
   mId(aId)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   MOZ_COUNT_CTOR(SynchronizedOp);
@@ -3544,29 +4049,31 @@ OriginClearRunnable::DeleteFiles(QuotaMa
   while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) {
     nsCOMPtr<nsISupports> entry;
     rv = entries->GetNext(getter_AddRefs(entry));
     NS_ENSURE_SUCCESS_VOID(rv);
 
     nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
     NS_ASSERTION(file, "Don't know what this is!");
 
+    nsString leafName;
+    rv = file->GetLeafName(leafName);
+    NS_ENSURE_SUCCESS_VOID(rv);
+
     bool isDirectory;
     rv = file->IsDirectory(&isDirectory);
     NS_ENSURE_SUCCESS_VOID(rv);
 
     if (!isDirectory) {
-      NS_WARNING("Something in the IndexedDB directory that doesn't belong!");
+      if (!leafName.EqualsLiteral(DSSTORE_FILE_NAME)) {
+        NS_WARNING("Something in the IndexedDB directory that doesn't belong!");
+      }
       continue;
     }
 
-    nsString leafName;
-    rv = file->GetLeafName(leafName);
-    NS_ENSURE_SUCCESS_VOID(rv);
-
     // Skip storages for other apps.
     if (!PatternMatchesOrigin(originSanitized,
                               NS_ConvertUTF16toUTF8(leafName))) {
       continue;
     }
 
     for (uint32_t index = 0; index < 10; index++) {
       // We can't guarantee that this will always succeed on Windows...
@@ -3659,31 +4166,36 @@ OriginClearRunnable::Run()
   }
 
   NS_NOTREACHED("Should never get here!");
   return NS_ERROR_UNEXPECTED;
 }
 
 AsyncUsageRunnable::AsyncUsageRunnable(uint32_t aAppId,
                                        bool aInMozBrowserOnly,
-                                       const nsACString& aGroup,
+                                       const nsACString& aPersistentGroup,
+                                       const nsACString& aTemporaryGroup,
                                        const OriginOrPatternString& aOrigin,
+                                       bool aIsApp,
                                        nsIURI* aURI,
                                        nsIUsageCallback* aCallback)
 : mURI(aURI),
   mCallback(aCallback),
   mAppId(aAppId),
-  mGroup(aGroup),
+  mPersistentGroup(aPersistentGroup),
+  mTemporaryGroup(aTemporaryGroup),
   mOrigin(aOrigin),
   mCallbackState(Pending),
-  mInMozBrowserOnly(aInMozBrowserOnly)
+  mInMozBrowserOnly(aInMozBrowserOnly),
+  mIsApp(aIsApp)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(aURI, "Null pointer!");
-  NS_ASSERTION(!aGroup.IsEmpty(), "Empty group!");
+  NS_ASSERTION(!aPersistentGroup.IsEmpty(), "Empty group!");
+  NS_ASSERTION(!aTemporaryGroup.IsEmpty(), "Empty group!");
   NS_ASSERTION(aOrigin.IsOrigin(), "Expect origin only here!");
   NS_ASSERTION(!aOrigin.IsEmpty(), "Empty origin!");
   NS_ASSERTION(aCallback, "Null pointer!");
 }
 
 nsresult
 AsyncUsageRunnable::TakeShortcut()
 {
@@ -3788,27 +4300,33 @@ AsyncUsageRunnable::AddToUsage(QuotaMana
   rv = directory->Exists(&exists);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // If the directory exists then enumerate all the files inside, adding up
   // the sizes to get the final usage statistic.
   if (exists && !mCanceled) {
     bool initialized;
 
-    if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
+    if (QuotaManager::IsTreatedAsPersistent(aPersistenceType, mOrigin,
+                                            mIsApp)) {
       initialized = aQuotaManager->mInitializedOrigins.Contains(mOrigin);
-
+    } else {
+      initialized = aQuotaManager->mTemporaryStorageInitialized;
+    }
+
+    nsCString* group;
+    if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
       if (!initialized) {
         rv = MaybeUpgradeOriginDirectory(directory);
         NS_ENSURE_SUCCESS(rv, rv);
       }
-    }
-    else {
-      NS_ASSERTION(aPersistenceType == PERSISTENCE_TYPE_TEMPORARY, "Huh?");
-      initialized = aQuotaManager->mTemporaryStorageInitialized;
+
+      group = &mPersistentGroup;
+    } else {
+      group = &mTemporaryGroup;
     }
 
     nsCOMPtr<nsISimpleEnumerator> entries;
     rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
     NS_ENSURE_SUCCESS(rv, rv);
 
     bool hasMore;
     while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) &&
@@ -3848,20 +4366,20 @@ AsyncUsageRunnable::AddToUsage(QuotaMana
           return NS_ERROR_UNEXPECTED;
         }
         continue;
       }
 
       nsRefPtr<Client>& client = aQuotaManager->mClients[clientType];
 
       if (initialized) {
-        rv = client->GetUsageForOrigin(aPersistenceType, mGroup, mOrigin, this);
+        rv = client->GetUsageForOrigin(aPersistenceType, *group, mOrigin, this);
       }
       else {
-        rv = client->InitOrigin(aPersistenceType, mGroup, mOrigin, this);
+        rv = client->InitOrigin(aPersistenceType, *group, mOrigin, this);
       }
       NS_ENSURE_SUCCESS(rv, rv);
     }
   }
 
   return NS_OK;
 }
 
@@ -4052,37 +4570,41 @@ FinalizeOriginEvictionRunnable::Run()
     }
 
     case IO: {
       AssertIsOnIOThread();
 
       AdvanceState();
 
       for (uint32_t index = 0; index < mOrigins.Length(); index++) {
+        const OriginParams& origin = mOrigins[index];
+
         quotaManager->OriginClearCompleted(
-                            PERSISTENCE_TYPE_TEMPORARY,
-                            OriginOrPatternString::FromOrigin(mOrigins[index]));
+                             origin.mPersistenceType,
+                             OriginOrPatternString::FromOrigin(origin.mOrigin));
       }
 
       if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) {
         NS_WARNING("Failed to dispatch to main thread!");
         return NS_ERROR_FAILURE;
       }
 
       return NS_OK;
     }
 
     case Complete: {
       NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
       for (uint32_t index = 0; index < mOrigins.Length(); index++) {
+        const OriginParams& origin = mOrigins[index];
+
         quotaManager->AllowNextSynchronizedOp(
-                          OriginOrPatternString::FromOrigin(mOrigins[index]),
-                          Nullable<PersistenceType>(PERSISTENCE_TYPE_TEMPORARY),
-                          EmptyCString());
+                             OriginOrPatternString::FromOrigin(origin.mOrigin),
+                             Nullable<PersistenceType>(origin.mPersistenceType),
+                             EmptyCString());
       }
 
       return NS_OK;
     }
 
     default:
       NS_ERROR("Unknown state value!");
       return NS_ERROR_UNEXPECTED;
@@ -4153,22 +4675,23 @@ SaveOriginAccessTimeRunnable::Run()
 {
   AssertIsOnIOThread();
 
   QuotaManager* quotaManager = QuotaManager::Get();
   NS_ASSERTION(quotaManager, "This should never fail!");
 
   nsCOMPtr<nsIFile> directory;
   nsresult rv =
-    quotaManager->GetDirectoryForOrigin(PERSISTENCE_TYPE_TEMPORARY, mOrigin,
+    quotaManager->GetDirectoryForOrigin(mPersistenceType, mOrigin,
                                         getter_AddRefs(directory));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIBinaryOutputStream> stream;
-  rv = GetDirectoryMetadataStream(directory, true, getter_AddRefs(stream));
+  rv = GetDirectoryMetadataOutputStream(directory, true,
+                                        getter_AddRefs(stream));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // The origin directory may not exist anymore.
   if (stream) {
     rv = stream->Write64(mTimestamp);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
--- a/dom/quota/QuotaManager.h
+++ b/dom/quota/QuotaManager.h
@@ -50,16 +50,28 @@ class GroupInfo;
 class GroupInfoPair;
 class OriginClearRunnable;
 class OriginInfo;
 class OriginOrPatternString;
 class QuotaObject;
 class ResetOrClearRunnable;
 struct SynchronizedOp;
 
+struct OriginParams
+{
+  OriginParams(PersistenceType aPersistenceType,
+               const nsACString& aOrigin)
+  : mOrigin(aOrigin)
+  , mPersistenceType(aPersistenceType)
+  { }
+
+  nsCString mOrigin;
+  PersistenceType mPersistenceType;
+};
+
 class QuotaManager MOZ_FINAL : public nsIQuotaManager,
                                public nsIObserver
 {
   friend class AsyncUsageRunnable;
   friend class CollectOriginsHelper;
   friend class FinalizeOriginEvictionRunnable;
   friend class GroupInfo;
   friend class OriginClearRunnable;
@@ -74,16 +86,19 @@ class QuotaManager MOZ_FINAL : public ns
     MozBrowser = 0,
     NotMozBrowser,
     IgnoreMozBrowser
   };
 
   typedef void
   (*WaitingOnStoragesCallback)(nsTArray<nsCOMPtr<nsIOfflineStorage> >&, void*);
 
+  typedef nsClassHashtable<nsCStringHashKey,
+                           nsTArray<nsIOfflineStorage*>> LiveStorageTable;
+
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIQUOTAMANAGER
   NS_DECL_NSIOBSERVER
 
   // Returns a non-owning reference.
   static QuotaManager*
   GetOrCreate();
@@ -98,16 +113,17 @@ public:
 
   // Returns true if we've begun the shutdown process.
   static bool IsShuttingDown();
 
   void
   InitQuotaForOrigin(PersistenceType aPersistenceType,
                      const nsACString& aGroup,
                      const nsACString& aOrigin,
+                     bool aIsApp,
                      uint64_t aLimitBytes,
                      uint64_t aUsageBytes,
                      int64_t aAccessTime);
 
   void
   DecreaseUsageForOrigin(PersistenceType aPersistenceType,
                          const nsACString& aGroup,
                          const nsACString& aOrigin,
@@ -117,17 +133,17 @@ public:
   UpdateOriginAccessTime(PersistenceType aPersistenceType,
                          const nsACString& aGroup,
                          const nsACString& aOrigin);
 
   void
   RemoveQuota();
 
   void
-  RemoveQuotaForPersistenceType(PersistenceType);
+  RemoveQuotaForTemporaryStorage();
 
   void
   RemoveQuotaForOrigin(PersistenceType aPersistenceType,
                        const nsACString& aGroup,
                        const nsACString& aOrigin)
   {
     MutexAutoLock lock(mQuotaMutex);
     LockedRemoveQuotaForOrigin(aPersistenceType, aGroup, aOrigin);
@@ -248,17 +264,18 @@ public:
   GetDirectoryForOrigin(PersistenceType aPersistenceType,
                         const nsACString& aASCIIOrigin,
                         nsIFile** aDirectory) const;
 
   nsresult
   EnsureOriginIsInitialized(PersistenceType aPersistenceType,
                             const nsACString& aGroup,
                             const nsACString& aOrigin,
-                            bool aTrackQuota,
+                            bool aIsApp,
+                            bool aHasUnlimStoragePerm,
                             nsIFile** aDirectory);
 
   void
   OriginClearCompleted(PersistenceType aPersistenceType,
                        const OriginOrPatternString& aOriginOrPattern);
 
   void
   ResetOrClearCompleted();
@@ -275,16 +292,22 @@ public:
     NS_ASSERTION(mIOThread, "This should never be null!");
     return mIOThread;
   }
 
   already_AddRefed<Client>
   GetClient(Client::Type aClientType);
 
   const nsString&
+  GetStoragePath() const
+  {
+    return mStoragePath;
+  }
+
+  const nsString&
   GetStoragePath(PersistenceType aPersistenceType) const
   {
     if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
       return mPersistentStoragePath;
     }
 
     NS_ASSERTION(aPersistenceType == PERSISTENCE_TYPE_TEMPORARY, "Huh?");
 
@@ -303,40 +326,66 @@ public:
                Client::Type aClientType,
                const nsAString& aName,
                nsACString& aDatabaseId);
 
   static nsresult
   GetInfoFromURI(nsIURI* aURI,
                  uint32_t aAppId,
                  bool aInMozBrowser,
+                 PersistenceType aPersistenceType,
                  nsACString* aGroup,
                  nsACString* aOrigin,
                  StoragePrivilege* aPrivilege,
-                 PersistenceType* aDefaultPersistenceType);
+                 bool* aIsApp,
+                 bool* aHasUnlimStoragePerm);
 
   static nsresult
   GetInfoFromPrincipal(nsIPrincipal* aPrincipal,
+                       PersistenceType aPersistenceType,
                        nsACString* aGroup,
                        nsACString* aOrigin,
                        StoragePrivilege* aPrivilege,
-                       PersistenceType* aDefaultPersistenceType);
+                       bool* aIsApp,
+                       bool* aHasUnlimStoragePerm);
 
   static nsresult
   GetInfoFromWindow(nsPIDOMWindow* aWindow,
+                    PersistenceType aPersistenceType,
                     nsACString* aGroup,
                     nsACString* aOrigin,
                     StoragePrivilege* aPrivilege,
-                    PersistenceType* aDefaultPersistenceType);
+                    bool* aIsApp,
+                    bool* aHasUnlimStoragePerm);
 
   static void
   GetInfoForChrome(nsACString* aGroup,
                    nsACString* aOrigin,
                    StoragePrivilege* aPrivilege,
-                   PersistenceType* aDefaultPersistenceType);
+                   bool* aIsApp,
+                   bool* aHasUnlimStoragePerm);
+
+  static bool
+  IsTreatedAsPersistent(PersistenceType aPersistenceType,
+                        const nsACString& aOrigin,
+                        bool aIsApp);
+
+  static bool
+  IsTreatedAsTemporary(PersistenceType aPersistenceType,
+                       const nsACString& aOrigin,
+                       bool aIsApp)
+  {
+    return !IsTreatedAsPersistent(aPersistenceType, aOrigin, aIsApp);
+  }
+
+  static bool
+  IsQuotaEnforced(PersistenceType aPersistenceType,
+                  const nsACString& aOrigin,
+                  bool aIsApp,
+                  bool aHasUnlimStoragePerm);
 
   static void
   ChromeOrigin(nsACString& aOrigin);
 
   static void
   GetOriginPatternString(uint32_t aAppId, bool aBrowserOnly,
                          const nsACString& aOrigin, nsAutoCString& _retval)
   {
@@ -402,67 +451,77 @@ private:
   FindSynchronizedOp(const nsACString& aPattern,
                      Nullable<PersistenceType> aPersistenceType,
                      const nsACString& aId);
 
   nsresult
   MaybeUpgradeIndexedDBDirectory();
 
   nsresult
+  InitializeRepository(PersistenceType aPersistenceType);
+
+  nsresult
   InitializeOrigin(PersistenceType aPersistenceType,
                    const nsACString& aGroup,
                    const nsACString& aOrigin,
-                   bool aTrackQuota,
+                   bool aIsApp,
+                   bool aHasUnlimStoragePerm,
                    int64_t aAccessTime,
                    nsIFile* aDirectory);
 
   nsresult
   ClearStoragesForApp(uint32_t aAppId, bool aBrowserOnly);
 
   void
   CheckTemporaryStorageLimits();
 
   // Collect inactive and the least recently used origins.
   uint64_t
   CollectOriginsForEviction(uint64_t aMinSizeToBeFreed,
                             nsTArray<OriginInfo*>& aOriginInfos);
 
   void
-  DeleteTemporaryFilesForOrigin(const nsACString& aOrigin);
+  DeleteFilesForOrigin(PersistenceType aPersistenceType,
+                       const nsACString& aOrigin);
 
   void
-  FinalizeOriginEviction(nsTArray<nsCString>& aOrigins);
+  FinalizeOriginEviction(nsTArray<OriginParams>& aOrigins);
 
   void
-  SaveOriginAccessTime(const nsACString& aOrigin, int64_t aTimestamp);
+  SaveOriginAccessTime(PersistenceType aPersistenceType,
+                       const nsACString& aOrigin,
+                       int64_t aTimestamp);
 
   void
   ReleaseIOThreadObjects()
   {
     AssertIsOnIOThread();
 
     for (uint32_t index = 0; index < Client::TYPE_MAX; index++) {
       mClients[index]->ReleaseIOThreadObjects();
     }
   }
 
   template <class OwnerClass>
   void
   AbortCloseStoragesFor(OwnerClass* aOwnerClass);
 
+  LiveStorageTable&
+  GetLiveStorageTable(PersistenceType aPersistenceType);
+
   static void
   GetOriginPatternString(uint32_t aAppId,
                          MozBrowserPatternFlag aBrowserFlag,
                          const nsACString& aOrigin,
                          nsAutoCString& _retval);
 
   static PLDHashOperator
-  RemoveQuotaForPersistenceTypeCallback(const nsACString& aKey,
-                                        nsAutoPtr<GroupInfoPair>& aValue,
-                                        void* aUserArg);
+  RemoveQuotaForTemporaryStorageCallback(const nsACString& aKey,
+                                         nsAutoPtr<GroupInfoPair>& aValue,
+                                         void* aUserArg);
 
   static PLDHashOperator
   RemoveQuotaCallback(const nsACString& aKey,
                       nsAutoPtr<GroupInfoPair>& aValue,
                       void* aUserArg);
 
   static PLDHashOperator
   RemoveQuotaForPatternCallback(const nsACString& aKey,
@@ -475,19 +534,19 @@ private:
                                 void* aUserArg);
 
   static PLDHashOperator
   GetAllTemporaryStorageOrigins(const nsACString& aKey,
                                 GroupInfoPair* aValue,
                                 void* aUserArg);
 
   static PLDHashOperator
-  AddTemporaryStorageOrigins(const nsACString& aKey,
-                             ArrayCluster<nsIOfflineStorage*>* aValue,
-                             void* aUserArg);
+  AddLiveStorageOrigins(const nsACString& aKey,
+                        nsTArray<nsIOfflineStorage*>* aValue,
+                        void* aUserArg);
 
   static PLDHashOperator
   GetInactiveTemporaryStorageOrigins(const nsACString& aKey,
                                      GroupInfoPair* aValue,
                                      void* aUserArg);
 
   // TLS storage index for the current thread's window.
   unsigned int mCurrentWindowIndex;
@@ -499,32 +558,36 @@ private:
   // A map of Windows to the corresponding quota helper.
   nsRefPtrHashtable<nsPtrHashKey<nsPIDOMWindow>,
                     CheckQuotaHelper> mCheckQuotaHelpers;
 
   // Maintains a list of live storages per origin.
   nsClassHashtable<nsCStringHashKey,
                    ArrayCluster<nsIOfflineStorage*> > mLiveStorages;
 
+  LiveStorageTable mPersistentLiveStorageTable;
+  LiveStorageTable mTemporaryLiveStorageTable;
+
   // Maintains a list of synchronized operatons that are in progress or queued.
   nsAutoTArray<nsAutoPtr<SynchronizedOp>, 5> mSynchronizedOps;
 
   // Thread on which IO is performed.
   nsCOMPtr<nsIThread> mIOThread;
 
   // A timer that gets activated at shutdown to ensure we close all storages.
   nsCOMPtr<nsITimer> mShutdownTimer;
 
   // A list of all successfully initialized origins. This list isn't protected
   // by any mutex but it is only ever touched on the IO thread.
   nsTArray<nsCString> mInitializedOrigins;
 
   nsAutoTArray<nsRefPtr<Client>, Client::TYPE_MAX> mClients;
 
   nsString mIndexedDBPath;
+  nsString mStoragePath;
   nsString mPersistentStoragePath;
   nsString mTemporaryStoragePath;
 
   uint64_t mTemporaryStorageLimit;
   uint64_t mTemporaryStorageUsage;
   bool mTemporaryStorageInitialized;
 
   bool mStorageAreaInitialized;
--- a/dom/quota/QuotaObject.cpp
+++ b/dom/quota/QuotaObject.cpp
@@ -70,27 +70,27 @@ QuotaObject::UpdateSize(int64_t aSize)
   MutexAutoLock lock(quotaManager->mQuotaMutex);
 
   if (!mOriginInfo) {
     return;
   }
 
   GroupInfo* groupInfo = mOriginInfo->mGroupInfo;
 
-  if (groupInfo->IsForTemporaryStorage()) {
+  if (mOriginInfo->IsTreatedAsTemporary()) {
     quotaManager->mTemporaryStorageUsage -= mSize;
   }
   groupInfo->mUsage -= mSize;
   mOriginInfo->mUsage -= mSize;
 
   mSize = aSize;
 
   mOriginInfo->mUsage += mSize;
   groupInfo->mUsage += mSize;
-  if (groupInfo->IsForTemporaryStorage()) {
+  if (mOriginInfo->IsTreatedAsTemporary()) {
     quotaManager->mTemporaryStorageUsage += mSize;
   }
 }
 
 bool
 QuotaObject::MaybeAllocateMoreSpace(int64_t aOffset, int32_t aCount)
 {
   int64_t end = aOffset + aCount;
@@ -101,17 +101,17 @@ QuotaObject::MaybeAllocateMoreSpace(int6
   MutexAutoLock lock(quotaManager->mQuotaMutex);
 
   if (mSize >= end || !mOriginInfo) {
     return true;
   }
 
   GroupInfo* groupInfo = mOriginInfo->mGroupInfo;
 
-  if (groupInfo->IsForPersistentStorage()) {
+  if (mOriginInfo->IsTreatedAsPersistent()) {
     uint64_t newUsage = mOriginInfo->mUsage - mSize + end;
 
     if (newUsage > mOriginInfo->mLimit) {
       // This will block the thread, but it will also drop the mutex while
       // waiting. The mutex will be reacquired again when the waiting is
       // finished.
       if (!quotaManager->LockedQuotaIsLifted()) {
         return false;
@@ -130,17 +130,17 @@ QuotaObject::MaybeAllocateMoreSpace(int6
 
       nsCString group = mOriginInfo->mGroupInfo->mGroup;
       nsCString origin = mOriginInfo->mOrigin;
 
       mOriginInfo->LockedClearOriginInfos();
       NS_ASSERTION(!mOriginInfo,
                    "Should have cleared in LockedClearOriginInfos!");
 
-      quotaManager->LockedRemoveQuotaForOrigin(PERSISTENCE_TYPE_PERSISTENT,
+      quotaManager->LockedRemoveQuotaForOrigin(groupInfo->mPersistenceType,
                                                group, origin);
 
       // Some other thread could increase the size without blocking (increasing
       // the origin usage without hitting the limit), but no more than this one.
       NS_ASSERTION(mSize < end, "This shouldn't happen!");
 
       mSize = end;
 
@@ -151,31 +151,37 @@ QuotaObject::MaybeAllocateMoreSpace(int6
 
     groupInfo->mUsage = groupInfo->mUsage - mSize + end;
 
     mSize = end;
 
     return true;
   }
 
-  NS_ASSERTION(groupInfo->mPersistenceType == PERSISTENCE_TYPE_TEMPORARY,
-               "Huh?");
+  nsRefPtr<GroupInfo> complementaryGroupInfo =
+    groupInfo->mGroupInfoPair->LockedGetGroupInfo(
+      ComplementaryPersistenceType(groupInfo->mPersistenceType));
 
   uint64_t delta = end - mSize;
 
   uint64_t newUsage = mOriginInfo->mUsage + delta;
 
   // Temporary storage has no limit for origin usage (there's a group and the
   // global limit though).
 
   uint64_t newGroupUsage = groupInfo->mUsage + delta;
 
+  uint64_t groupUsage = groupInfo->LockedGetTemporaryUsage();
+  if (complementaryGroupInfo) {
+    groupUsage += complementaryGroupInfo->LockedGetTemporaryUsage();
+  }
+
   // Temporary storage has a hard limit for group usage (20 % of the global
   // limit).
-  if (newGroupUsage > quotaManager->GetGroupLimit()) {
+  if (groupUsage + delta > quotaManager->GetGroupLimit()) {
     return false;
   }
 
   uint64_t newTemporaryStorageUsage = quotaManager->mTemporaryStorageUsage +
                                       delta;
 
   if (newTemporaryStorageUsage > quotaManager->mTemporaryStorageLimit) {
     // This will block the thread without holding the lock while waitting.
@@ -189,52 +195,62 @@ QuotaObject::MaybeAllocateMoreSpace(int6
     }
 
     NS_ASSERTION(sizeToBeFreed >= delta, "Huh?");
 
     {
       MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex);
 
       for (uint32_t i = 0; i < originInfos.Length(); i++) {
-        quotaManager->DeleteTemporaryFilesForOrigin(originInfos[i]->mOrigin);
+        OriginInfo* originInfo = originInfos[i];
+
+        quotaManager->DeleteFilesForOrigin(
+                                       originInfo->mGroupInfo->mPersistenceType,
+                                       originInfo->mOrigin);
       }
     }
 
     // Relocked.
 
     NS_ASSERTION(mOriginInfo, "How come?!");
 
-    nsTArray<nsCString> origins;
+    nsTArray<OriginParams> origins;
     for (uint32_t i = 0; i < originInfos.Length(); i++) {
       OriginInfo* originInfo = originInfos[i];
 
       NS_ASSERTION(originInfo != mOriginInfo, "Deleted itself!");
 
+      PersistenceType persistenceType =
+        originInfo->mGroupInfo->mPersistenceType;
       nsCString group = originInfo->mGroupInfo->mGroup;
       nsCString origin = originInfo->mOrigin;
-      quotaManager->LockedRemoveQuotaForOrigin(PERSISTENCE_TYPE_TEMPORARY,
-                                               group, origin);
+      quotaManager->LockedRemoveQuotaForOrigin(persistenceType, group, origin);
 
 #ifdef DEBUG
       originInfos[i] = nullptr;
 #endif
 
-      origins.AppendElement(origin);
+      origins.AppendElement(OriginParams(persistenceType, origin));
     }
 
     // We unlocked and relocked several times so we need to recompute all the
     // essential variables and recheck the group limit.
 
     delta = end - mSize;
 
     newUsage = mOriginInfo->mUsage + delta;
 
     newGroupUsage = groupInfo->mUsage + delta;
 
-    if (newGroupUsage > quotaManager->GetGroupLimit()) {
+    groupUsage = groupInfo->LockedGetTemporaryUsage();
+    if (complementaryGroupInfo) {
+      groupUsage += complementaryGroupInfo->LockedGetTemporaryUsage();
+    }
+
+    if (groupUsage + delta > quotaManager->GetGroupLimit()) {
       // Unfortunately some other thread increased the group usage in the
       // meantime and we are not below the group limit anymore.
 
       // However, the origin eviction must be finalized in this case too.
       MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex);
 
       quotaManager->FinalizeOriginEviction(origins);
 
@@ -271,26 +287,40 @@ QuotaObject::MaybeAllocateMoreSpace(int6
   groupInfo->mUsage = newGroupUsage;
   quotaManager->mTemporaryStorageUsage = newTemporaryStorageUsage;
 
   mSize = end;
 
   return true;
 }
 
+bool
+OriginInfo::IsTreatedAsPersistent() const
+{
+  return QuotaManager::IsTreatedAsPersistent(mGroupInfo->mPersistenceType,
+                                             mOrigin, mIsApp);
+}
+
+bool
+OriginInfo::IsTreatedAsTemporary() const
+{
+  return QuotaManager::IsTreatedAsTemporary(mGroupInfo->mPersistenceType,
+                                            mOrigin, mIsApp);
+}
+
 void
 OriginInfo::LockedDecreaseUsage(int64_t aSize)
 {
   AssertCurrentThreadOwnsQuotaMutex();
 
   mUsage -= aSize;
 
   mGroupInfo->mUsage -= aSize;
 
-  if (mGroupInfo->IsForTemporaryStorage()) {
+  if (IsTreatedAsTemporary()) {
     QuotaManager* quotaManager = QuotaManager::Get();
     NS_ASSERTION(quotaManager, "Shouldn't be null!");
 
     quotaManager->mTemporaryStorageUsage -= aSize;
   }
 }
 
 // static
@@ -330,34 +360,34 @@ GroupInfo::LockedAddOriginInfo(OriginInf
   AssertCurrentThreadOwnsQuotaMutex();
 
   NS_ASSERTION(!mOriginInfos.Contains(aOriginInfo),
                "Replacing an existing entry!");
   mOriginInfos.AppendElement(aOriginInfo);
 
   mUsage += aOriginInfo->mUsage;
 
-  if (IsForTemporaryStorage()) {
+  if (aOriginInfo->IsTreatedAsTemporary()) {
     QuotaManager* quotaManager = QuotaManager::Get();
     NS_ASSERTION(quotaManager, "Shouldn't be null!");
 
     quotaManager->mTemporaryStorageUsage += aOriginInfo->mUsage;
   }
 }
 
 void
 GroupInfo::LockedRemoveOriginInfo(const nsACString& aOrigin)
 {
   AssertCurrentThreadOwnsQuotaMutex();
 
   for (uint32_t index = 0; index < mOriginInfos.Length(); index++) {
     if (mOriginInfos[index]->mOrigin == aOrigin) {
       mUsage -= mOriginInfos[index]->mUsage;
 
-      if (IsForTemporaryStorage()) {
+      if (mOriginInfos[index]->IsTreatedAsTemporary()) {
         QuotaManager* quotaManager = QuotaManager::Get();
         NS_ASSERTION(quotaManager, "Shouldn't be null!");
 
         quotaManager->mTemporaryStorageUsage -= mOriginInfos[index]->mUsage;
       }
 
       mOriginInfos.RemoveElementAt(index);
 
@@ -367,50 +397,108 @@ GroupInfo::LockedRemoveOriginInfo(const 
 }
 
 void
 GroupInfo::LockedRemoveOriginInfos()
 {
   AssertCurrentThreadOwnsQuotaMutex();
 
   for (uint32_t index = mOriginInfos.Length(); index > 0; index--) {
-    mUsage -= mOriginInfos[index - 1]->mUsage;
+    OriginInfo* originInfo = mOriginInfos[index - 1];
 
-    if (IsForTemporaryStorage()) {
+    mUsage -= originInfo->mUsage;
+
+    if (originInfo->IsTreatedAsTemporary()) {
       QuotaManager* quotaManager = QuotaManager::Get();
       NS_ASSERTION(quotaManager, "Shouldn't be null!");
 
-      quotaManager->mTemporaryStorageUsage -= mOriginInfos[index - 1]->mUsage;
+      quotaManager->mTemporaryStorageUsage -= originInfo->mUsage;
     }
 
     mOriginInfos.RemoveElementAt(index - 1);
   }
 }
 
 void
 GroupInfo::LockedRemoveOriginInfosForPattern(const nsACString& aPattern)
 {
   AssertCurrentThreadOwnsQuotaMutex();
 
   for (uint32_t index = mOriginInfos.Length(); index > 0; index--) {
     if (PatternMatchesOrigin(aPattern, mOriginInfos[index - 1]->mOrigin)) {
       mUsage -= mOriginInfos[index - 1]->mUsage;
 
-      if (IsForTemporaryStorage()) {
+      if (mOriginInfos[index - 1]->IsTreatedAsTemporary()) {
         QuotaManager* quotaManager = QuotaManager::Get();
         NS_ASSERTION(quotaManager, "Shouldn't be null!");
 
         quotaManager->mTemporaryStorageUsage -= mOriginInfos[index - 1]->mUsage;
       }
 
       mOriginInfos.RemoveElementAt(index - 1);
     }
   }
 }
 
+uint64_t
+GroupInfo::LockedGetTemporaryUsage()
+{
+  uint64_t usage = 0;
+
+  for (uint32_t count = mOriginInfos.Length(), index = 0;
+       index < count;
+       index++) {
+    nsRefPtr<OriginInfo>& originInfo = mOriginInfos[index];
+
+    if (originInfo->IsTreatedAsTemporary()) {
+      usage += originInfo->mUsage;
+    }
+  }
+
+  return usage;
+}
+
+void
+GroupInfo::LockedGetTemporaryOriginInfos(nsTArray<OriginInfo*>* aOriginInfos)
+{
+  AssertCurrentThreadOwnsQuotaMutex();
+
+  for (uint32_t count = mOriginInfos.Length(), index = 0;
+       index < count;
+       index++) {
+    nsRefPtr<OriginInfo>& originInfo = mOriginInfos[index];
+
+    if (originInfo->IsTreatedAsTemporary()) {
+      aOriginInfos->AppendElement(originInfo);
+    }
+  }
+}
+
+void
+GroupInfo::LockedRemoveTemporaryOriginInfos()
+{
+  AssertCurrentThreadOwnsQuotaMutex();
+
+  QuotaManager* quotaManager = QuotaManager::Get();
+  MOZ_ASSERT(quotaManager);
+
+  for (uint32_t index = mOriginInfos.Length(); index > 0; index--) {
+    OriginInfo* originInfo = mOriginInfos[index - 1];
+    if (originInfo->IsTreatedAsTemporary()) {
+      MOZ_ASSERT(mUsage >= originInfo->mUsage);
+      mUsage -= originInfo->mUsage;
+
+      MOZ_ASSERT(quotaManager->mTemporaryStorageUsage >= originInfo->mUsage);
+      quotaManager->mTemporaryStorageUsage -= originInfo->mUsage;
+
+      mOriginInfos.RemoveElementAt(index - 1);
+    }
+  }
+}
+
 nsRefPtr<GroupInfo>&
 GroupInfoPair::GetGroupInfoForPersistenceType(PersistenceType aPersistenceType)
 {
   switch (aPersistenceType) {
     case PERSISTENCE_TYPE_PERSISTENT:
       return mPersistentStorageGroupInfo;
     case PERSISTENCE_TYPE_TEMPORARY:
       return mTemporaryStorageGroupInfo;
--- a/dom/quota/QuotaObject.h
+++ b/dom/quota/QuotaObject.h
@@ -70,32 +70,38 @@ private:
 
 class OriginInfo MOZ_FINAL
 {
   friend class GroupInfo;
   friend class QuotaManager;
   friend class QuotaObject;
 
 public:
-  OriginInfo(GroupInfo* aGroupInfo, const nsACString& aOrigin, uint64_t aLimit,
-             uint64_t aUsage, int64_t aAccessTime)
+  OriginInfo(GroupInfo* aGroupInfo, const nsACString& aOrigin, bool aIsApp,
+             uint64_t aLimit, uint64_t aUsage, int64_t aAccessTime)
   : mGroupInfo(aGroupInfo), mOrigin(aOrigin), mLimit(aLimit), mUsage(aUsage),
-    mAccessTime(aAccessTime)
+    mAccessTime(aAccessTime), mIsApp(aIsApp)
   {
     MOZ_COUNT_CTOR(OriginInfo);
   }
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OriginInfo)
 
   int64_t
   AccessTime() const
   {
     return mAccessTime;
   }
 
+  bool
+  IsTreatedAsPersistent() const;
+
+  bool
+  IsTreatedAsTemporary() const;
+
 private:
   // Private destructor, to discourage deletion outside of Release():
   ~OriginInfo()
   {
     MOZ_COUNT_DTOR(OriginInfo);
   }
 
   void
@@ -119,20 +125,21 @@ private:
 
   static PLDHashOperator
   ClearOriginInfoCallback(const nsAString& aKey,
                           QuotaObject* aValue, void* aUserArg);
 
   nsDataHashtable<nsStringHashKey, QuotaObject*> mQuotaObjects;
 
   GroupInfo* mGroupInfo;
-  nsCString mOrigin;
-  uint64_t mLimit;
+  const nsCString mOrigin;
+  const uint64_t mLimit;
   uint64_t mUsage;
   int64_t mAccessTime;
+  const bool mIsApp;
 };
 
 class OriginInfoLRUComparator
 {
 public:
   bool
   Equals(const OriginInfo* a, const OriginInfo* b) const
   {
@@ -150,36 +157,26 @@ public:
 class GroupInfo MOZ_FINAL
 {
   friend class GroupInfoPair;
   friend class OriginInfo;
   friend class QuotaManager;
   friend class QuotaObject;
 
 public:
-  GroupInfo(PersistenceType aPersistenceType, const nsACString& aGroup)
-  : mPersistenceType(aPersistenceType), mGroup(aGroup), mUsage(0)
+  GroupInfo(GroupInfoPair* aGroupInfoPair, PersistenceType aPersistenceType,
+            const nsACString& aGroup)
+  : mGroupInfoPair(aGroupInfoPair), mPersistenceType(aPersistenceType),
+    mGroup(aGroup), mUsage(0)
   {
     MOZ_COUNT_CTOR(GroupInfo);
   }
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GroupInfo)
 
-  bool
-  IsForPersistentStorage() const
-  {
-    return mPersistenceType == PERSISTENCE_TYPE_PERSISTENT;
-  }
-
-  bool
-  IsForTemporaryStorage() const
-  {
-    return mPersistenceType == PERSISTENCE_TYPE_TEMPORARY;
-  }
-
 private:
   // Private destructor, to discourage deletion outside of Release():
   ~GroupInfo()
   {
     MOZ_COUNT_DTOR(GroupInfo);
   }
 
   already_AddRefed<OriginInfo>
@@ -200,26 +197,37 @@ private:
   bool
   LockedHasOriginInfos()
   {
     AssertCurrentThreadOwnsQuotaMutex();
 
     return !mOriginInfos.IsEmpty();
   }
 
+  uint64_t
+  LockedGetTemporaryUsage();
+
+  void
+  LockedGetTemporaryOriginInfos(nsTArray<OriginInfo*>* aOriginInfos);
+
+  void
+  LockedRemoveTemporaryOriginInfos();
+
   nsTArray<nsRefPtr<OriginInfo> > mOriginInfos;
 
+  GroupInfoPair* mGroupInfoPair;
   PersistenceType mPersistenceType;
   nsCString mGroup;
   uint64_t mUsage;
 };
 
 class GroupInfoPair
 {
   friend class QuotaManager;
+  friend class QuotaObject;
 
 public:
   GroupInfoPair()
   {
     MOZ_COUNT_CTOR(GroupInfoPair);
   }
 
   ~GroupInfoPair()