summaryrefslogtreecommitdiffstats
path: root/dom/quota
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /dom/quota
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/quota')
-rw-r--r--dom/quota/ActorsChild.cpp318
-rw-r--r--dom/quota/ActorsChild.h156
-rw-r--r--dom/quota/ActorsParent.cpp7898
-rw-r--r--dom/quota/ActorsParent.h26
-rw-r--r--dom/quota/Client.h151
-rw-r--r--dom/quota/FileStreams.cpp135
-rw-r--r--dom/quota/FileStreams.h127
-rw-r--r--dom/quota/OriginScope.h428
-rw-r--r--dom/quota/PQuota.ipdl87
-rw-r--r--dom/quota/PQuotaRequest.ipdl46
-rw-r--r--dom/quota/PQuotaUsageRequest.ipdl50
-rw-r--r--dom/quota/PersistenceType.h128
-rw-r--r--dom/quota/QuotaCommon.h73
-rw-r--r--dom/quota/QuotaManager.h546
-rw-r--r--dom/quota/QuotaManagerService.cpp845
-rw-r--r--dom/quota/QuotaManagerService.h113
-rw-r--r--dom/quota/QuotaObject.h85
-rw-r--r--dom/quota/QuotaRequests.cpp291
-rw-r--r--dom/quota/QuotaRequests.h142
-rw-r--r--dom/quota/QuotaResults.cpp91
-rw-r--r--dom/quota/QuotaResults.h60
-rw-r--r--dom/quota/SerializationHelpers.h26
-rw-r--r--dom/quota/StorageManager.cpp378
-rw-r--r--dom/quota/StorageManager.h59
-rw-r--r--dom/quota/UsageInfo.h108
-rw-r--r--dom/quota/moz.build59
-rw-r--r--dom/quota/nsIQuotaCallbacks.idl22
-rw-r--r--dom/quota/nsIQuotaManagerService.idl91
-rw-r--r--dom/quota/nsIQuotaRequests.idl40
-rw-r--r--dom/quota/nsIQuotaResults.idl27
30 files changed, 12606 insertions, 0 deletions
diff --git a/dom/quota/ActorsChild.cpp b/dom/quota/ActorsChild.cpp
new file mode 100644
index 000000000..03ed10981
--- /dev/null
+++ b/dom/quota/ActorsChild.cpp
@@ -0,0 +1,318 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ActorsChild.h"
+
+#include "nsVariant.h"
+#include "QuotaManagerService.h"
+#include "QuotaRequests.h"
+#include "QuotaResults.h"
+
+namespace mozilla {
+namespace dom {
+namespace quota {
+
+/*******************************************************************************
+ * QuotaChild
+ ******************************************************************************/
+
+QuotaChild::QuotaChild(QuotaManagerService* aService)
+ : mService(aService)
+#ifdef DEBUG
+ , mOwningThread(NS_GetCurrentThread())
+#endif
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aService);
+
+ MOZ_COUNT_CTOR(quota::QuotaChild);
+}
+
+QuotaChild::~QuotaChild()
+{
+ AssertIsOnOwningThread();
+
+ MOZ_COUNT_DTOR(quota::QuotaChild);
+}
+
+#ifdef DEBUG
+
+void
+QuotaChild::AssertIsOnOwningThread() const
+{
+ MOZ_ASSERT(mOwningThread);
+
+ bool current;
+ MOZ_ASSERT(NS_SUCCEEDED(mOwningThread->IsOnCurrentThread(&current)));
+ MOZ_ASSERT(current);
+}
+
+#endif // DEBUG
+
+void
+QuotaChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+ AssertIsOnOwningThread();
+
+ if (mService) {
+ mService->ClearBackgroundActor();
+#ifdef DEBUG
+ mService = nullptr;
+#endif
+ }
+}
+
+PQuotaUsageRequestChild*
+QuotaChild::AllocPQuotaUsageRequestChild(const UsageRequestParams& aParams)
+{
+ AssertIsOnOwningThread();
+
+ MOZ_CRASH("PQuotaUsageRequestChild actors should be manually constructed!");
+}
+
+bool
+QuotaChild::DeallocPQuotaUsageRequestChild(PQuotaUsageRequestChild* aActor)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aActor);
+
+ delete static_cast<QuotaUsageRequestChild*>(aActor);
+ return true;
+}
+
+PQuotaRequestChild*
+QuotaChild::AllocPQuotaRequestChild(const RequestParams& aParams)
+{
+ AssertIsOnOwningThread();
+
+ MOZ_CRASH("PQuotaRequestChild actors should be manually constructed!");
+}
+
+bool
+QuotaChild::DeallocPQuotaRequestChild(PQuotaRequestChild* aActor)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aActor);
+
+ delete static_cast<QuotaRequestChild*>(aActor);
+ return true;
+}
+
+/*******************************************************************************
+ * QuotaUsageRequestChild
+ ******************************************************************************/
+
+QuotaUsageRequestChild::QuotaUsageRequestChild(UsageRequest* aRequest)
+ : mRequest(aRequest)
+{
+ AssertIsOnOwningThread();
+
+ MOZ_COUNT_CTOR(quota::QuotaUsageRequestChild);
+}
+
+QuotaUsageRequestChild::~QuotaUsageRequestChild()
+{
+ // Can't assert owning thread here because the request is cleared.
+
+ MOZ_COUNT_DTOR(quota::QuotaUsageRequestChild);
+}
+
+#ifdef DEBUG
+
+void
+QuotaUsageRequestChild::AssertIsOnOwningThread() const
+{
+ MOZ_ASSERT(mRequest);
+ mRequest->AssertIsOnOwningThread();
+}
+
+#endif // DEBUG
+
+void
+QuotaUsageRequestChild::HandleResponse(nsresult aResponse)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(NS_FAILED(aResponse));
+ MOZ_ASSERT(mRequest);
+
+ mRequest->SetError(aResponse);
+}
+
+void
+QuotaUsageRequestChild::HandleResponse(const nsTArray<OriginUsage>& aResponse)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mRequest);
+
+ RefPtr<nsVariant> variant = new nsVariant();
+
+ if (aResponse.IsEmpty()) {
+ variant->SetAsEmptyArray();
+ } else {
+ nsTArray<RefPtr<UsageResult>> usageResults;
+
+ const uint32_t count = aResponse.Length();
+
+ usageResults.SetCapacity(count);
+
+ for (uint32_t index = 0; index < count; index++) {
+ auto& originUsage = aResponse[index];
+
+ RefPtr<UsageResult> usageResult = new UsageResult(originUsage.origin(),
+ originUsage.persisted(),
+ originUsage.usage());
+
+ usageResults.AppendElement(usageResult.forget());
+ }
+
+ variant->SetAsArray(nsIDataType::VTYPE_INTERFACE_IS,
+ &NS_GET_IID(nsIQuotaUsageResult),
+ usageResults.Length(),
+ static_cast<void*>(usageResults.Elements()));
+ }
+
+ mRequest->SetResult(variant);
+}
+
+void
+QuotaUsageRequestChild::HandleResponse(const OriginUsageResponse& aResponse)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mRequest);
+
+ RefPtr<OriginUsageResult> result =
+ new OriginUsageResult(aResponse.usage(),
+ aResponse.fileUsage(),
+ aResponse.limit());
+
+ RefPtr<nsVariant> variant = new nsVariant();
+ variant->SetAsInterface(NS_GET_IID(nsIQuotaOriginUsageResult), result);
+
+ mRequest->SetResult(variant);
+}
+
+void
+QuotaUsageRequestChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+ AssertIsOnOwningThread();
+
+ if (mRequest) {
+ mRequest->ClearBackgroundActor();
+#ifdef DEBUG
+ mRequest = nullptr;
+#endif
+ }
+}
+
+bool
+QuotaUsageRequestChild::Recv__delete__(const UsageRequestResponse& aResponse)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mRequest);
+
+ switch (aResponse.type()) {
+ case UsageRequestResponse::Tnsresult:
+ HandleResponse(aResponse.get_nsresult());
+ break;
+
+ case UsageRequestResponse::TAllUsageResponse:
+ HandleResponse(aResponse.get_AllUsageResponse().originUsages());
+ break;
+
+ case UsageRequestResponse::TOriginUsageResponse:
+ HandleResponse(aResponse.get_OriginUsageResponse());
+ break;
+
+ default:
+ MOZ_CRASH("Unknown response type!");
+ }
+
+ return true;
+}
+
+/*******************************************************************************
+ * QuotaRequestChild
+ ******************************************************************************/
+
+QuotaRequestChild::QuotaRequestChild(Request* aRequest)
+ : mRequest(aRequest)
+{
+ AssertIsOnOwningThread();
+
+ MOZ_COUNT_CTOR(quota::QuotaRequestChild);
+}
+
+QuotaRequestChild::~QuotaRequestChild()
+{
+ AssertIsOnOwningThread();
+
+ MOZ_COUNT_DTOR(quota::QuotaRequestChild);
+}
+
+#ifdef DEBUG
+
+void
+QuotaRequestChild::AssertIsOnOwningThread() const
+{
+ MOZ_ASSERT(mRequest);
+ mRequest->AssertIsOnOwningThread();
+}
+
+#endif // DEBUG
+
+void
+QuotaRequestChild::HandleResponse(nsresult aResponse)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(NS_FAILED(aResponse));
+ MOZ_ASSERT(mRequest);
+
+ mRequest->SetError(aResponse);
+}
+
+void
+QuotaRequestChild::HandleResponse()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mRequest);
+
+ mRequest->SetResult();
+}
+
+void
+QuotaRequestChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+ AssertIsOnOwningThread();
+}
+
+bool
+QuotaRequestChild::Recv__delete__(const RequestResponse& aResponse)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mRequest);
+
+ switch (aResponse.type()) {
+ case RequestResponse::Tnsresult:
+ HandleResponse(aResponse.get_nsresult());
+ break;
+
+ case RequestResponse::TClearOriginResponse:
+ case RequestResponse::TClearOriginsResponse:
+ case RequestResponse::TClearAllResponse:
+ case RequestResponse::TResetAllResponse:
+ HandleResponse();
+ break;
+
+ default:
+ MOZ_CRASH("Unknown response type!");
+ }
+
+ return true;
+}
+
+} // namespace quota
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/quota/ActorsChild.h b/dom/quota/ActorsChild.h
new file mode 100644
index 000000000..7aa4616f5
--- /dev/null
+++ b/dom/quota/ActorsChild.h
@@ -0,0 +1,156 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_quota_ActorsChild_h
+#define mozilla_dom_quota_ActorsChild_h
+
+#include "mozilla/dom/quota/PQuotaChild.h"
+#include "mozilla/dom/quota/PQuotaRequestChild.h"
+#include "mozilla/dom/quota/PQuotaUsageRequestChild.h"
+
+namespace mozilla {
+namespace ipc {
+
+class BackgroundChildImpl;
+
+} // namespace ipc
+
+namespace dom {
+namespace quota {
+
+class QuotaManagerService;
+class Request;
+class UsageRequest;
+
+class QuotaChild final
+ : public PQuotaChild
+{
+ friend class mozilla::ipc::BackgroundChildImpl;
+ friend class QuotaManagerService;
+
+ QuotaManagerService* mService;
+
+#ifdef DEBUG
+ nsCOMPtr<nsIEventTarget> mOwningThread;
+#endif
+
+public:
+ void
+ AssertIsOnOwningThread() const
+#ifdef DEBUG
+ ;
+#else
+ { }
+#endif
+
+private:
+ // Only created by QuotaManagerService.
+ explicit QuotaChild(QuotaManagerService* aService);
+
+ // Only destroyed by mozilla::ipc::BackgroundChildImpl.
+ ~QuotaChild();
+
+ // IPDL methods are only called by IPDL.
+ virtual void
+ ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual PQuotaUsageRequestChild*
+ AllocPQuotaUsageRequestChild(const UsageRequestParams& aParams) override;
+
+ virtual bool
+ DeallocPQuotaUsageRequestChild(PQuotaUsageRequestChild* aActor) override;
+
+ virtual PQuotaRequestChild*
+ AllocPQuotaRequestChild(const RequestParams& aParams) override;
+
+ virtual bool
+ DeallocPQuotaRequestChild(PQuotaRequestChild* aActor) override;
+};
+
+class QuotaUsageRequestChild final
+ : public PQuotaUsageRequestChild
+{
+ friend class QuotaChild;
+ friend class QuotaManagerService;
+
+ RefPtr<UsageRequest> mRequest;
+
+public:
+ void
+ AssertIsOnOwningThread() const
+#ifdef DEBUG
+ ;
+#else
+ { }
+#endif
+
+private:
+ // Only created by QuotaManagerService.
+ explicit QuotaUsageRequestChild(UsageRequest* aRequest);
+
+ // Only destroyed by QuotaChild.
+ ~QuotaUsageRequestChild();
+
+ void
+ HandleResponse(nsresult aResponse);
+
+ void
+ HandleResponse(const nsTArray<OriginUsage>& aResponse);
+
+ void
+ HandleResponse(const OriginUsageResponse& aResponse);
+
+ // IPDL methods are only called by IPDL.
+ virtual void
+ ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual bool
+ Recv__delete__(const UsageRequestResponse& aResponse) override;
+};
+
+class QuotaRequestChild final
+ : public PQuotaRequestChild
+{
+ friend class QuotaChild;
+ friend class QuotaManagerService;
+
+ RefPtr<Request> mRequest;
+
+public:
+ void
+ AssertIsOnOwningThread() const
+#ifdef DEBUG
+ ;
+#else
+ { }
+#endif
+
+private:
+ // Only created by QuotaManagerService.
+ explicit QuotaRequestChild(Request* aRequest);
+
+ // Only destroyed by QuotaChild.
+ ~QuotaRequestChild();
+
+ void
+ HandleResponse(nsresult aResponse);
+
+ void
+ HandleResponse();
+
+ // IPDL methods are only called by IPDL.
+ virtual void
+ ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual bool
+ Recv__delete__(const RequestResponse& aResponse) override;
+};
+
+} // namespace quota
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_quota_ActorsChild_h
diff --git a/dom/quota/ActorsParent.cpp b/dom/quota/ActorsParent.cpp
new file mode 100644
index 000000000..afdd0e6df
--- /dev/null
+++ b/dom/quota/ActorsParent.cpp
@@ -0,0 +1,7898 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ActorsParent.h"
+
+#include "mozIStorageConnection.h"
+#include "mozIStorageService.h"
+#include "nsIBinaryInputStream.h"
+#include "nsIBinaryOutputStream.h"
+#include "nsIFile.h"
+#include "nsIFileStreams.h"
+#include "nsIObserverService.h"
+#include "nsIPermissionManager.h"
+#include "nsIPrincipal.h"
+#include "nsIRunnable.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsITimer.h"
+#include "nsIURI.h"
+#include "nsPIDOMWindow.h"
+
+#include <algorithm>
+#include "GeckoProfiler.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/CondVar.h"
+#include "mozilla/dom/PContent.h"
+#include "mozilla/dom/asmjscache/AsmJSCache.h"
+#include "mozilla/dom/cache/QuotaClient.h"
+#include "mozilla/dom/indexedDB/ActorsParent.h"
+#include "mozilla/dom/quota/PQuotaParent.h"
+#include "mozilla/dom/quota/PQuotaRequestParent.h"
+#include "mozilla/dom/quota/PQuotaUsageRequestParent.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/LazyIdleThread.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TypeTraits.h"
+#include "mozilla/Unused.h"
+#include "mozStorageCID.h"
+#include "mozStorageHelper.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsComponentManagerUtils.h"
+#include "nsAboutProtocolUtils.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsContentUtils.h"
+#include "nsCRTGlue.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsEscape.h"
+#include "nsNetUtil.h"
+#include "nsPrintfCString.h"
+#include "nsScriptSecurityManager.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "prio.h"
+#include "xpcpublic.h"
+
+#include "OriginScope.h"
+#include "QuotaManager.h"
+#include "QuotaManagerService.h"
+#include "QuotaObject.h"
+#include "UsageInfo.h"
+
+#define DISABLE_ASSERTS_FOR_FUZZING 0
+
+#if DISABLE_ASSERTS_FOR_FUZZING
+#define ASSERT_UNLESS_FUZZING(...) do { } while (0)
+#else
+#define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__)
+#endif
+
+// The amount of time, in milliseconds, that our IO thread will stay alive
+// after the last event it processes.
+#define DEFAULT_THREAD_TIMEOUT_MS 30000
+
+// The amount of time, in milliseconds, that we will wait for active storage
+// transactions on shutdown before aborting them.
+#define DEFAULT_SHUTDOWN_TIMER_MS 30000
+
+// Preference that users can set to override temporary storage smart limit
+// calculation.
+#define PREF_FIXED_LIMIT "dom.quotaManager.temporaryStorage.fixedLimit"
+#define PREF_CHUNK_SIZE "dom.quotaManager.temporaryStorage.chunkSize"
+
+// Preference that is used to enable testing features
+#define PREF_TESTING_FEATURES "dom.quotaManager.testing"
+
+// profile-before-change, when we need to shut down quota manager
+#define PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID "profile-before-change-qm"
+
+#define KB * 1024ULL
+#define MB * 1024ULL KB
+#define GB * 1024ULL MB
+
+namespace mozilla {
+namespace dom {
+namespace quota {
+
+using namespace mozilla::ipc;
+
+// We want profiles to be platform-independent so we always need to replace
+// the same characters on every platform. Windows has the most extensive set
+// of illegal characters so we use its FILE_ILLEGAL_CHARACTERS and
+// FILE_PATH_SEPARATOR.
+const char QuotaManager::kReplaceChars[] = CONTROL_CHARACTERS "/:*?\"<>|\\";
+
+namespace {
+
+/*******************************************************************************
+ * Constants
+ ******************************************************************************/
+
+const uint32_t kSQLitePageSizeOverride = 512;
+
+// Major storage version. Bump for backwards-incompatible changes.
+const uint32_t kMajorStorageVersion = 1;
+
+// Minor storage version. Bump for backwards-compatible changes.
+const uint32_t kMinorStorageVersion = 0;
+
+// The storage version we store in the SQLite database is a (signed) 32-bit
+// integer. The major version is left-shifted 16 bits so the max value is
+// 0xFFFF. The minor version occupies the lower 16 bits and its max is 0xFFFF.
+static_assert(kMajorStorageVersion <= 0xFFFF,
+ "Major version needs to fit in 16 bits.");
+static_assert(kMinorStorageVersion <= 0xFFFF,
+ "Minor version needs to fit in 16 bits.");
+
+const int32_t kStorageVersion =
+ int32_t((kMajorStorageVersion << 16) + kMinorStorageVersion);
+
+static_assert(
+ static_cast<uint32_t>(StorageType::Persistent) ==
+ static_cast<uint32_t>(PERSISTENCE_TYPE_PERSISTENT),
+ "Enum values should match.");
+
+static_assert(
+ static_cast<uint32_t>(StorageType::Temporary) ==
+ static_cast<uint32_t>(PERSISTENCE_TYPE_TEMPORARY),
+ "Enum values should match.");
+
+static_assert(
+ static_cast<uint32_t>(StorageType::Default) ==
+ static_cast<uint32_t>(PERSISTENCE_TYPE_DEFAULT),
+ "Enum values should match.");
+
+const char kChromeOrigin[] = "chrome";
+const char kAboutHomeOriginPrefix[] = "moz-safe-about:home";
+const char kIndexedDBOriginPrefix[] = "indexeddb://";
+const char kResourceOriginPrefix[] = "resource://";
+
+#define INDEXEDDB_DIRECTORY_NAME "indexedDB"
+#define STORAGE_DIRECTORY_NAME "storage"
+#define PERSISTENT_DIRECTORY_NAME "persistent"
+#define PERMANENT_DIRECTORY_NAME "permanent"
+#define TEMPORARY_DIRECTORY_NAME "temporary"
+#define DEFAULT_DIRECTORY_NAME "default"
+
+enum AppId {
+ kNoAppId = nsIScriptSecurityManager::NO_APP_ID,
+ kUnknownAppId = nsIScriptSecurityManager::UNKNOWN_APP_ID
+};
+
+#define STORAGE_FILE_NAME "storage.sqlite"
+
+// The name of the file that we use to load/save the last access time of an
+// origin.
+#define METADATA_FILE_NAME ".metadata"
+#define METADATA_V2_FILE_NAME ".metadata-v2"
+
+/******************************************************************************
+ * SQLite functions
+ ******************************************************************************/
+
+#if 0
+int32_t
+MakeStorageVersion(uint32_t aMajorStorageVersion,
+ uint32_t aMinorStorageVersion)
+{
+ return int32_t((aMajorStorageVersion << 16) + aMinorStorageVersion);
+}
+#endif
+
+uint32_t
+GetMajorStorageVersion(int32_t aStorageVersion)
+{
+ return uint32_t(aStorageVersion >> 16);
+}
+
+/******************************************************************************
+ * Quota manager class declarations
+ ******************************************************************************/
+
+} // namespace
+
+class DirectoryLockImpl final
+ : public DirectoryLock
+{
+ RefPtr<QuotaManager> mQuotaManager;
+
+ const Nullable<PersistenceType> mPersistenceType;
+ const nsCString mGroup;
+ const OriginScope mOriginScope;
+ const Nullable<bool> mIsApp;
+ const Nullable<Client::Type> mClientType;
+ RefPtr<OpenDirectoryListener> mOpenListener;
+
+ nsTArray<DirectoryLockImpl*> mBlocking;
+ nsTArray<DirectoryLockImpl*> mBlockedOn;
+
+ const bool mExclusive;
+
+ // Internal quota manager operations use this flag to prevent directory lock
+ // registraction/unregistration from updating origin access time, etc.
+ const bool mInternal;
+
+ bool mInvalidated;
+
+public:
+ DirectoryLockImpl(QuotaManager* aQuotaManager,
+ Nullable<PersistenceType> aPersistenceType,
+ const nsACString& aGroup,
+ const OriginScope& aOriginScope,
+ Nullable<bool> aIsApp,
+ Nullable<Client::Type> aClientType,
+ bool aExclusive,
+ bool aInternal,
+ OpenDirectoryListener* aOpenListener);
+
+ void
+ AssertIsOnOwningThread() const
+#ifdef DEBUG
+ ;
+#else
+ { }
+#endif
+
+ const Nullable<PersistenceType>&
+ GetPersistenceType() const
+ {
+ return mPersistenceType;
+ }
+
+ const nsACString&
+ GetGroup() const
+ {
+ return mGroup;
+ }
+
+ const OriginScope&
+ GetOriginScope() const
+ {
+ return mOriginScope;
+ }
+
+ const Nullable<bool>&
+ GetIsApp() const
+ {
+ return mIsApp;
+ }
+
+ const Nullable<Client::Type>&
+ GetClientType() const
+ {
+ return mClientType;
+ }
+
+ bool
+ IsInternal() const
+ {
+ return mInternal;
+ }
+
+ bool
+ ShouldUpdateLockTable()
+ {
+ return !mInternal &&
+ mPersistenceType.Value() != PERSISTENCE_TYPE_PERSISTENT;
+ }
+
+ // Test whether this DirectoryLock needs to wait for the given lock.
+ bool
+ MustWaitFor(const DirectoryLockImpl& aLock);
+
+ void
+ AddBlockingLock(DirectoryLockImpl* aLock)
+ {
+ AssertIsOnOwningThread();
+
+ mBlocking.AppendElement(aLock);
+ }
+
+ const nsTArray<DirectoryLockImpl*>&
+ GetBlockedOnLocks()
+ {
+ return mBlockedOn;
+ }
+
+ void
+ AddBlockedOnLock(DirectoryLockImpl* aLock)
+ {
+ AssertIsOnOwningThread();
+
+ mBlockedOn.AppendElement(aLock);
+ }
+
+ void
+ MaybeUnblock(DirectoryLockImpl* aLock)
+ {
+ AssertIsOnOwningThread();
+
+ mBlockedOn.RemoveElement(aLock);
+ if (mBlockedOn.IsEmpty()) {
+ NotifyOpenListener();
+ }
+ }
+
+ void
+ NotifyOpenListener();
+
+ void
+ Invalidate()
+ {
+ AssertIsOnOwningThread();
+
+ mInvalidated = true;
+ }
+
+ NS_INLINE_DECL_REFCOUNTING(DirectoryLockImpl)
+
+private:
+ ~DirectoryLockImpl();
+};
+
+class QuotaManager::CreateRunnable final
+ : public BackgroundThreadObject
+ , public Runnable
+{
+ nsTArray<nsCOMPtr<nsIRunnable>> mCallbacks;
+ nsString mBaseDirPath;
+ RefPtr<QuotaManager> mManager;
+ nsresult mResultCode;
+
+ enum class State
+ {
+ Initial,
+ CreatingManager,
+ RegisteringObserver,
+ CallingCallbacks,
+ Completed
+ };
+
+ State mState;
+
+public:
+ CreateRunnable()
+ : mResultCode(NS_OK)
+ , mState(State::Initial)
+ {
+ AssertIsOnBackgroundThread();
+ }
+
+ void
+ AddCallback(nsIRunnable* aCallback)
+ {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aCallback);
+
+ mCallbacks.AppendElement(aCallback);
+ }
+
+private:
+ ~CreateRunnable()
+ { }
+
+ nsresult
+ Init();
+
+ nsresult
+ CreateManager();
+
+ nsresult
+ RegisterObserver();
+
+ void
+ CallCallbacks();
+
+ State
+ GetNextState(nsCOMPtr<nsIEventTarget>& aThread);
+
+ NS_DECL_NSIRUNNABLE
+};
+
+class QuotaManager::ShutdownRunnable final
+ : public Runnable
+{
+ // Only touched on the main thread.
+ bool& mDone;
+
+public:
+ explicit ShutdownRunnable(bool& aDone)
+ : mDone(aDone)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+private:
+ ~ShutdownRunnable()
+ { }
+
+ NS_DECL_NSIRUNNABLE
+};
+
+class QuotaManager::ShutdownObserver final
+ : public nsIObserver
+{
+ nsCOMPtr<nsIEventTarget> mBackgroundThread;
+
+public:
+ explicit ShutdownObserver(nsIEventTarget* aBackgroundThread)
+ : mBackgroundThread(aBackgroundThread)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+private:
+ ~ShutdownObserver()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+};
+
+namespace {
+
+/*******************************************************************************
+ * Local class declarations
+ ******************************************************************************/
+
+} // namespace
+
+class OriginInfo final
+{
+ friend class GroupInfo;
+ friend class QuotaManager;
+ friend class QuotaObject;
+
+public:
+ OriginInfo(GroupInfo* aGroupInfo, const nsACString& aOrigin, bool aIsApp,
+ uint64_t aUsage, int64_t aAccessTime)
+ : mGroupInfo(aGroupInfo), mOrigin(aOrigin), mUsage(aUsage),
+ mAccessTime(aAccessTime), mIsApp(aIsApp)
+ {
+ MOZ_COUNT_CTOR(OriginInfo);
+ }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OriginInfo)
+
+ int64_t
+ AccessTime() const
+ {
+ return mAccessTime;
+ }
+
+private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~OriginInfo()
+ {
+ MOZ_COUNT_DTOR(OriginInfo);
+
+ MOZ_ASSERT(!mQuotaObjects.Count());
+ }
+
+ void
+ LockedDecreaseUsage(int64_t aSize);
+
+ void
+ LockedUpdateAccessTime(int64_t aAccessTime)
+ {
+ AssertCurrentThreadOwnsQuotaMutex();
+
+ mAccessTime = aAccessTime;
+ }
+
+ nsDataHashtable<nsStringHashKey, QuotaObject*> mQuotaObjects;
+
+ GroupInfo* mGroupInfo;
+ const nsCString mOrigin;
+ uint64_t mUsage;
+ int64_t mAccessTime;
+ const bool mIsApp;
+};
+
+class OriginInfoLRUComparator
+{
+public:
+ bool
+ Equals(const OriginInfo* a, const OriginInfo* b) const
+ {
+ return
+ a && b ? a->AccessTime() == b->AccessTime() : !a && !b ? true : false;
+ }
+
+ bool
+ LessThan(const OriginInfo* a, const OriginInfo* b) const
+ {
+ return a && b ? a->AccessTime() < b->AccessTime() : b ? true : false;
+ }
+};
+
+class GroupInfo final
+{
+ friend class GroupInfoPair;
+ friend class OriginInfo;
+ friend class QuotaManager;
+ friend class QuotaObject;
+
+public:
+ GroupInfo(GroupInfoPair* aGroupInfoPair, PersistenceType aPersistenceType,
+ const nsACString& aGroup)
+ : mGroupInfoPair(aGroupInfoPair), mPersistenceType(aPersistenceType),
+ mGroup(aGroup), mUsage(0)
+ {
+ MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
+
+ MOZ_COUNT_CTOR(GroupInfo);
+ }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GroupInfo)
+
+private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~GroupInfo()
+ {
+ MOZ_COUNT_DTOR(GroupInfo);
+ }
+
+ already_AddRefed<OriginInfo>
+ LockedGetOriginInfo(const nsACString& aOrigin);
+
+ void
+ LockedAddOriginInfo(OriginInfo* aOriginInfo);
+
+ void
+ LockedRemoveOriginInfo(const nsACString& aOrigin);
+
+ void
+ LockedRemoveOriginInfos();
+
+ bool
+ LockedHasOriginInfos()
+ {
+ AssertCurrentThreadOwnsQuotaMutex();
+
+ return !mOriginInfos.IsEmpty();
+ }
+
+ nsTArray<RefPtr<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()
+ {
+ MOZ_COUNT_DTOR(GroupInfoPair);
+ }
+
+private:
+ already_AddRefed<GroupInfo>
+ LockedGetGroupInfo(PersistenceType aPersistenceType)
+ {
+ AssertCurrentThreadOwnsQuotaMutex();
+ MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
+
+ RefPtr<GroupInfo> groupInfo =
+ GetGroupInfoForPersistenceType(aPersistenceType);
+ return groupInfo.forget();
+ }
+
+ void
+ LockedSetGroupInfo(PersistenceType aPersistenceType, GroupInfo* aGroupInfo)
+ {
+ AssertCurrentThreadOwnsQuotaMutex();
+ MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
+
+ RefPtr<GroupInfo>& groupInfo =
+ GetGroupInfoForPersistenceType(aPersistenceType);
+ groupInfo = aGroupInfo;
+ }
+
+ void
+ LockedClearGroupInfo(PersistenceType aPersistenceType)
+ {
+ AssertCurrentThreadOwnsQuotaMutex();
+ MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
+
+ RefPtr<GroupInfo>& groupInfo =
+ GetGroupInfoForPersistenceType(aPersistenceType);
+ groupInfo = nullptr;
+ }
+
+ bool
+ LockedHasGroupInfos()
+ {
+ AssertCurrentThreadOwnsQuotaMutex();
+
+ return mTemporaryStorageGroupInfo || mDefaultStorageGroupInfo;
+ }
+
+ RefPtr<GroupInfo>&
+ GetGroupInfoForPersistenceType(PersistenceType aPersistenceType);
+
+ RefPtr<GroupInfo> mTemporaryStorageGroupInfo;
+ RefPtr<GroupInfo> mDefaultStorageGroupInfo;
+};
+
+namespace {
+
+class CollectOriginsHelper final
+ : public Runnable
+{
+ uint64_t mMinSizeToBeFreed;
+
+ Mutex& mMutex;
+ CondVar mCondVar;
+
+ // The members below are protected by mMutex.
+ nsTArray<RefPtr<DirectoryLockImpl>> mLocks;
+ uint64_t mSizeToBeFreed;
+ bool mWaiting;
+
+public:
+ CollectOriginsHelper(mozilla::Mutex& aMutex,
+ uint64_t aMinSizeToBeFreed);
+
+ // Blocks the current thread until origins are collected on the main thread.
+ // The returned value contains an aggregate size of those origins.
+ int64_t
+ BlockAndReturnOriginsForEviction(
+ nsTArray<RefPtr<DirectoryLockImpl>>& aLocks);
+
+private:
+ ~CollectOriginsHelper()
+ { }
+
+ NS_IMETHOD
+ Run() override;
+};
+
+class OriginOperationBase
+ : public BackgroundThreadObject
+ , public Runnable
+{
+protected:
+ nsresult mResultCode;
+
+ enum State {
+ // Not yet run.
+ State_Initial,
+
+ // Running initialization on the main thread.
+ State_Initializing,
+
+ // Running initialization on the owning thread.
+ State_FinishingInit,
+
+ // Running quota manager initialization on the owning thread.
+ State_CreatingQuotaManager,
+
+ // Running on the owning thread in the listener for OpenDirectory.
+ State_DirectoryOpenPending,
+
+ // Running on the IO thread.
+ State_DirectoryWorkOpen,
+
+ // Running on the owning thread after all work is done.
+ State_UnblockingOpen,
+
+ // All done.
+ State_Complete
+ };
+
+private:
+ State mState;
+ bool mActorDestroyed;
+
+protected:
+ bool mNeedsMainThreadInit;
+ bool mNeedsQuotaManagerInit;
+
+public:
+ void
+ NoteActorDestroyed()
+ {
+ AssertIsOnOwningThread();
+
+ mActorDestroyed = true;
+ }
+
+ bool
+ IsActorDestroyed() const
+ {
+ AssertIsOnOwningThread();
+
+ return mActorDestroyed;
+ }
+
+protected:
+ explicit OriginOperationBase(
+ nsIEventTarget* aOwningThread = NS_GetCurrentThread())
+ : BackgroundThreadObject(aOwningThread)
+ , mResultCode(NS_OK)
+ , mState(State_Initial)
+ , mActorDestroyed(false)
+ , mNeedsMainThreadInit(false)
+ , mNeedsQuotaManagerInit(false)
+ { }
+
+ // Reference counted.
+ virtual ~OriginOperationBase()
+ {
+ MOZ_ASSERT(mState == State_Complete);
+ MOZ_ASSERT(mActorDestroyed);
+ }
+
+ State
+ GetState() const
+ {
+ return mState;
+ }
+
+ void
+ SetState(State aState)
+ {
+ MOZ_ASSERT(mState == State_Initial);
+ mState = aState;
+ }
+
+ void
+ AdvanceState()
+ {
+ switch (mState) {
+ case State_Initial:
+ mState = State_Initializing;
+ return;
+ case State_Initializing:
+ mState = State_FinishingInit;
+ return;
+ case State_FinishingInit:
+ mState = State_CreatingQuotaManager;
+ return;
+ case State_CreatingQuotaManager:
+ mState = State_DirectoryOpenPending;
+ return;
+ case State_DirectoryOpenPending:
+ mState = State_DirectoryWorkOpen;
+ return;
+ case State_DirectoryWorkOpen:
+ mState = State_UnblockingOpen;
+ return;
+ case State_UnblockingOpen:
+ mState = State_Complete;
+ return;
+ default:
+ MOZ_CRASH("Bad state!");
+ }
+ }
+
+ NS_IMETHOD
+ Run() override;
+
+ virtual nsresult
+ DoInitOnMainThread()
+ {
+ return NS_OK;
+ }
+
+ virtual void
+ Open() = 0;
+
+ nsresult
+ DirectoryOpen();
+
+ virtual nsresult
+ DoDirectoryWork(QuotaManager* aQuotaManager) = 0;
+
+ void
+ Finish(nsresult aResult);
+
+ virtual void
+ UnblockOpen() = 0;
+
+private:
+ nsresult
+ Init();
+
+ nsresult
+ InitOnMainThread();
+
+ nsresult
+ FinishInit();
+
+ nsresult
+ QuotaManagerOpen();
+
+ nsresult
+ DirectoryWork();
+};
+
+class FinalizeOriginEvictionOp
+ : public OriginOperationBase
+{
+ nsTArray<RefPtr<DirectoryLockImpl>> mLocks;
+
+public:
+ FinalizeOriginEvictionOp(nsIEventTarget* aBackgroundThread,
+ nsTArray<RefPtr<DirectoryLockImpl>>& aLocks)
+ : OriginOperationBase(aBackgroundThread)
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ mLocks.SwapElements(aLocks);
+ }
+
+ void
+ Dispatch();
+
+ void
+ RunOnIOThreadImmediately();
+
+private:
+ ~FinalizeOriginEvictionOp()
+ { }
+
+ virtual void
+ Open() override;
+
+ virtual nsresult
+ DoDirectoryWork(QuotaManager* aQuotaManager) override;
+
+ virtual void
+ UnblockOpen() override;
+};
+
+class NormalOriginOperationBase
+ : public OriginOperationBase
+ , public OpenDirectoryListener
+{
+ RefPtr<DirectoryLock> mDirectoryLock;
+
+protected:
+ Nullable<PersistenceType> mPersistenceType;
+ OriginScope mOriginScope;
+ mozilla::Atomic<bool> mCanceled;
+ const bool mExclusive;
+
+public:
+ void
+ RunImmediately()
+ {
+ MOZ_ASSERT(GetState() == State_Initial);
+
+ MOZ_ALWAYS_SUCCEEDS(this->Run());
+ }
+
+protected:
+ NormalOriginOperationBase(Nullable<PersistenceType> aPersistenceType,
+ const OriginScope& aOriginScope,
+ bool aExclusive)
+ : mPersistenceType(aPersistenceType)
+ , mOriginScope(aOriginScope)
+ , mExclusive(aExclusive)
+ {
+ AssertIsOnOwningThread();
+ }
+
+ ~NormalOriginOperationBase()
+ { }
+
+private:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ virtual void
+ Open() override;
+
+ virtual void
+ UnblockOpen() override;
+
+ // OpenDirectoryListener overrides.
+ virtual void
+ DirectoryLockAcquired(DirectoryLock* aLock) override;
+
+ virtual void
+ DirectoryLockFailed() override;
+
+ // Used to send results before unblocking open.
+ virtual void
+ SendResults() = 0;
+};
+
+class SaveOriginAccessTimeOp
+ : public NormalOriginOperationBase
+{
+ int64_t mTimestamp;
+
+public:
+ SaveOriginAccessTimeOp(PersistenceType aPersistenceType,
+ const nsACString& aOrigin,
+ int64_t aTimestamp)
+ : NormalOriginOperationBase(Nullable<PersistenceType>(aPersistenceType),
+ OriginScope::FromOrigin(aOrigin),
+ /* aExclusive */ false)
+ , mTimestamp(aTimestamp)
+ {
+ AssertIsOnOwningThread();
+ }
+
+private:
+ ~SaveOriginAccessTimeOp()
+ { }
+
+ virtual nsresult
+ DoDirectoryWork(QuotaManager* aQuotaManager) override;
+
+ virtual void
+ SendResults() override;
+};
+
+/*******************************************************************************
+ * Actor class declarations
+ ******************************************************************************/
+
+class Quota final
+ : public PQuotaParent
+{
+#ifdef DEBUG
+ bool mActorDestroyed;
+#endif
+
+public:
+ Quota();
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::quota::Quota)
+
+private:
+ ~Quota();
+
+ void
+ StartIdleMaintenance();
+
+ // IPDL methods.
+ virtual void
+ ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual PQuotaUsageRequestParent*
+ AllocPQuotaUsageRequestParent(const UsageRequestParams& aParams) override;
+
+ virtual bool
+ RecvPQuotaUsageRequestConstructor(PQuotaUsageRequestParent* aActor,
+ const UsageRequestParams& aParams) override;
+
+ virtual bool
+ DeallocPQuotaUsageRequestParent(PQuotaUsageRequestParent* aActor) override;
+
+ virtual PQuotaRequestParent*
+ AllocPQuotaRequestParent(const RequestParams& aParams) override;
+
+ virtual bool
+ RecvPQuotaRequestConstructor(PQuotaRequestParent* aActor,
+ const RequestParams& aParams) override;
+
+ virtual bool
+ DeallocPQuotaRequestParent(PQuotaRequestParent* aActor) override;
+
+ virtual bool
+ RecvStartIdleMaintenance() override;
+
+ virtual bool
+ RecvStopIdleMaintenance() override;
+};
+
+class QuotaUsageRequestBase
+ : public NormalOriginOperationBase
+ , public PQuotaUsageRequestParent
+{
+public:
+ // May be overridden by subclasses if they need to perform work on the
+ // background thread before being run.
+ virtual bool
+ Init(Quota* aQuota);
+
+protected:
+ QuotaUsageRequestBase()
+ : NormalOriginOperationBase(Nullable<PersistenceType>(),
+ OriginScope::FromNull(),
+ /* aExclusive */ false)
+ { }
+
+ nsresult
+ GetUsageForOrigin(QuotaManager* aQuotaManager,
+ PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ bool aIsApp,
+ UsageInfo* aUsageInfo);
+
+ // Subclasses use this override to set the IPDL response value.
+ virtual void
+ GetResponse(UsageRequestResponse& aResponse) = 0;
+
+private:
+ void
+ SendResults() override;
+
+ // IPDL methods.
+ void
+ ActorDestroy(ActorDestroyReason aWhy) override;
+
+ bool
+ RecvCancel() override;
+};
+
+class GetUsageOp final
+ : public QuotaUsageRequestBase
+{
+ nsTArray<OriginUsage> mOriginUsages;
+ nsDataHashtable<nsCStringHashKey, uint32_t> mOriginUsagesIndex;
+
+ bool mGetAll;
+
+public:
+ explicit GetUsageOp(const UsageRequestParams& aParams);
+
+private:
+ ~GetUsageOp()
+ { }
+
+ nsresult
+ TraverseRepository(QuotaManager* aQuotaManager,
+ PersistenceType aPersistenceType);
+
+ nsresult
+ DoDirectoryWork(QuotaManager* aQuotaManager) override;
+
+ void
+ GetResponse(UsageRequestResponse& aResponse) override;
+};
+
+class GetOriginUsageOp final
+ : public QuotaUsageRequestBase
+{
+ // If mGetGroupUsage is false, we use mUsageInfo to record the origin usage
+ // and the file usage. Otherwise, we use it to record the group usage and the
+ // limit.
+ UsageInfo mUsageInfo;
+
+ const OriginUsageParams mParams;
+ nsCString mSuffix;
+ nsCString mGroup;
+ bool mIsApp;
+ bool mGetGroupUsage;
+
+public:
+ explicit GetOriginUsageOp(const UsageRequestParams& aParams);
+
+ MOZ_IS_CLASS_INIT bool
+ Init(Quota* aQuota) override;
+
+private:
+ ~GetOriginUsageOp()
+ { }
+
+ MOZ_IS_CLASS_INIT virtual nsresult
+ DoInitOnMainThread() override;
+
+ virtual nsresult
+ DoDirectoryWork(QuotaManager* aQuotaManager) override;
+
+ void
+ GetResponse(UsageRequestResponse& aResponse) override;
+};
+
+class QuotaRequestBase
+ : public NormalOriginOperationBase
+ , public PQuotaRequestParent
+{
+public:
+ // May be overridden by subclasses if they need to perform work on the
+ // background thread before being run.
+ virtual bool
+ Init(Quota* aQuota);
+
+protected:
+ explicit QuotaRequestBase(bool aExclusive)
+ : NormalOriginOperationBase(Nullable<PersistenceType>(),
+ OriginScope::FromNull(),
+ aExclusive)
+ { }
+
+ // Subclasses use this override to set the IPDL response value.
+ virtual void
+ GetResponse(RequestResponse& aResponse) = 0;
+
+private:
+ virtual void
+ SendResults() override;
+
+ // IPDL methods.
+ virtual void
+ ActorDestroy(ActorDestroyReason aWhy) override;
+};
+
+class ResetOrClearOp final
+ : public QuotaRequestBase
+{
+ const bool mClear;
+
+public:
+ explicit ResetOrClearOp(bool aClear)
+ : QuotaRequestBase(/* aExclusive */ true)
+ , mClear(aClear)
+ {
+ AssertIsOnOwningThread();
+ }
+
+private:
+ ~ResetOrClearOp()
+ { }
+
+ void
+ DeleteFiles(QuotaManager* aQuotaManager);
+
+ virtual nsresult
+ DoDirectoryWork(QuotaManager* aQuotaManager) override;
+
+ virtual void
+ GetResponse(RequestResponse& aResponse) override;
+};
+
+class OriginClearOp final
+ : public QuotaRequestBase
+{
+ const RequestParams mParams;
+ const bool mMultiple;
+
+public:
+ explicit OriginClearOp(const RequestParams& aParams);
+
+ virtual bool
+ Init(Quota* aQuota) override;
+
+private:
+ ~OriginClearOp()
+ { }
+
+ virtual nsresult
+ DoInitOnMainThread() override;
+
+ void
+ DeleteFiles(QuotaManager* aQuotaManager,
+ PersistenceType aPersistenceType);
+
+ virtual nsresult
+ DoDirectoryWork(QuotaManager* aQuotaManager) override;
+
+ virtual void
+ GetResponse(RequestResponse& aResponse) override;
+};
+
+/*******************************************************************************
+ * Helper Functions
+ ******************************************************************************/
+
+template <typename T, bool = mozilla::IsUnsigned<T>::value>
+struct IntChecker
+{
+ static void
+ Assert(T aInt)
+ {
+ static_assert(mozilla::IsIntegral<T>::value, "Not an integer!");
+ MOZ_ASSERT(aInt >= 0);
+ }
+};
+
+template <typename T>
+struct IntChecker<T, true>
+{
+ static void
+ Assert(T aInt)
+ {
+ static_assert(mozilla::IsIntegral<T>::value, "Not an integer!");
+ }
+};
+
+template <typename T>
+void
+AssertNoOverflow(uint64_t aDest, T aArg)
+{
+ IntChecker<T>::Assert(aDest);
+ IntChecker<T>::Assert(aArg);
+ MOZ_ASSERT(UINT64_MAX - aDest >= uint64_t(aArg));
+}
+
+template <typename T, typename U>
+void
+AssertNoUnderflow(T aDest, U aArg)
+{
+ IntChecker<T>::Assert(aDest);
+ IntChecker<T>::Assert(aArg);
+ MOZ_ASSERT(uint64_t(aDest) >= uint64_t(aArg));
+}
+
+} // namespace
+
+BackgroundThreadObject::BackgroundThreadObject()
+ : mOwningThread(NS_GetCurrentThread())
+{
+ AssertIsOnOwningThread();
+}
+
+BackgroundThreadObject::BackgroundThreadObject(nsIEventTarget* aOwningThread)
+ : mOwningThread(aOwningThread)
+{
+}
+
+#ifdef DEBUG
+
+void
+BackgroundThreadObject::AssertIsOnOwningThread() const
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mOwningThread);
+ bool current;
+ MOZ_ASSERT(NS_SUCCEEDED(mOwningThread->IsOnCurrentThread(&current)));
+ MOZ_ASSERT(current);
+}
+
+#endif // DEBUG
+
+nsIEventTarget*
+BackgroundThreadObject::OwningThread() const
+{
+ MOZ_ASSERT(mOwningThread);
+ return mOwningThread;
+}
+
+bool
+IsOnIOThread()
+{
+ QuotaManager* quotaManager = QuotaManager::Get();
+ NS_ASSERTION(quotaManager, "Must have a manager here!");
+
+ bool currentThread;
+ return NS_SUCCEEDED(quotaManager->IOThread()->
+ IsOnCurrentThread(&currentThread)) && currentThread;
+}
+
+void
+AssertIsOnIOThread()
+{
+ NS_ASSERTION(IsOnIOThread(), "Running on the wrong thread!");
+}
+
+void
+AssertCurrentThreadOwnsQuotaMutex()
+{
+#ifdef DEBUG
+ QuotaManager* quotaManager = QuotaManager::Get();
+ NS_ASSERTION(quotaManager, "Must have a manager here!");
+
+ quotaManager->AssertCurrentThreadOwnsQuotaMutex();
+#endif
+}
+
+void
+ReportInternalError(const char* aFile, uint32_t aLine, const char* aStr)
+{
+ // Get leaf of file path
+ for (const char* p = aFile; *p; ++p) {
+ if (*p == '/' && *(p + 1)) {
+ aFile = p + 1;
+ }
+ }
+
+ nsContentUtils::LogSimpleConsoleError(
+ NS_ConvertUTF8toUTF16(nsPrintfCString(
+ "Quota %s: %s:%lu", aStr, aFile, aLine)),
+ "quota");
+}
+
+namespace {
+
+StaticRefPtr<QuotaManager> gInstance;
+bool gCreateFailed = false;
+StaticRefPtr<QuotaManager::CreateRunnable> gCreateRunnable;
+mozilla::Atomic<bool> gShutdown(false);
+
+// Constants for temporary storage limit computing.
+static const int32_t kDefaultFixedLimitKB = -1;
+static const uint32_t kDefaultChunkSizeKB = 10 * 1024;
+int32_t gFixedLimitKB = kDefaultFixedLimitKB;
+uint32_t gChunkSizeKB = kDefaultChunkSizeKB;
+
+bool gTestingEnabled = false;
+
+class StorageDirectoryHelper
+ : public Runnable
+{
+ mozilla::Mutex mMutex;
+ mozilla::CondVar mCondVar;
+ nsresult mMainThreadResultCode;
+ bool mWaiting;
+
+protected:
+ struct OriginProps;
+
+ nsTArray<OriginProps> mOriginProps;
+
+ nsCOMPtr<nsIFile> mDirectory;
+
+public:
+ StorageDirectoryHelper(nsIFile* aDirectory)
+ : mMutex("StorageDirectoryHelper::mMutex")
+ , mCondVar(mMutex, "StorageDirectoryHelper::mCondVar")
+ , mMainThreadResultCode(NS_OK)
+ , mWaiting(true)
+ , mDirectory(aDirectory)
+ {
+ AssertIsOnIOThread();
+ }
+
+protected:
+ ~StorageDirectoryHelper()
+ { }
+
+ nsresult
+ AddOriginDirectory(nsIFile* aDirectory,
+ OriginProps** aOriginProps);
+
+ nsresult
+ ProcessOriginDirectories();
+
+ virtual nsresult
+ DoProcessOriginDirectories() = 0;
+
+private:
+ nsresult
+ RunOnMainThread();
+
+ NS_IMETHOD
+ Run() override;
+};
+
+struct StorageDirectoryHelper::OriginProps
+{
+ enum Type
+ {
+ eChrome,
+ eContent
+ };
+
+ nsCOMPtr<nsIFile> mDirectory;
+ nsCString mSpec;
+ PrincipalOriginAttributes mAttrs;
+ int64_t mTimestamp;
+ nsCString mSuffix;
+ nsCString mGroup;
+ nsCString mOrigin;
+
+ Type mType;
+ bool mIsApp;
+ bool mNeedsRestore;
+ bool mIgnore;
+
+public:
+ explicit OriginProps()
+ : mTimestamp(0)
+ , mType(eContent)
+ , mIsApp(false)
+ , mNeedsRestore(false)
+ , mIgnore(false)
+ { }
+};
+
+class MOZ_STACK_CLASS OriginParser final
+{
+ static bool
+ IgnoreWhitespace(char16_t /* aChar */)
+ {
+ return false;
+ }
+
+ typedef nsCCharSeparatedTokenizerTemplate<IgnoreWhitespace> Tokenizer;
+
+ enum SchemaType {
+ eNone,
+ eFile,
+ eAbout
+ };
+
+ enum State {
+ eExpectingAppIdOrSchema,
+ eExpectingInMozBrowser,
+ eExpectingSchema,
+ eExpectingEmptyToken1,
+ eExpectingEmptyToken2,
+ eExpectingEmptyToken3,
+ eExpectingHost,
+ eExpectingPort,
+ eExpectingEmptyTokenOrDriveLetterOrPathnameComponent,
+ eExpectingEmptyTokenOrPathnameComponent,
+ eComplete,
+ eHandledTrailingSeparator
+ };
+
+ const nsCString mOrigin;
+ const PrincipalOriginAttributes mOriginAttributes;
+ Tokenizer mTokenizer;
+
+ uint32_t mAppId;
+ nsCString mSchema;
+ nsCString mHost;
+ Nullable<uint32_t> mPort;
+ nsTArray<nsCString> mPathnameComponents;
+ nsCString mHandledTokens;
+
+ SchemaType mSchemaType;
+ State mState;
+ bool mInIsolatedMozBrowser;
+ bool mMaybeDriveLetter;
+ bool mError;
+
+public:
+ OriginParser(const nsACString& aOrigin,
+ const PrincipalOriginAttributes& aOriginAttributes)
+ : mOrigin(aOrigin)
+ , mOriginAttributes(aOriginAttributes)
+ , mTokenizer(aOrigin, '+')
+ , mAppId(kNoAppId)
+ , mPort()
+ , mSchemaType(eNone)
+ , mState(eExpectingAppIdOrSchema)
+ , mInIsolatedMozBrowser(false)
+ , mMaybeDriveLetter(false)
+ , mError(false)
+ { }
+
+ static bool
+ ParseOrigin(const nsACString& aOrigin,
+ nsCString& aSpec,
+ PrincipalOriginAttributes* aAttrs);
+
+ bool
+ Parse(nsACString& aSpec, PrincipalOriginAttributes* aAttrs);
+
+private:
+ void
+ HandleSchema(const nsDependentCSubstring& aSchema);
+
+ void
+ HandlePathnameComponent(const nsDependentCSubstring& aSchema);
+
+ void
+ HandleToken(const nsDependentCSubstring& aToken);
+
+ void
+ HandleTrailingSeparator();
+};
+
+class CreateOrUpgradeDirectoryMetadataHelper final
+ : public StorageDirectoryHelper
+{
+ const bool mPersistent;
+
+public:
+ CreateOrUpgradeDirectoryMetadataHelper(nsIFile* aDirectory,
+ bool aPersistent)
+ : StorageDirectoryHelper(aDirectory)
+ , mPersistent(aPersistent)
+ { }
+
+ nsresult
+ CreateOrUpgradeMetadataFiles();
+
+private:
+ nsresult
+ MaybeUpgradeOriginDirectory(nsIFile* aDirectory);
+
+ nsresult
+ GetDirectoryMetadata(nsIFile* aDirectory,
+ int64_t* aTimestamp,
+ nsACString& aGroup,
+ nsACString& aOrigin,
+ bool* aHasIsApp);
+
+ virtual nsresult
+ DoProcessOriginDirectories();
+};
+
+class UpgradeDirectoryMetadataFrom1To2Helper final
+ : public StorageDirectoryHelper
+{
+ const bool mPersistent;
+
+public:
+ UpgradeDirectoryMetadataFrom1To2Helper(nsIFile* aDirectory,
+ bool aPersistent)
+ : StorageDirectoryHelper(aDirectory)
+ , mPersistent(aPersistent)
+ { }
+
+ nsresult
+ UpgradeMetadataFiles();
+
+private:
+ nsresult
+ GetDirectoryMetadata(nsIFile* aDirectory,
+ int64_t* aTimestamp,
+ nsACString& aGroup,
+ nsACString& aOrigin,
+ bool* aIsApp);
+
+ virtual nsresult
+ DoProcessOriginDirectories() override;
+};
+
+class RestoreDirectoryMetadata2Helper final
+ : public StorageDirectoryHelper
+{
+ const bool mPersistent;
+
+public:
+ RestoreDirectoryMetadata2Helper(nsIFile* aDirectory,
+ bool aPersistent)
+ : StorageDirectoryHelper(aDirectory)
+ , mPersistent(aPersistent)
+ { }
+
+ nsresult
+ RestoreMetadata2File();
+
+private:
+ virtual nsresult
+ DoProcessOriginDirectories();
+};
+
+class OriginKey : public nsAutoCString
+{
+public:
+ OriginKey(PersistenceType aPersistenceType,
+ const nsACString& aOrigin)
+ {
+ PersistenceTypeToText(aPersistenceType, *this);
+ Append(':');
+ Append(aOrigin);
+ }
+};
+
+void
+SanitizeOriginString(nsCString& aOrigin)
+{
+
+#ifdef XP_WIN
+ NS_ASSERTION(!strcmp(QuotaManager::kReplaceChars,
+ FILE_ILLEGAL_CHARACTERS FILE_PATH_SEPARATOR),
+ "Illegal file characters have changed!");
+#endif
+
+ aOrigin.ReplaceChar(QuotaManager::kReplaceChars, '+');
+}
+
+bool
+IsTreatedAsPersistent(PersistenceType aPersistenceType,
+ bool aIsApp)
+{
+ if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT ||
+ (aPersistenceType == PERSISTENCE_TYPE_DEFAULT && aIsApp)) {
+ return true;
+ }
+
+ return false;
+}
+
+bool
+IsTreatedAsTemporary(PersistenceType aPersistenceType,
+ bool aIsApp)
+{
+ return !IsTreatedAsPersistent(aPersistenceType, aIsApp);
+}
+
+nsresult
+CloneStoragePath(nsIFile* aBaseDir,
+ const nsAString& aStorageName,
+ nsAString& aStoragePath)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> storageDir;
+ rv = aBaseDir->Clone(getter_AddRefs(storageDir));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = storageDir->Append(aStorageName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = storageDir->GetPath(aStoragePath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+GetLastModifiedTime(nsIFile* aFile, int64_t* aTimestamp)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aFile);
+ MOZ_ASSERT(aTimestamp);
+
+ class MOZ_STACK_CLASS Helper final
+ {
+ public:
+ static nsresult
+ GetLastModifiedTime(nsIFile* aFile, int64_t* aTimestamp)
+ {
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aFile);
+ MOZ_ASSERT(aTimestamp);
+
+ bool isDirectory;
+ nsresult rv = aFile->IsDirectory(&isDirectory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!isDirectory) {
+ nsString leafName;
+ rv = aFile->GetLeafName(leafName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (leafName.EqualsLiteral(METADATA_FILE_NAME) ||
+ leafName.EqualsLiteral(METADATA_V2_FILE_NAME) ||
+ leafName.EqualsLiteral(DSSTORE_FILE_NAME)) {
+ return NS_OK;
+ }
+
+ int64_t timestamp;
+ rv = aFile->GetLastModifiedTime(&timestamp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Need to convert from milliseconds to microseconds.
+ MOZ_ASSERT((INT64_MAX / PR_USEC_PER_MSEC) > timestamp);
+ timestamp *= int64_t(PR_USEC_PER_MSEC);
+
+ if (timestamp > *aTimestamp) {
+ *aTimestamp = timestamp;
+ }
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ rv = aFile->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> file = do_QueryInterface(entry);
+ MOZ_ASSERT(file);
+
+ rv = GetLastModifiedTime(file, aTimestamp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+ }
+ };
+
+ int64_t timestamp = INT64_MIN;
+ nsresult rv = Helper::GetLastModifiedTime(aFile, &timestamp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ *aTimestamp = timestamp;
+ return NS_OK;
+}
+
+nsresult
+EnsureDirectory(nsIFile* aDirectory, bool* aCreated)
+{
+ AssertIsOnIOThread();
+
+ nsresult rv = aDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
+ bool isDirectory;
+ rv = aDirectory->IsDirectory(&isDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(isDirectory, NS_ERROR_UNEXPECTED);
+
+ *aCreated = false;
+ }
+ else {
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aCreated = true;
+ }
+
+ return NS_OK;
+}
+
+enum FileFlag {
+ kTruncateFileFlag,
+ kUpdateFileFlag,
+ kAppendFileFlag
+};
+
+nsresult
+GetOutputStream(nsIFile* aDirectory,
+ const nsAString& aFilename,
+ FileFlag aFileFlag,
+ nsIOutputStream** aStream)
+{
+ AssertIsOnIOThread();
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = aDirectory->Clone(getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = file->Append(aFilename);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIOutputStream> outputStream;
+ switch (aFileFlag) {
+ case kTruncateFileFlag: {
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream),
+ file);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ break;
+ }
+
+ case kUpdateFileFlag: {
+ bool exists;
+ rv = file->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!exists) {
+ *aStream = nullptr;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFileStream> stream;
+ rv = NS_NewLocalFileStream(getter_AddRefs(stream), file);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ outputStream = do_QueryInterface(stream);
+ if (NS_WARN_IF(!outputStream)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ break;
+ }
+
+ case kAppendFileFlag: {
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream),
+ file,
+ PR_WRONLY | PR_CREATE_FILE | PR_APPEND);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ outputStream.forget(aStream);
+ return NS_OK;
+}
+
+nsresult
+GetBinaryOutputStream(nsIFile* aDirectory,
+ const nsAString& aFilename,
+ FileFlag aFileFlag,
+ nsIBinaryOutputStream** aStream)
+{
+ nsCOMPtr<nsIOutputStream> outputStream;
+ nsresult rv = GetOutputStream(aDirectory,
+ aFilename,
+ aFileFlag,
+ getter_AddRefs(outputStream));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+
+ nsCOMPtr<nsIBinaryOutputStream> binaryStream =
+ do_CreateInstance("@mozilla.org/binaryoutputstream;1");
+ if (NS_WARN_IF(!binaryStream)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = binaryStream->SetOutputStream(outputStream);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ binaryStream.forget(aStream);
+ return NS_OK;
+}
+
+void
+GetJarPrefix(uint32_t aAppId,
+ bool aInIsolatedMozBrowser,
+ nsACString& aJarPrefix)
+{
+ MOZ_ASSERT(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID);
+
+ if (aAppId == nsIScriptSecurityManager::UNKNOWN_APP_ID) {
+ aAppId = nsIScriptSecurityManager::NO_APP_ID;
+ }
+
+ aJarPrefix.Truncate();
+
+ // Fallback.
+ if (aAppId == nsIScriptSecurityManager::NO_APP_ID && !aInIsolatedMozBrowser) {
+ return;
+ }
+
+ // aJarPrefix = appId + "+" + { 't', 'f' } + "+";
+ aJarPrefix.AppendInt(aAppId);
+ aJarPrefix.Append('+');
+ aJarPrefix.Append(aInIsolatedMozBrowser ? 't' : 'f');
+ aJarPrefix.Append('+');
+}
+
+nsresult
+CreateDirectoryMetadata(nsIFile* aDirectory, int64_t aTimestamp,
+ const nsACString& aSuffix, const nsACString& aGroup,
+ const nsACString& aOrigin, bool aIsApp)
+{
+ AssertIsOnIOThread();
+
+ PrincipalOriginAttributes groupAttributes;
+
+ nsCString groupNoSuffix;
+ bool ok = groupAttributes.PopulateFromOrigin(aGroup, groupNoSuffix);
+ if (!ok) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCString groupPrefix;
+ GetJarPrefix(groupAttributes.mAppId,
+ groupAttributes.mInIsolatedMozBrowser,
+ groupPrefix);
+
+ nsCString group = groupPrefix + groupNoSuffix;
+
+ PrincipalOriginAttributes originAttributes;
+
+ nsCString originNoSuffix;
+ ok = originAttributes.PopulateFromOrigin(aOrigin, originNoSuffix);
+ if (!ok) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCString originPrefix;
+ GetJarPrefix(originAttributes.mAppId,
+ originAttributes.mInIsolatedMozBrowser,
+ originPrefix);
+
+ nsCString origin = originPrefix + originNoSuffix;
+
+ MOZ_ASSERT(groupPrefix == originPrefix);
+
+ nsCOMPtr<nsIBinaryOutputStream> stream;
+ nsresult rv = GetBinaryOutputStream(aDirectory,
+ NS_LITERAL_STRING(METADATA_FILE_NAME),
+ kTruncateFileFlag,
+ 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;
+ }
+
+ rv = stream->WriteStringZ(group.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stream->WriteStringZ(origin.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stream->WriteBoolean(aIsApp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+CreateDirectoryMetadata2(nsIFile* aDirectory, int64_t aTimestamp,
+ const nsACString& aSuffix, const nsACString& aGroup,
+ const nsACString& aOrigin, bool aIsApp)
+{
+ AssertIsOnIOThread();
+
+ nsCOMPtr<nsIBinaryOutputStream> stream;
+ nsresult rv = GetBinaryOutputStream(aDirectory,
+ NS_LITERAL_STRING(METADATA_V2_FILE_NAME),
+ kTruncateFileFlag,
+ 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;
+ }
+
+ // Reserved for navigator.persist()
+ rv = stream->WriteBoolean(false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Reserved data 1
+ rv = stream->Write32(0);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Reserved data 2
+ rv = stream->Write32(0);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stream->WriteStringZ(PromiseFlatCString(aSuffix).get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stream->WriteStringZ(PromiseFlatCString(aGroup).get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stream->WriteStringZ(PromiseFlatCString(aOrigin).get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stream->WriteBoolean(aIsApp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+GetBinaryInputStream(nsIFile* aDirectory,
+ const nsAString& aFilename,
+ nsIBinaryInputStream** aStream)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aDirectory);
+ MOZ_ASSERT(aStream);
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = aDirectory->Clone(getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = file->Append(aFilename);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIInputStream> bufferedStream;
+ rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), stream, 512);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIBinaryInputStream> binaryStream =
+ do_CreateInstance("@mozilla.org/binaryinputstream;1");
+ if (NS_WARN_IF(!binaryStream)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = binaryStream->SetInputStream(bufferedStream);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ binaryStream.forget(aStream);
+ return NS_OK;
+}
+
+// This method computes and returns our best guess for the temporary storage
+// limit (in bytes), based on the amount of space users have free on their hard
+// drive and on given temporary storage usage (also in bytes).
+nsresult
+GetTemporaryStorageLimit(nsIFile* aDirectory, uint64_t aCurrentUsage,
+ uint64_t* aLimit)
+{
+ // Check for free space on device where temporary storage directory lives.
+ int64_t bytesAvailable;
+ nsresult rv = aDirectory->GetDiskSpaceAvailable(&bytesAvailable);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(bytesAvailable >= 0, "Negative bytes available?!");
+
+ uint64_t availableKB =
+ static_cast<uint64_t>((bytesAvailable + aCurrentUsage) / 1024);
+
+ // Grow/shrink in gChunkSizeKB units, deliberately, so that in the common case
+ // we don't shrink temporary storage and evict origin data every time we
+ // initialize.
+ availableKB = (availableKB / gChunkSizeKB) * gChunkSizeKB;
+
+ // Allow temporary storage to consume up to half the available space.
+ uint64_t resultKB = availableKB * .50;
+
+ *aLimit = resultKB * 1024;
+ return NS_OK;
+}
+
+} // namespace
+
+/*******************************************************************************
+ * Exported functions
+ ******************************************************************************/
+
+PQuotaParent*
+AllocPQuotaParent()
+{
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(QuotaManager::IsShuttingDown())) {
+ return nullptr;
+ }
+
+ RefPtr<Quota> actor = new Quota();
+
+ return actor.forget().take();
+}
+
+bool
+DeallocPQuotaParent(PQuotaParent* aActor)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ RefPtr<Quota> actor = dont_AddRef(static_cast<Quota*>(aActor));
+ return true;
+}
+
+/*******************************************************************************
+ * Directory lock
+ ******************************************************************************/
+
+DirectoryLockImpl::DirectoryLockImpl(QuotaManager* aQuotaManager,
+ Nullable<PersistenceType> aPersistenceType,
+ const nsACString& aGroup,
+ const OriginScope& aOriginScope,
+ Nullable<bool> aIsApp,
+ Nullable<Client::Type> aClientType,
+ bool aExclusive,
+ bool aInternal,
+ OpenDirectoryListener* aOpenListener)
+ : mQuotaManager(aQuotaManager)
+ , mPersistenceType(aPersistenceType)
+ , mGroup(aGroup)
+ , mOriginScope(aOriginScope)
+ , mIsApp(aIsApp)
+ , mClientType(aClientType)
+ , mOpenListener(aOpenListener)
+ , mExclusive(aExclusive)
+ , mInternal(aInternal)
+ , mInvalidated(false)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aQuotaManager);
+ MOZ_ASSERT_IF(aOriginScope.IsOrigin(), !aOriginScope.GetOrigin().IsEmpty());
+ MOZ_ASSERT_IF(!aInternal, !aPersistenceType.IsNull());
+ MOZ_ASSERT_IF(!aInternal,
+ aPersistenceType.Value() != PERSISTENCE_TYPE_INVALID);
+ MOZ_ASSERT_IF(!aInternal, !aGroup.IsEmpty());
+ MOZ_ASSERT_IF(!aInternal, aOriginScope.IsOrigin());
+ MOZ_ASSERT_IF(!aInternal, !aIsApp.IsNull());
+ MOZ_ASSERT_IF(!aInternal, !aClientType.IsNull());
+ MOZ_ASSERT_IF(!aInternal, aClientType.Value() != Client::TYPE_MAX);
+ MOZ_ASSERT_IF(!aInternal, aOpenListener);
+}
+
+DirectoryLockImpl::~DirectoryLockImpl()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mQuotaManager);
+
+ for (DirectoryLockImpl* blockingLock : mBlocking) {
+ blockingLock->MaybeUnblock(this);
+ }
+
+ mBlocking.Clear();
+
+ mQuotaManager->UnregisterDirectoryLock(this);
+}
+
+#ifdef DEBUG
+
+void
+DirectoryLockImpl::AssertIsOnOwningThread() const
+{
+ MOZ_ASSERT(mQuotaManager);
+ mQuotaManager->AssertIsOnOwningThread();
+}
+
+#endif // DEBUG
+
+bool
+DirectoryLockImpl::MustWaitFor(const DirectoryLockImpl& aExistingLock)
+{
+ AssertIsOnOwningThread();
+
+ // Waiting is never required if the ops in comparison represent shared locks.
+ if (!aExistingLock.mExclusive && !mExclusive) {
+ return false;
+ }
+
+ // If the persistence types don't overlap, the op can proceed.
+ if (!aExistingLock.mPersistenceType.IsNull() && !mPersistenceType.IsNull() &&
+ aExistingLock.mPersistenceType.Value() != mPersistenceType.Value()) {
+ return false;
+ }
+
+ // If the origin scopes don't overlap, the op can proceed.
+ bool match = aExistingLock.mOriginScope.Matches(mOriginScope);
+ if (!match) {
+ return false;
+ }
+
+ // If the client types don't overlap, the op can proceed.
+ if (!aExistingLock.mClientType.IsNull() && !mClientType.IsNull() &&
+ aExistingLock.mClientType.Value() != mClientType.Value()) {
+ return false;
+ }
+
+ // Otherwise, when all attributes overlap (persistence type, origin scope and
+ // client type) the op must wait.
+ return true;
+}
+
+void
+DirectoryLockImpl::NotifyOpenListener()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mQuotaManager);
+ MOZ_ASSERT(mOpenListener);
+
+ if (mInvalidated) {
+ mOpenListener->DirectoryLockFailed();
+ } else {
+ mOpenListener->DirectoryLockAcquired(this);
+ }
+
+ mOpenListener = nullptr;
+
+ mQuotaManager->RemovePendingDirectoryLock(this);
+}
+
+nsresult
+QuotaManager::
+CreateRunnable::Init()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mState == State::Initial);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> baseDir;
+ rv = NS_GetSpecialDirectory(NS_APP_INDEXEDDB_PARENT_DIR,
+ getter_AddRefs(baseDir));
+ if (NS_FAILED(rv)) {
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(baseDir));
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = baseDir->GetPath(mBaseDirPath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+QuotaManager::
+CreateRunnable::CreateManager()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::CreatingManager);
+
+ mManager = new QuotaManager();
+
+ nsresult rv = mManager->Init(mBaseDirPath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+QuotaManager::
+CreateRunnable::RegisterObserver()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mState == State::RegisteringObserver);
+
+ if (NS_FAILED(Preferences::AddIntVarCache(&gFixedLimitKB, PREF_FIXED_LIMIT,
+ kDefaultFixedLimitKB)) ||
+ NS_FAILED(Preferences::AddUintVarCache(&gChunkSizeKB,
+ PREF_CHUNK_SIZE,
+ kDefaultChunkSizeKB))) {
+ NS_WARNING("Unable to respond to temp storage pref changes!");
+ }
+
+ if (NS_FAILED(Preferences::AddBoolVarCache(&gTestingEnabled,
+ PREF_TESTING_FEATURES, false))) {
+ NS_WARNING("Unable to respond to testing pref changes!");
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (NS_WARN_IF(!observerService)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIObserver> observer = new ShutdownObserver(mOwningThread);
+
+ nsresult rv =
+ observerService->AddObserver(observer,
+ PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID,
+ false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // This service has to be started on the main thread currently.
+ nsCOMPtr<mozIStorageService> ss =
+ do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ QuotaManagerService* qms = QuotaManagerService::GetOrCreate();
+ if (NS_WARN_IF(!qms)) {
+ return rv;
+ }
+
+ qms->NoteLiveManager(mManager);
+
+ for (RefPtr<Client>& client : mManager->mClients) {
+ client->DidInitialize(mManager);
+ }
+
+ return NS_OK;
+}
+
+void
+QuotaManager::
+CreateRunnable::CallCallbacks()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State::CallingCallbacks);
+
+ gCreateRunnable = nullptr;
+
+ if (NS_FAILED(mResultCode)) {
+ gCreateFailed = true;
+ } else {
+ gInstance = mManager;
+ }
+
+ mManager = nullptr;
+
+ nsTArray<nsCOMPtr<nsIRunnable>> callbacks;
+ mCallbacks.SwapElements(callbacks);
+
+ for (nsCOMPtr<nsIRunnable>& callback : callbacks) {
+ Unused << callback->Run();
+ }
+}
+
+auto
+QuotaManager::
+CreateRunnable::GetNextState(nsCOMPtr<nsIEventTarget>& aThread) -> State
+{
+ switch (mState) {
+ case State::Initial:
+ aThread = mOwningThread;
+ return State::CreatingManager;
+ case State::CreatingManager:
+ aThread = do_GetMainThread();
+ return State::RegisteringObserver;
+ case State::RegisteringObserver:
+ aThread = mOwningThread;
+ return State::CallingCallbacks;
+ case State::CallingCallbacks:
+ aThread = nullptr;
+ return State::Completed;
+ default:
+ MOZ_CRASH("Bad state!");
+ }
+}
+
+NS_IMETHODIMP
+QuotaManager::
+CreateRunnable::Run()
+{
+ nsresult rv;
+
+ switch (mState) {
+ case State::Initial:
+ rv = Init();
+ break;
+
+ case State::CreatingManager:
+ rv = CreateManager();
+ break;
+
+ case State::RegisteringObserver:
+ rv = RegisterObserver();
+ break;
+
+ case State::CallingCallbacks:
+ CallCallbacks();
+ rv = NS_OK;
+ break;
+
+ case State::Completed:
+ default:
+ MOZ_CRASH("Bad state!");
+ }
+
+ nsCOMPtr<nsIEventTarget> thread;
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ if (NS_SUCCEEDED(mResultCode)) {
+ mResultCode = rv;
+ }
+
+ mState = State::CallingCallbacks;
+ thread = mOwningThread;
+ } else {
+ mState = GetNextState(thread);
+ }
+
+ if (thread) {
+ MOZ_ALWAYS_SUCCEEDS(thread->Dispatch(this, NS_DISPATCH_NORMAL));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+QuotaManager::
+ShutdownRunnable::Run()
+{
+ if (NS_IsMainThread()) {
+ mDone = true;
+
+ return NS_OK;
+ }
+
+ AssertIsOnBackgroundThread();
+
+ RefPtr<QuotaManager> quotaManager = gInstance.get();
+ if (quotaManager) {
+ quotaManager->Shutdown();
+
+ gInstance = nullptr;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(QuotaManager::ShutdownObserver, nsIObserver)
+
+NS_IMETHODIMP
+QuotaManager::
+ShutdownObserver::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!strcmp(aTopic, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID));
+ MOZ_ASSERT(gInstance);
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (NS_WARN_IF(!observerService)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Unregister ourselves from the observer service first to make sure the
+ // nested event loop below will not cause re-entrancy issues.
+ Unused <<
+ observerService->RemoveObserver(this,
+ PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID);
+
+ QuotaManagerService* qms = QuotaManagerService::Get();
+ MOZ_ASSERT(qms);
+
+ qms->NoteShuttingDownManager();
+
+ for (RefPtr<Client>& client : gInstance->mClients) {
+ client->WillShutdown();
+ }
+
+ bool done = false;
+
+ RefPtr<ShutdownRunnable> shutdownRunnable = new ShutdownRunnable(done);
+ MOZ_ALWAYS_SUCCEEDS(
+ mBackgroundThread->Dispatch(shutdownRunnable, NS_DISPATCH_NORMAL));
+
+ nsIThread* currentThread = NS_GetCurrentThread();
+ MOZ_ASSERT(currentThread);
+
+ while (!done) {
+ MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(currentThread));
+ }
+
+ return NS_OK;
+}
+
+/*******************************************************************************
+ * Quota object
+ ******************************************************************************/
+
+void
+QuotaObject::AddRef()
+{
+ QuotaManager* quotaManager = QuotaManager::Get();
+ if (!quotaManager) {
+ NS_ERROR("Null quota manager, this shouldn't happen, possible leak!");
+
+ ++mRefCnt;
+
+ return;
+ }
+
+ MutexAutoLock lock(quotaManager->mQuotaMutex);
+
+ ++mRefCnt;
+}
+
+void
+QuotaObject::Release()
+{
+ QuotaManager* quotaManager = QuotaManager::Get();
+ if (!quotaManager) {
+ NS_ERROR("Null quota manager, this shouldn't happen, possible leak!");
+
+ nsrefcnt count = --mRefCnt;
+ if (count == 0) {
+ mRefCnt = 1;
+ delete this;
+ }
+
+ return;
+ }
+
+ {
+ MutexAutoLock lock(quotaManager->mQuotaMutex);
+
+ --mRefCnt;
+
+ if (mRefCnt > 0) {
+ return;
+ }
+
+ if (mOriginInfo) {
+ mOriginInfo->mQuotaObjects.Remove(mPath);
+ }
+ }
+
+ delete this;
+}
+
+bool
+QuotaObject::MaybeUpdateSize(int64_t aSize, bool aTruncate)
+{
+ QuotaManager* quotaManager = QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ MutexAutoLock lock(quotaManager->mQuotaMutex);
+
+ if (mQuotaCheckDisabled) {
+ return true;
+ }
+
+ if (mSize == aSize) {
+ return true;
+ }
+
+ if (!mOriginInfo) {
+ mSize = aSize;
+ return true;
+ }
+
+ GroupInfo* groupInfo = mOriginInfo->mGroupInfo;
+ MOZ_ASSERT(groupInfo);
+
+ if (mSize > aSize) {
+ if (aTruncate) {
+ const int64_t delta = mSize - aSize;
+
+ AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, delta);
+ quotaManager->mTemporaryStorageUsage -= delta;
+
+ AssertNoUnderflow(groupInfo->mUsage, delta);
+ groupInfo->mUsage -= delta;
+
+ AssertNoUnderflow(mOriginInfo->mUsage, delta);
+ mOriginInfo->mUsage -= delta;
+
+ mSize = aSize;
+ }
+ return true;
+ }
+
+ MOZ_ASSERT(mSize < aSize);
+
+ RefPtr<GroupInfo> complementaryGroupInfo =
+ groupInfo->mGroupInfoPair->LockedGetGroupInfo(
+ ComplementaryPersistenceType(groupInfo->mPersistenceType));
+
+ uint64_t delta = aSize - mSize;
+
+ AssertNoOverflow(mOriginInfo->mUsage, delta);
+ uint64_t newUsage = mOriginInfo->mUsage + delta;
+
+ // Temporary storage has no limit for origin usage (there's a group and the
+ // global limit though).
+
+ AssertNoOverflow(groupInfo->mUsage, delta);
+ uint64_t newGroupUsage = groupInfo->mUsage + delta;
+
+ uint64_t groupUsage = groupInfo->mUsage;
+ if (complementaryGroupInfo) {
+ AssertNoOverflow(groupUsage, complementaryGroupInfo->mUsage);
+ groupUsage += complementaryGroupInfo->mUsage;
+ }
+
+ // Temporary storage has a hard limit for group usage (20 % of the global
+ // limit).
+ AssertNoOverflow(groupUsage, delta);
+ if (groupUsage + delta > quotaManager->GetGroupLimit()) {
+ return false;
+ }
+
+ AssertNoOverflow(quotaManager->mTemporaryStorageUsage, delta);
+ uint64_t newTemporaryStorageUsage = quotaManager->mTemporaryStorageUsage +
+ delta;
+
+ if (newTemporaryStorageUsage > quotaManager->mTemporaryStorageLimit) {
+ // This will block the thread without holding the lock while waitting.
+
+ AutoTArray<RefPtr<DirectoryLockImpl>, 10> locks;
+
+ uint64_t sizeToBeFreed =
+ quotaManager->LockedCollectOriginsForEviction(delta, locks);
+
+ if (!sizeToBeFreed) {
+ return false;
+ }
+
+ NS_ASSERTION(sizeToBeFreed >= delta, "Huh?");
+
+ {
+ MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex);
+
+ for (RefPtr<DirectoryLockImpl>& lock : locks) {
+ MOZ_ASSERT(!lock->GetPersistenceType().IsNull());
+ MOZ_ASSERT(lock->GetOriginScope().IsOrigin());
+ MOZ_ASSERT(!lock->GetOriginScope().GetOrigin().IsEmpty());
+
+ quotaManager->DeleteFilesForOrigin(lock->GetPersistenceType().Value(),
+ lock->GetOriginScope().GetOrigin());
+ }
+ }
+
+ // Relocked.
+
+ NS_ASSERTION(mOriginInfo, "How come?!");
+
+ for (DirectoryLockImpl* lock : locks) {
+ MOZ_ASSERT(!lock->GetPersistenceType().IsNull());
+ MOZ_ASSERT(!lock->GetGroup().IsEmpty());
+ MOZ_ASSERT(lock->GetOriginScope().IsOrigin());
+ MOZ_ASSERT(!lock->GetOriginScope().GetOrigin().IsEmpty());
+ MOZ_ASSERT(lock->GetOriginScope().GetOrigin() != mOriginInfo->mOrigin,
+ "Deleted itself!");
+
+ quotaManager->LockedRemoveQuotaForOrigin(
+ lock->GetPersistenceType().Value(),
+ lock->GetGroup(),
+ lock->GetOriginScope().GetOrigin());
+ }
+
+ // We unlocked and relocked several times so we need to recompute all the
+ // essential variables and recheck the group limit.
+
+ AssertNoUnderflow(aSize, mSize);
+ delta = aSize - mSize;
+
+ AssertNoOverflow(mOriginInfo->mUsage, delta);
+ newUsage = mOriginInfo->mUsage + delta;
+
+ AssertNoOverflow(groupInfo->mUsage, delta);
+ newGroupUsage = groupInfo->mUsage + delta;
+
+ groupUsage = groupInfo->mUsage;
+ if (complementaryGroupInfo) {
+ AssertNoOverflow(groupUsage, complementaryGroupInfo->mUsage);
+ groupUsage += complementaryGroupInfo->mUsage;
+ }
+
+ AssertNoOverflow(groupUsage, delta);
+ 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(locks);
+
+ return false;
+ }
+
+ AssertNoOverflow(quotaManager->mTemporaryStorageUsage, delta);
+ newTemporaryStorageUsage = quotaManager->mTemporaryStorageUsage + delta;
+
+ NS_ASSERTION(newTemporaryStorageUsage <=
+ quotaManager->mTemporaryStorageLimit, "How come?!");
+
+ // Ok, we successfully freed enough space and the operation can continue
+ // without throwing the quota error.
+ mOriginInfo->mUsage = newUsage;
+ groupInfo->mUsage = newGroupUsage;
+ quotaManager->mTemporaryStorageUsage = newTemporaryStorageUsage;;
+
+ // Some other thread could increase the size in the meantime, but no more
+ // than this one.
+ MOZ_ASSERT(mSize < aSize);
+ mSize = aSize;
+
+ // Finally, release IO thread only objects and allow next synchronized
+ // ops for the evicted origins.
+ MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex);
+
+ quotaManager->FinalizeOriginEviction(locks);
+
+ return true;
+ }
+
+ mOriginInfo->mUsage = newUsage;
+ groupInfo->mUsage = newGroupUsage;
+ quotaManager->mTemporaryStorageUsage = newTemporaryStorageUsage;
+
+ mSize = aSize;
+
+ return true;
+}
+
+void
+QuotaObject::DisableQuotaCheck()
+{
+ QuotaManager* quotaManager = QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ MutexAutoLock lock(quotaManager->mQuotaMutex);
+
+ mQuotaCheckDisabled = true;
+}
+
+void
+QuotaObject::EnableQuotaCheck()
+{
+ QuotaManager* quotaManager = QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ MutexAutoLock lock(quotaManager->mQuotaMutex);
+
+ mQuotaCheckDisabled = false;
+}
+
+/*******************************************************************************
+ * Quota manager
+ ******************************************************************************/
+
+QuotaManager::QuotaManager()
+: mQuotaMutex("QuotaManager.mQuotaMutex"),
+ mTemporaryStorageLimit(0),
+ mTemporaryStorageUsage(0),
+ mTemporaryStorageInitialized(false),
+ mStorageInitialized(false)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(!gInstance);
+}
+
+QuotaManager::~QuotaManager()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(!gInstance || gInstance == this);
+}
+
+void
+QuotaManager::GetOrCreate(nsIRunnable* aCallback)
+{
+ AssertIsOnBackgroundThread();
+
+ if (IsShuttingDown()) {
+ MOZ_ASSERT(false, "Calling GetOrCreate() after shutdown!");
+ return;
+ }
+
+ if (gInstance || gCreateFailed) {
+ MOZ_ASSERT(!gCreateRunnable);
+ MOZ_ASSERT_IF(gCreateFailed, !gInstance);
+
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(aCallback));
+ } else {
+ if (!gCreateRunnable) {
+ gCreateRunnable = new CreateRunnable();
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(gCreateRunnable));
+ }
+
+ gCreateRunnable->AddCallback(aCallback);
+ }
+}
+
+// static
+QuotaManager*
+QuotaManager::Get()
+{
+ // Does not return an owning reference.
+ return gInstance;
+}
+
+// static
+bool
+QuotaManager::IsShuttingDown()
+{
+ return gShutdown;
+}
+
+auto
+QuotaManager::CreateDirectoryLock(Nullable<PersistenceType> aPersistenceType,
+ const nsACString& aGroup,
+ const OriginScope& aOriginScope,
+ Nullable<bool> aIsApp,
+ Nullable<Client::Type> aClientType,
+ bool aExclusive,
+ bool aInternal,
+ OpenDirectoryListener* aOpenListener)
+ -> already_AddRefed<DirectoryLockImpl>
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT_IF(aOriginScope.IsOrigin(), !aOriginScope.GetOrigin().IsEmpty());
+ MOZ_ASSERT_IF(!aInternal, !aPersistenceType.IsNull());
+ MOZ_ASSERT_IF(!aInternal,
+ aPersistenceType.Value() != PERSISTENCE_TYPE_INVALID);
+ MOZ_ASSERT_IF(!aInternal, !aGroup.IsEmpty());
+ MOZ_ASSERT_IF(!aInternal, aOriginScope.IsOrigin());
+ MOZ_ASSERT_IF(!aInternal, !aIsApp.IsNull());
+ MOZ_ASSERT_IF(!aInternal, !aClientType.IsNull());
+ MOZ_ASSERT_IF(!aInternal, aClientType.Value() != Client::TYPE_MAX);
+ MOZ_ASSERT_IF(!aInternal, aOpenListener);
+
+ RefPtr<DirectoryLockImpl> lock = new DirectoryLockImpl(this,
+ aPersistenceType,
+ aGroup,
+ aOriginScope,
+ aIsApp,
+ aClientType,
+ aExclusive,
+ aInternal,
+ aOpenListener);
+
+ mPendingDirectoryLocks.AppendElement(lock);
+
+ // See if this lock needs to wait.
+ bool blocked = false;
+ for (uint32_t index = mDirectoryLocks.Length(); index > 0; index--) {
+ DirectoryLockImpl* existingLock = mDirectoryLocks[index - 1];
+ if (lock->MustWaitFor(*existingLock)) {
+ existingLock->AddBlockingLock(lock);
+ lock->AddBlockedOnLock(existingLock);
+ blocked = true;
+ }
+ }
+
+ RegisterDirectoryLock(lock);
+
+ // Otherwise, notify the open listener immediately.
+ if (!blocked) {
+ lock->NotifyOpenListener();
+ }
+
+ return lock.forget();
+}
+
+auto
+QuotaManager::CreateDirectoryLockForEviction(PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ bool aIsApp)
+ -> already_AddRefed<DirectoryLockImpl>
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_INVALID);
+ MOZ_ASSERT(!aOrigin.IsEmpty());
+
+ RefPtr<DirectoryLockImpl> lock =
+ new DirectoryLockImpl(this,
+ Nullable<PersistenceType>(aPersistenceType),
+ aGroup,
+ OriginScope::FromOrigin(aOrigin),
+ Nullable<bool>(aIsApp),
+ Nullable<Client::Type>(),
+ /* aExclusive */ true,
+ /* aInternal */ true,
+ nullptr);
+
+#ifdef DEBUG
+ for (uint32_t index = mDirectoryLocks.Length(); index > 0; index--) {
+ DirectoryLockImpl* existingLock = mDirectoryLocks[index - 1];
+ MOZ_ASSERT(!lock->MustWaitFor(*existingLock));
+ }
+#endif
+
+ RegisterDirectoryLock(lock);
+
+ return lock.forget();
+}
+
+void
+QuotaManager::RegisterDirectoryLock(DirectoryLockImpl* aLock)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aLock);
+
+ mDirectoryLocks.AppendElement(aLock);
+
+ if (aLock->ShouldUpdateLockTable()) {
+ const Nullable<PersistenceType>& persistenceType =
+ aLock->GetPersistenceType();
+ const OriginScope& originScope = aLock->GetOriginScope();
+
+ MOZ_ASSERT(!persistenceType.IsNull());
+ MOZ_ASSERT(!aLock->GetGroup().IsEmpty());
+ MOZ_ASSERT(originScope.IsOrigin());
+ MOZ_ASSERT(!originScope.GetOrigin().IsEmpty());
+
+ DirectoryLockTable& directoryLockTable =
+ GetDirectoryLockTable(persistenceType.Value());
+
+ nsTArray<DirectoryLockImpl*>* array;
+ if (!directoryLockTable.Get(originScope.GetOrigin(), &array)) {
+ array = new nsTArray<DirectoryLockImpl*>();
+ directoryLockTable.Put(originScope.GetOrigin(), array);
+
+ if (!IsShuttingDown()) {
+ UpdateOriginAccessTime(persistenceType.Value(),
+ aLock->GetGroup(),
+ originScope.GetOrigin());
+ }
+ }
+ array->AppendElement(aLock);
+ }
+}
+
+void
+QuotaManager::UnregisterDirectoryLock(DirectoryLockImpl* aLock)
+{
+ AssertIsOnOwningThread();
+
+ MOZ_ALWAYS_TRUE(mDirectoryLocks.RemoveElement(aLock));
+
+ if (aLock->ShouldUpdateLockTable()) {
+ const Nullable<PersistenceType>& persistenceType =
+ aLock->GetPersistenceType();
+ const OriginScope& originScope = aLock->GetOriginScope();
+
+ MOZ_ASSERT(!persistenceType.IsNull());
+ MOZ_ASSERT(!aLock->GetGroup().IsEmpty());
+ MOZ_ASSERT(originScope.IsOrigin());
+ MOZ_ASSERT(!originScope.GetOrigin().IsEmpty());
+
+ DirectoryLockTable& directoryLockTable =
+ GetDirectoryLockTable(persistenceType.Value());
+
+ nsTArray<DirectoryLockImpl*>* array;
+ MOZ_ALWAYS_TRUE(directoryLockTable.Get(originScope.GetOrigin(), &array));
+
+ MOZ_ALWAYS_TRUE(array->RemoveElement(aLock));
+ if (array->IsEmpty()) {
+ directoryLockTable.Remove(originScope.GetOrigin());
+
+ if (!IsShuttingDown()) {
+ UpdateOriginAccessTime(persistenceType.Value(),
+ aLock->GetGroup(),
+ originScope.GetOrigin());
+ }
+ }
+ }
+}
+
+void
+QuotaManager::RemovePendingDirectoryLock(DirectoryLockImpl* aLock)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aLock);
+
+ MOZ_ALWAYS_TRUE(mPendingDirectoryLocks.RemoveElement(aLock));
+}
+
+uint64_t
+QuotaManager::CollectOriginsForEviction(
+ uint64_t aMinSizeToBeFreed,
+ nsTArray<RefPtr<DirectoryLockImpl>>& aLocks)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aLocks.IsEmpty());
+
+ struct MOZ_STACK_CLASS Helper final
+ {
+ static void
+ GetInactiveOriginInfos(nsTArray<RefPtr<OriginInfo>>& aOriginInfos,
+ nsTArray<DirectoryLockImpl*>& aLocks,
+ nsTArray<OriginInfo*>& aInactiveOriginInfos)
+ {
+ for (OriginInfo* originInfo : aOriginInfos) {
+ MOZ_ASSERT(IsTreatedAsTemporary(originInfo->mGroupInfo->mPersistenceType,
+ originInfo->mIsApp));
+
+ OriginScope originScope = OriginScope::FromOrigin(originInfo->mOrigin);
+
+ bool match = false;
+ for (uint32_t j = aLocks.Length(); j > 0; j--) {
+ DirectoryLockImpl* lock = aLocks[j - 1];
+ if (originScope.Matches(lock->GetOriginScope())) {
+ match = true;
+ break;
+ }
+ }
+
+ if (!match) {
+ MOZ_ASSERT(!originInfo->mQuotaObjects.Count(),
+ "Inactive origin shouldn't have open files!");
+ aInactiveOriginInfos.InsertElementSorted(originInfo,
+ OriginInfoLRUComparator());
+ }
+ }
+ }
+ };
+
+ // Split locks into separate arrays and filter out locks for persistent
+ // storage, they can't block us.
+ nsTArray<DirectoryLockImpl*> temporaryStorageLocks;
+ nsTArray<DirectoryLockImpl*> defaultStorageLocks;
+ for (DirectoryLockImpl* lock : mDirectoryLocks) {
+ const Nullable<PersistenceType>& persistenceType =
+ lock->GetPersistenceType();
+
+ if (persistenceType.IsNull()) {
+ temporaryStorageLocks.AppendElement(lock);
+ defaultStorageLocks.AppendElement(lock);
+ } else if (persistenceType.Value() == PERSISTENCE_TYPE_TEMPORARY) {
+ temporaryStorageLocks.AppendElement(lock);
+ } else if (persistenceType.Value() == PERSISTENCE_TYPE_DEFAULT) {
+ defaultStorageLocks.AppendElement(lock);
+ } else {
+ MOZ_ASSERT(persistenceType.Value() == PERSISTENCE_TYPE_PERSISTENT);
+
+ // Do nothing here, persistent origins don't need to be collected ever.
+ }
+ }
+
+ nsTArray<OriginInfo*> inactiveOrigins;
+
+ // Enumerate and process inactive origins. This must be protected by the
+ // mutex.
+ MutexAutoLock lock(mQuotaMutex);
+
+ for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) {
+ GroupInfoPair* pair = iter.UserData();
+
+ MOZ_ASSERT(!iter.Key().IsEmpty());
+ MOZ_ASSERT(pair);
+
+ RefPtr<GroupInfo> groupInfo =
+ pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
+ if (groupInfo) {
+ Helper::GetInactiveOriginInfos(groupInfo->mOriginInfos,
+ temporaryStorageLocks,
+ inactiveOrigins);
+ }
+
+ groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
+ if (groupInfo) {
+ Helper::GetInactiveOriginInfos(groupInfo->mOriginInfos,
+ defaultStorageLocks,
+ inactiveOrigins);
+ }
+ }
+
+#ifdef DEBUG
+ // Make sure the array is sorted correctly.
+ for (uint32_t index = inactiveOrigins.Length(); index > 1; index--) {
+ MOZ_ASSERT(inactiveOrigins[index - 1]->mAccessTime >=
+ inactiveOrigins[index - 2]->mAccessTime);
+ }
+#endif
+
+ // Create a list of inactive and the least recently used origins
+ // whose aggregate size is greater or equals the minimal size to be freed.
+ uint64_t sizeToBeFreed = 0;
+ for(uint32_t count = inactiveOrigins.Length(), index = 0;
+ index < count;
+ index++) {
+ if (sizeToBeFreed >= aMinSizeToBeFreed) {
+ inactiveOrigins.TruncateLength(index);
+ break;
+ }
+
+ sizeToBeFreed += inactiveOrigins[index]->mUsage;
+ }
+
+ if (sizeToBeFreed >= aMinSizeToBeFreed) {
+ // Success, add directory locks for these origins, so any other
+ // operations for them will be delayed (until origin eviction is finalized).
+
+ for (OriginInfo* originInfo : inactiveOrigins) {
+ RefPtr<DirectoryLockImpl> lock =
+ CreateDirectoryLockForEviction(originInfo->mGroupInfo->mPersistenceType,
+ originInfo->mGroupInfo->mGroup,
+ originInfo->mOrigin,
+ originInfo->mIsApp);
+ aLocks.AppendElement(lock.forget());
+ }
+
+ return sizeToBeFreed;
+ }
+
+ return 0;
+}
+
+nsresult
+QuotaManager::Init(const nsAString& aBasePath)
+{
+ nsresult rv;
+
+ mBasePath = aBasePath;
+
+ nsCOMPtr<nsIFile> baseDir = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = baseDir->InitWithPath(aBasePath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = CloneStoragePath(baseDir,
+ NS_LITERAL_STRING(INDEXEDDB_DIRECTORY_NAME),
+ mIndexedDBPath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = baseDir->Append(NS_LITERAL_STRING(STORAGE_DIRECTORY_NAME));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = baseDir->GetPath(mStoragePath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = CloneStoragePath(baseDir,
+ NS_LITERAL_STRING(PERMANENT_DIRECTORY_NAME),
+ mPermanentStoragePath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = CloneStoragePath(baseDir,
+ NS_LITERAL_STRING(TEMPORARY_DIRECTORY_NAME),
+ mTemporaryStoragePath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = CloneStoragePath(baseDir,
+ NS_LITERAL_STRING(DEFAULT_DIRECTORY_NAME),
+ mDefaultStoragePath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Make a lazy thread for any IO we need (like clearing or enumerating the
+ // contents of storage directories).
+ mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
+ NS_LITERAL_CSTRING("Storage I/O"),
+ LazyIdleThread::ManualShutdown);
+
+ // Make a timer here to avoid potential failures later. We don't actually
+ // initialize the timer until shutdown.
+ mShutdownTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ if (NS_WARN_IF(!mShutdownTimer)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ static_assert(Client::IDB == 0 && Client::ASMJS == 1 && Client::DOMCACHE == 2 &&
+ Client::TYPE_MAX == 3, "Fix the registration!");
+
+ MOZ_ASSERT(mClients.Capacity() == Client::TYPE_MAX,
+ "Should be using an auto array with correct capacity!");
+
+ // Register clients.
+ mClients.AppendElement(indexedDB::CreateQuotaClient());
+ mClients.AppendElement(asmjscache::CreateClient());
+ mClients.AppendElement(cache::CreateQuotaClient());
+
+ return NS_OK;
+}
+
+void
+QuotaManager::Shutdown()
+{
+ AssertIsOnOwningThread();
+
+ // Setting this flag prevents the service from being recreated and prevents
+ // further storagess from being created.
+ if (gShutdown.exchange(true)) {
+ NS_ERROR("Shutdown more than once?!");
+ }
+
+ StopIdleMaintenance();
+
+ // Kick off the shutdown timer.
+ MOZ_ALWAYS_SUCCEEDS(
+ mShutdownTimer->InitWithFuncCallback(&ShutdownTimerCallback,
+ this,
+ DEFAULT_SHUTDOWN_TIMER_MS,
+ nsITimer::TYPE_ONE_SHOT));
+
+ // Each client will spin the event loop while we wait on all the threads
+ // to close. Our timer may fire during that loop.
+ for (uint32_t index = 0; index < Client::TYPE_MAX; index++) {
+ mClients[index]->ShutdownWorkThreads();
+ }
+
+ // Cancel the timer regardless of whether it actually fired.
+ if (NS_FAILED(mShutdownTimer->Cancel())) {
+ NS_WARNING("Failed to cancel shutdown timer!");
+ }
+
+ // NB: It's very important that runnable is destroyed on this thread
+ // (i.e. after we join the IO thread) because we can't release the
+ // QuotaManager on the IO thread. This should probably use
+ // NewNonOwningRunnableMethod ...
+ RefPtr<Runnable> runnable =
+ NewRunnableMethod(this, &QuotaManager::ReleaseIOThreadObjects);
+ MOZ_ASSERT(runnable);
+
+ // Give clients a chance to cleanup IO thread only objects.
+ if (NS_FAILED(mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL))) {
+ NS_WARNING("Failed to dispatch runnable!");
+ }
+
+ // Make sure to join with our IO thread.
+ if (NS_FAILED(mIOThread->Shutdown())) {
+ NS_WARNING("Failed to shutdown IO thread!");
+ }
+
+ for (RefPtr<DirectoryLockImpl>& lock : mPendingDirectoryLocks) {
+ lock->Invalidate();
+ }
+}
+
+void
+QuotaManager::InitQuotaForOrigin(PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ bool aIsApp,
+ uint64_t aUsageBytes,
+ int64_t aAccessTime)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(IsTreatedAsTemporary(aPersistenceType, aIsApp));
+
+ 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.
+ }
+
+ RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
+ if (!groupInfo) {
+ groupInfo = new GroupInfo(pair, aPersistenceType, aGroup);
+ pair->LockedSetGroupInfo(aPersistenceType, groupInfo);
+ }
+
+ RefPtr<OriginInfo> originInfo =
+ new OriginInfo(groupInfo, aOrigin, aIsApp, aUsageBytes, aAccessTime);
+ groupInfo->LockedAddOriginInfo(originInfo);
+}
+
+void
+QuotaManager::DecreaseUsageForOrigin(PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ int64_t aSize)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
+
+ MutexAutoLock lock(mQuotaMutex);
+
+ GroupInfoPair* pair;
+ if (!mGroupInfoPairs.Get(aGroup, &pair)) {
+ return;
+ }
+
+ RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
+ if (!groupInfo) {
+ return;
+ }
+
+ RefPtr<OriginInfo> originInfo = groupInfo->LockedGetOriginInfo(aOrigin);
+ if (originInfo) {
+ originInfo->LockedDecreaseUsage(aSize);
+ }
+}
+
+void
+QuotaManager::UpdateOriginAccessTime(PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
+
+ MutexAutoLock lock(mQuotaMutex);
+
+ GroupInfoPair* pair;
+ if (!mGroupInfoPairs.Get(aGroup, &pair)) {
+ return;
+ }
+
+ RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
+ if (!groupInfo) {
+ return;
+ }
+
+ RefPtr<OriginInfo> originInfo = groupInfo->LockedGetOriginInfo(aOrigin);
+ if (originInfo) {
+ int64_t timestamp = PR_Now();
+ originInfo->LockedUpdateAccessTime(timestamp);
+
+ MutexAutoUnlock autoUnlock(mQuotaMutex);
+
+ RefPtr<SaveOriginAccessTimeOp> op =
+ new SaveOriginAccessTimeOp(aPersistenceType, aOrigin, timestamp);
+
+ op->RunImmediately();
+ }
+}
+
+void
+QuotaManager::RemoveQuota()
+{
+ MutexAutoLock lock(mQuotaMutex);
+
+ for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<GroupInfoPair>& pair = iter.Data();
+
+ MOZ_ASSERT(!iter.Key().IsEmpty(), "Empty key!");
+ MOZ_ASSERT(pair, "Null pointer!");
+
+ RefPtr<GroupInfo> groupInfo =
+ pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
+ if (groupInfo) {
+ groupInfo->LockedRemoveOriginInfos();
+ }
+
+ groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
+ if (groupInfo) {
+ groupInfo->LockedRemoveOriginInfos();
+ }
+
+ iter.Remove();
+ }
+
+ NS_ASSERTION(mTemporaryStorageUsage == 0, "Should be zero!");
+}
+
+already_AddRefed<QuotaObject>
+QuotaManager::GetQuotaObject(PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ nsIFile* aFile)
+{
+ NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+ if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
+ return nullptr;
+ }
+
+ nsString path;
+ nsresult rv = aFile->GetPath(path);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ int64_t fileSize;
+
+ bool exists;
+ rv = aFile->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ if (exists) {
+ rv = aFile->GetFileSize(&fileSize);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ }
+ else {
+ fileSize = 0;
+ }
+
+ // Re-escape our parameters above to make sure we get the right quota group.
+ nsAutoCString group;
+ rv = NS_EscapeURL(aGroup, esc_Query, group, fallible);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsAutoCString origin;
+ rv = NS_EscapeURL(aOrigin, esc_Query, origin, fallible);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ RefPtr<QuotaObject> result;
+ {
+ MutexAutoLock lock(mQuotaMutex);
+
+ GroupInfoPair* pair;
+ if (!mGroupInfoPairs.Get(group, &pair)) {
+ return nullptr;
+ }
+
+ RefPtr<GroupInfo> groupInfo =
+ pair->LockedGetGroupInfo(aPersistenceType);
+
+ if (!groupInfo) {
+ return nullptr;
+ }
+
+ RefPtr<OriginInfo> originInfo = groupInfo->LockedGetOriginInfo(origin);
+
+ if (!originInfo) {
+ return nullptr;
+ }
+
+ // We need this extra raw pointer because we can't assign to the smart
+ // pointer directly since QuotaObject::AddRef would try to acquire the same
+ // mutex.
+ QuotaObject* quotaObject;
+ if (!originInfo->mQuotaObjects.Get(path, &quotaObject)) {
+ // Create a new QuotaObject.
+ quotaObject = new QuotaObject(originInfo, path, fileSize);
+
+ // Put it to the hashtable. The hashtable is not responsible to delete
+ // the QuotaObject.
+ originInfo->mQuotaObjects.Put(path, quotaObject);
+ }
+
+ // Addref the QuotaObject and move the ownership to the result. This must
+ // happen before we unlock!
+ result = quotaObject->LockedAddRef();
+ }
+
+ // The caller becomes the owner of the QuotaObject, that is, the caller is
+ // is responsible to delete it when the last reference is removed.
+ return result.forget();
+}
+
+already_AddRefed<QuotaObject>
+QuotaManager::GetQuotaObject(PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ const nsAString& aPath)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ rv = file->InitWithPath(aPath);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ return GetQuotaObject(aPersistenceType, aGroup, aOrigin, file);
+}
+
+void
+QuotaManager::AbortOperationsForProcess(ContentParentId aContentParentId)
+{
+ AssertIsOnOwningThread();
+
+ for (RefPtr<Client>& client : mClients) {
+ client->AbortOperationsForProcess(aContentParentId);
+ }
+}
+
+nsresult
+QuotaManager::GetDirectoryForOrigin(PersistenceType aPersistenceType,
+ const nsACString& aASCIIOrigin,
+ nsIFile** aDirectory) const
+{
+ nsresult rv;
+ nsCOMPtr<nsIFile> directory =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = directory->InitWithPath(GetStoragePath(aPersistenceType));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString originSanitized(aASCIIOrigin);
+ SanitizeOriginString(originSanitized);
+
+ rv = directory->Append(NS_ConvertASCIItoUTF16(originSanitized));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ directory.forget(aDirectory);
+ return NS_OK;
+}
+
+nsresult
+QuotaManager::RestoreDirectoryMetadata2(nsIFile* aDirectory, bool aPersistent)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aDirectory);
+ MOZ_ASSERT(mStorageInitialized);
+
+ RefPtr<RestoreDirectoryMetadata2Helper> helper =
+ new RestoreDirectoryMetadata2Helper(aDirectory, aPersistent);
+
+ nsresult rv = helper->RestoreMetadata2File();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+QuotaManager::GetDirectoryMetadata2(nsIFile* aDirectory,
+ int64_t* aTimestamp,
+ nsACString& aSuffix,
+ nsACString& aGroup,
+ nsACString& aOrigin,
+ bool* aIsApp)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aDirectory);
+ MOZ_ASSERT(aTimestamp);
+ MOZ_ASSERT(aIsApp);
+ MOZ_ASSERT(mStorageInitialized);
+
+ nsCOMPtr<nsIBinaryInputStream> binaryStream;
+ nsresult rv = GetBinaryInputStream(aDirectory,
+ NS_LITERAL_STRING(METADATA_V2_FILE_NAME),
+ getter_AddRefs(binaryStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint64_t timestamp;
+ rv = binaryStream->Read64(&timestamp);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool persisted;
+ rv = binaryStream->ReadBoolean(&persisted);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ uint32_t reservedData1;
+ rv = binaryStream->Read32(&reservedData1);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ uint32_t reservedData2;
+ rv = binaryStream->Read32(&reservedData2);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCString suffix;
+ rv = binaryStream->ReadCString(suffix);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCString group;
+ rv = binaryStream->ReadCString(group);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString origin;
+ rv = binaryStream->ReadCString(origin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isApp;
+ rv = binaryStream->ReadBoolean(&isApp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ *aTimestamp = timestamp;
+ aSuffix = suffix;
+ aGroup = group;
+ aOrigin = origin;
+ *aIsApp = isApp;
+ return NS_OK;
+}
+
+nsresult
+QuotaManager::GetDirectoryMetadata2WithRestore(nsIFile* aDirectory,
+ bool aPersistent,
+ int64_t* aTimestamp,
+ nsACString& aSuffix,
+ nsACString& aGroup,
+ nsACString& aOrigin,
+ bool* aIsApp)
+{
+ nsresult rv = GetDirectoryMetadata2(aDirectory,
+ aTimestamp,
+ aSuffix,
+ aGroup,
+ aOrigin,
+ aIsApp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ rv = RestoreDirectoryMetadata2(aDirectory, aPersistent);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = GetDirectoryMetadata2(aDirectory,
+ aTimestamp,
+ aSuffix,
+ aGroup,
+ aOrigin,
+ aIsApp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+QuotaManager::GetDirectoryMetadata2(nsIFile* aDirectory, int64_t* aTimestamp)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aDirectory);
+ MOZ_ASSERT(aTimestamp);
+ MOZ_ASSERT(mStorageInitialized);
+
+ nsCOMPtr<nsIBinaryInputStream> binaryStream;
+ nsresult rv = GetBinaryInputStream(aDirectory,
+ NS_LITERAL_STRING(METADATA_V2_FILE_NAME),
+ 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;
+}
+
+nsresult
+QuotaManager::GetDirectoryMetadata2WithRestore(nsIFile* aDirectory,
+ bool aPersistent,
+ int64_t* aTimestamp)
+{
+ nsresult rv = GetDirectoryMetadata2(aDirectory, aTimestamp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ rv = RestoreDirectoryMetadata2(aDirectory, aPersistent);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = GetDirectoryMetadata2(aDirectory, aTimestamp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+QuotaManager::InitializeRepository(PersistenceType aPersistenceType)
+{
+ MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_TEMPORARY ||
+ aPersistenceType == PERSISTENCE_TYPE_DEFAULT);
+
+ 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);
+
+ bool isDirectory;
+ rv = childDirectory->IsDirectory(&isDirectory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!isDirectory) {
+ nsString leafName;
+ rv = childDirectory->GetLeafName(leafName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (leafName.EqualsLiteral(DSSTORE_FILE_NAME)) {
+ continue;
+ }
+
+ QM_WARNING("Something (%s) in the repository that doesn't belong!",
+ NS_ConvertUTF16toUTF8(leafName).get());
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ int64_t timestamp;
+ nsCString suffix;
+ nsCString group;
+ nsCString origin;
+ bool isApp;
+ rv = GetDirectoryMetadata2WithRestore(childDirectory,
+ /* aPersistent */ false,
+ &timestamp,
+ suffix,
+ group,
+ origin,
+ &isApp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (IsTreatedAsPersistent(aPersistenceType, isApp)) {
+ continue;
+ }
+
+ rv = InitializeOrigin(aPersistenceType, group, origin, isApp, timestamp,
+ childDirectory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+namespace {
+
+// The Cache API was creating top level morgue directories by accident for
+// a short time in nightly. This unfortunately prevents all storage from
+// working. So recover these profiles by removing these corrupt directories.
+// This should be removed at some point in the future.
+bool
+MaybeRemoveCorruptDirectory(const nsAString& aLeafName, nsIFile* aDir)
+{
+#ifdef NIGHTLY_BUILD
+ MOZ_ASSERT(aDir);
+
+ if (aLeafName != NS_LITERAL_STRING("morgue")) {
+ return false;
+ }
+
+ NS_WARNING("QuotaManager removing corrupt morgue directory!");
+
+ nsresult rv = aDir->Remove(true /* recursive */);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ return true;
+#else
+ return false;
+#endif // NIGHTLY_BUILD
+}
+
+} // namespace
+
+nsresult
+QuotaManager::InitializeOrigin(PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ bool aIsApp,
+ int64_t aAccessTime,
+ nsIFile* aDirectory)
+{
+ AssertIsOnIOThread();
+
+ nsresult rv;
+
+ bool trackQuota = IsQuotaEnforced(aPersistenceType, aOrigin, aIsApp);
+
+ // 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 (trackQuota) {
+ usageInfo = new UsageInfo();
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ rv = aDirectory->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> file = do_QueryInterface(entry);
+ NS_ENSURE_TRUE(file, NS_NOINTERFACE);
+
+ nsString leafName;
+ rv = file->GetLeafName(leafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (leafName.EqualsLiteral(METADATA_FILE_NAME) ||
+ leafName.EqualsLiteral(METADATA_V2_FILE_NAME) ||
+ leafName.EqualsLiteral(DSSTORE_FILE_NAME)) {
+ continue;
+ }
+
+ bool isDirectory;
+ rv = file->IsDirectory(&isDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!isDirectory) {
+ NS_WARNING("Unknown file found!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (MaybeRemoveCorruptDirectory(leafName, file)) {
+ continue;
+ }
+
+ Client::Type clientType;
+ rv = Client::TypeFromText(leafName, clientType);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Unknown directory found!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ Atomic<bool> dummy(false);
+ rv = mClients[clientType]->InitOrigin(aPersistenceType,
+ aGroup,
+ aOrigin,
+ /* aCanceled */ dummy,
+ usageInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (trackQuota) {
+ InitQuotaForOrigin(aPersistenceType, aGroup, aOrigin, aIsApp,
+ usageInfo->TotalUsage(), aAccessTime);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+QuotaManager::MaybeUpgradeIndexedDBDirectory()
+{
+ AssertIsOnIOThread();
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> indexedDBDir =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = indexedDBDir->InitWithPath(mIndexedDBPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ rv = indexedDBDir->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!exists) {
+ // Nothing to upgrade.
+ return NS_OK;
+ }
+
+ bool isDirectory;
+ rv = indexedDBDir->IsDirectory(&isDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!isDirectory) {
+ NS_WARNING("indexedDB entry is not a directory!");
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> persistentStorageDir =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = persistentStorageDir->InitWithPath(mStoragePath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = persistentStorageDir->Append(NS_LITERAL_STRING(PERSISTENT_DIRECTORY_NAME));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = persistentStorageDir->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (exists) {
+ NS_WARNING("indexedDB directory shouldn't exist after the upgrade!");
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> storageDir;
+ rv = persistentStorageDir->GetParent(getter_AddRefs(storageDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // MoveTo() is atomic if the move happens on the same volume which should
+ // be our case, so even if we crash in the middle of the operation nothing
+ // breaks next time we try to initialize.
+ // However there's a theoretical possibility that the indexedDB directory
+ // is on different volume, but it should be rare enough that we don't have
+ // to worry about it.
+ rv = indexedDBDir->MoveTo(storageDir, NS_LITERAL_STRING(PERSISTENT_DIRECTORY_NAME));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+QuotaManager::MaybeUpgradePersistentStorageDirectory()
+{
+ AssertIsOnIOThread();
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> persistentStorageDir =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = persistentStorageDir->InitWithPath(mStoragePath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = persistentStorageDir->Append(NS_LITERAL_STRING(PERSISTENT_DIRECTORY_NAME));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool exists;
+ rv = persistentStorageDir->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!exists) {
+ // Nothing to upgrade.
+ return NS_OK;
+ }
+
+ bool isDirectory;
+ rv = persistentStorageDir->IsDirectory(&isDirectory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!isDirectory) {
+ NS_WARNING("persistent entry is not a directory!");
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> defaultStorageDir =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = defaultStorageDir->InitWithPath(mDefaultStoragePath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = defaultStorageDir->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (exists) {
+ NS_WARNING("storage/persistent shouldn't exist after the upgrade!");
+ return NS_OK;
+ }
+
+ // Create real metadata files for origin directories in persistent storage.
+ RefPtr<CreateOrUpgradeDirectoryMetadataHelper> helper =
+ new CreateOrUpgradeDirectoryMetadataHelper(persistentStorageDir,
+ /* aPersistent */ true);
+
+ rv = helper->CreateOrUpgradeMetadataFiles();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Upgrade metadata files for origin directories in temporary storage.
+ nsCOMPtr<nsIFile> temporaryStorageDir =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = temporaryStorageDir->InitWithPath(mTemporaryStoragePath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = temporaryStorageDir->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (exists) {
+ rv = temporaryStorageDir->IsDirectory(&isDirectory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!isDirectory) {
+ NS_WARNING("temporary entry is not a directory!");
+ return NS_OK;
+ }
+
+ helper =
+ new CreateOrUpgradeDirectoryMetadataHelper(temporaryStorageDir,
+ /* aPersistent */ false);
+
+ rv = helper->CreateOrUpgradeMetadataFiles();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ // And finally rename persistent to default.
+ rv = persistentStorageDir->RenameTo(nullptr, NS_LITERAL_STRING(DEFAULT_DIRECTORY_NAME));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+QuotaManager::MaybeRemoveOldDirectories()
+{
+ AssertIsOnIOThread();
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> indexedDBDir =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = indexedDBDir->InitWithPath(mIndexedDBPath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool exists;
+ rv = indexedDBDir->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (exists) {
+ QM_WARNING("Deleting old <profile>/indexedDB directory!");
+
+ rv = indexedDBDir->Remove(/* aRecursive */ true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ nsCOMPtr<nsIFile> persistentStorageDir =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = persistentStorageDir->InitWithPath(mStoragePath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = persistentStorageDir->Append(NS_LITERAL_STRING(PERSISTENT_DIRECTORY_NAME));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = persistentStorageDir->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (exists) {
+ QM_WARNING("Deleting old <profile>/storage/persistent directory!");
+
+ rv = persistentStorageDir->Remove(/* aRecursive */ true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+QuotaManager::UpgradeStorageFrom0ToCurrent(mozIStorageConnection* aConnection)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aConnection);
+
+ nsresult rv;
+
+ for (const PersistenceType persistenceType : kAllPersistenceTypes) {
+ nsCOMPtr<nsIFile> directory =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = directory->InitWithPath(GetStoragePath(persistenceType));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool persistent = persistenceType == PERSISTENCE_TYPE_PERSISTENT;
+ RefPtr<UpgradeDirectoryMetadataFrom1To2Helper> helper =
+ new UpgradeDirectoryMetadataFrom1To2Helper(directory, persistent);
+
+ rv = helper->UpgradeMetadataFiles();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+#ifdef DEBUG
+ {
+ int32_t storageVersion;
+ rv = aConnection->GetSchemaVersion(&storageVersion);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(storageVersion == 0);
+ }
+#endif
+
+ rv = aConnection->SetSchemaVersion(kStorageVersion);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+#if 0
+nsresult
+QuotaManager::UpgradeStorageFrom1To2(mozIStorageConnection* aConnection)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aConnection);
+
+ nsresult rv = aConnection->SetSchemaVersion(MakeStorageVersion(2, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+#endif
+
+nsresult
+QuotaManager::EnsureStorageIsInitialized()
+{
+ AssertIsOnIOThread();
+
+ if (mStorageInitialized) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> storageFile =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = storageFile->InitWithPath(mBasePath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = storageFile->Append(NS_LITERAL_STRING(STORAGE_FILE_NAME));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<mozIStorageService> ss =
+ do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<mozIStorageConnection> connection;
+ rv = ss->OpenUnsharedDatabase(storageFile, getter_AddRefs(connection));
+ if (rv == NS_ERROR_FILE_CORRUPTED) {
+ // Nuke the database file.
+ rv = storageFile->Remove(false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = ss->OpenUnsharedDatabase(storageFile, getter_AddRefs(connection));
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // We want extra durability for this important file.
+ rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "PRAGMA synchronous = EXTRA;"
+ ));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Check to make sure that the storage version is correct.
+ int32_t storageVersion;
+ rv = connection->GetSchemaVersion(&storageVersion);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (GetMajorStorageVersion(storageVersion) > kMajorStorageVersion) {
+ NS_WARNING("Unable to initialize storage, version is too high!");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (storageVersion < kStorageVersion) {
+ const bool newDatabase = !storageVersion;
+
+ if (newDatabase) {
+ // Set the page size first.
+ if (kSQLitePageSizeOverride) {
+ rv = connection->ExecuteSimpleSQL(
+ nsPrintfCString("PRAGMA page_size = %lu;", kSQLitePageSizeOverride)
+ );
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ }
+
+ mozStorageTransaction transaction(connection, false,
+ mozIStorageConnection::TRANSACTION_IMMEDIATE);
+ if (newDatabase) {
+ rv = MaybeUpgradeIndexedDBDirectory();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = MaybeUpgradePersistentStorageDirectory();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = MaybeRemoveOldDirectories();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = UpgradeStorageFrom0ToCurrent(connection);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(NS_SUCCEEDED(connection->GetSchemaVersion(&storageVersion)));
+ MOZ_ASSERT(storageVersion == kStorageVersion);
+ } else {
+ // This logic needs to change next time we change the storage!
+ static_assert(kStorageVersion == int32_t((1 << 16) + 0),
+ "Upgrade function needed due to storage version increase.");
+
+ while (storageVersion != kStorageVersion) {
+ /* if (storageVersion == MakeStorageVersion(1, 0)) {
+ rv = UpgradeStorageFrom1To2(connection);
+ } else */ {
+ NS_WARNING("Unable to initialize storage, no upgrade path is "
+ "available!");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = connection->GetSchemaVersion(&storageVersion);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ MOZ_ASSERT(storageVersion == kStorageVersion);
+ }
+
+ rv = transaction.Commit();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ mStorageInitialized = true;
+
+ return NS_OK;
+}
+
+void
+QuotaManager::OpenDirectory(PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ bool aIsApp,
+ Client::Type aClientType,
+ bool aExclusive,
+ OpenDirectoryListener* aOpenListener)
+{
+ AssertIsOnOwningThread();
+
+ RefPtr<DirectoryLockImpl> lock =
+ CreateDirectoryLock(Nullable<PersistenceType>(aPersistenceType),
+ aGroup,
+ OriginScope::FromOrigin(aOrigin),
+ Nullable<bool>(aIsApp),
+ Nullable<Client::Type>(aClientType),
+ aExclusive,
+ false,
+ aOpenListener);
+ MOZ_ASSERT(lock);
+}
+
+void
+QuotaManager::OpenDirectoryInternal(Nullable<PersistenceType> aPersistenceType,
+ const OriginScope& aOriginScope,
+ Nullable<Client::Type> aClientType,
+ bool aExclusive,
+ OpenDirectoryListener* aOpenListener)
+{
+ AssertIsOnOwningThread();
+
+ RefPtr<DirectoryLockImpl> lock =
+ CreateDirectoryLock(aPersistenceType,
+ EmptyCString(),
+ aOriginScope,
+ Nullable<bool>(),
+ Nullable<Client::Type>(aClientType),
+ aExclusive,
+ true,
+ aOpenListener);
+ MOZ_ASSERT(lock);
+
+ if (!aExclusive) {
+ return;
+ }
+
+ // All the locks that block this new exclusive lock need to be invalidated.
+ // We also need to notify clients to abort operations for them.
+ AutoTArray<nsAutoPtr<nsTHashtable<nsCStringHashKey>>,
+ Client::TYPE_MAX> origins;
+ origins.SetLength(Client::TYPE_MAX);
+
+ const nsTArray<DirectoryLockImpl*>& blockedOnLocks =
+ lock->GetBlockedOnLocks();
+
+ for (DirectoryLockImpl* blockedOnLock : blockedOnLocks) {
+ blockedOnLock->Invalidate();
+
+ if (!blockedOnLock->IsInternal()) {
+ MOZ_ASSERT(!blockedOnLock->GetClientType().IsNull());
+ Client::Type clientType = blockedOnLock->GetClientType().Value();
+ MOZ_ASSERT(clientType < Client::TYPE_MAX);
+
+ const OriginScope& originScope = blockedOnLock->GetOriginScope();
+ MOZ_ASSERT(originScope.IsOrigin());
+ MOZ_ASSERT(!originScope.GetOrigin().IsEmpty());
+
+ nsAutoPtr<nsTHashtable<nsCStringHashKey>>& origin = origins[clientType];
+ if (!origin) {
+ origin = new nsTHashtable<nsCStringHashKey>();
+ }
+ origin->PutEntry(originScope.GetOrigin());
+ }
+ }
+
+ for (uint32_t index : MakeRange(uint32_t(Client::TYPE_MAX))) {
+ if (origins[index]) {
+ for (auto iter = origins[index]->Iter(); !iter.Done(); iter.Next()) {
+ MOZ_ASSERT(mClients[index]);
+
+ mClients[index]->AbortOperations(iter.Get()->GetKey());
+ }
+ }
+ }
+}
+
+nsresult
+QuotaManager::EnsureOriginIsInitialized(PersistenceType aPersistenceType,
+ const nsACString& aSuffix,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ bool aIsApp,
+ nsIFile** aDirectory)
+{
+ AssertIsOnIOThread();
+
+ nsresult rv = EnsureStorageIsInitialized();
+ 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 (IsTreatedAsPersistent(aPersistenceType, aIsApp)) {
+ if (mInitializedOrigins.Contains(OriginKey(aPersistenceType, aOrigin))) {
+ directory.forget(aDirectory);
+ return NS_OK;
+ }
+ } else if (!mTemporaryStorageInitialized) {
+ rv = InitializeRepository(aPersistenceType);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // We have to cleanup partially initialized quota.
+ RemoveQuota();
+
+ return rv;
+ }
+
+ rv = InitializeRepository(ComplementaryPersistenceType(aPersistenceType));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // We have to cleanup partially initialized quota.
+ RemoveQuota();
+
+ return rv;
+ }
+
+ if (gFixedLimitKB >= 0) {
+ mTemporaryStorageLimit = static_cast<uint64_t>(gFixedLimitKB) * 1024;
+ }
+ else {
+ 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();
+ }
+
+ int64_t timestamp;
+
+ bool created;
+ rv = EnsureDirectory(directory, &created);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (IsTreatedAsPersistent(aPersistenceType, aIsApp)) {
+ if (created) {
+ timestamp = PR_Now();
+
+ rv = CreateDirectoryMetadata(directory,
+ timestamp,
+ aSuffix,
+ aGroup,
+ aOrigin,
+ aIsApp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = CreateDirectoryMetadata2(directory,
+ timestamp,
+ aSuffix,
+ aGroup,
+ aOrigin,
+ aIsApp);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ bool persistent = aPersistenceType == PERSISTENCE_TYPE_PERSISTENT;
+ rv = GetDirectoryMetadata2WithRestore(directory,
+ persistent,
+ &timestamp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(timestamp <= PR_Now());
+ }
+
+ rv = InitializeOrigin(aPersistenceType, aGroup, aOrigin, aIsApp, timestamp,
+ directory);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mInitializedOrigins.AppendElement(OriginKey(aPersistenceType, aOrigin));
+ } else if (created) {
+ timestamp = PR_Now();
+
+ rv = CreateDirectoryMetadata(directory,
+ timestamp,
+ aSuffix,
+ aGroup,
+ aOrigin,
+ aIsApp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = CreateDirectoryMetadata2(directory,
+ timestamp,
+ aSuffix,
+ aGroup,
+ aOrigin,
+ aIsApp);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = InitializeOrigin(aPersistenceType, aGroup, aOrigin, aIsApp, timestamp,
+ directory);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ directory.forget(aDirectory);
+ return NS_OK;
+}
+
+void
+QuotaManager::OriginClearCompleted(PersistenceType aPersistenceType,
+ const nsACString& aOrigin,
+ bool aIsApp)
+{
+ AssertIsOnIOThread();
+
+ if (IsTreatedAsPersistent(aPersistenceType, aIsApp)) {
+ mInitializedOrigins.RemoveElement(OriginKey(aPersistenceType, aOrigin));
+ }
+
+ for (uint32_t index = 0; index < Client::TYPE_MAX; index++) {
+ mClients[index]->OnOriginClearCompleted(aPersistenceType, aOrigin);
+ }
+}
+
+void
+QuotaManager::ResetOrClearCompleted()
+{
+ AssertIsOnIOThread();
+
+ mInitializedOrigins.Clear();
+ mTemporaryStorageInitialized = false;
+ mStorageInitialized = false;
+
+ ReleaseIOThreadObjects();
+}
+
+Client*
+QuotaManager::GetClient(Client::Type aClientType)
+{
+ MOZ_ASSERT(aClientType >= Client::IDB);
+ MOZ_ASSERT(aClientType < Client::TYPE_MAX);
+
+ return mClients.ElementAt(aClientType);
+}
+
+uint64_t
+QuotaManager::GetGroupLimit() const
+{
+ MOZ_ASSERT(mTemporaryStorageInitialized);
+
+ // To avoid one group evicting all the rest, limit the amount any one group
+ // can use to 20%. To prevent individual sites from using exorbitant amounts
+ // of storage where there is a lot of free space, cap the group limit to 2GB.
+ uint64_t x = std::min<uint64_t>(mTemporaryStorageLimit * .20, 2 GB);
+
+ // In low-storage situations, make an exception (while not exceeding the total
+ // storage limit).
+ return std::min<uint64_t>(mTemporaryStorageLimit,
+ std::max<uint64_t>(x, 10 MB));
+}
+
+void
+QuotaManager::GetGroupUsageAndLimit(const nsACString& aGroup,
+ UsageInfo* aUsageInfo)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aUsageInfo);
+
+ {
+ MutexAutoLock lock(mQuotaMutex);
+
+ aUsageInfo->SetLimit(GetGroupLimit());
+ aUsageInfo->ResetUsage();
+
+ GroupInfoPair* pair;
+ if (!mGroupInfoPairs.Get(aGroup, &pair)) {
+ return;
+ }
+
+ // Calculate temporary group usage
+ RefPtr<GroupInfo> temporaryGroupInfo =
+ pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
+ if (temporaryGroupInfo) {
+ aUsageInfo->AppendToDatabaseUsage(temporaryGroupInfo->mUsage);
+ }
+
+ // Calculate default group usage
+ RefPtr<GroupInfo> defaultGroupInfo =
+ pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
+ if (defaultGroupInfo) {
+ aUsageInfo->AppendToDatabaseUsage(defaultGroupInfo->mUsage);
+ }
+ }
+}
+
+// static
+void
+QuotaManager::GetStorageId(PersistenceType aPersistenceType,
+ const nsACString& aOrigin,
+ Client::Type aClientType,
+ nsACString& aDatabaseId)
+{
+ nsAutoCString str;
+ str.AppendInt(aPersistenceType);
+ str.Append('*');
+ str.Append(aOrigin);
+ str.Append('*');
+ str.AppendInt(aClientType);
+
+ aDatabaseId = str;
+}
+
+// static
+nsresult
+QuotaManager::GetInfoFromPrincipal(nsIPrincipal* aPrincipal,
+ nsACString* aSuffix,
+ nsACString* aGroup,
+ nsACString* aOrigin,
+ bool* aIsApp)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPrincipal);
+
+ if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
+ GetInfoForChrome(aSuffix, aGroup, aOrigin, aIsApp);
+ return NS_OK;
+ }
+
+
+ if (aPrincipal->GetIsNullPrincipal()) {
+ NS_WARNING("IndexedDB not supported from this principal!");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCString origin;
+ nsresult rv = aPrincipal->GetOrigin(origin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (origin.EqualsLiteral(kChromeOrigin)) {
+ NS_WARNING("Non-chrome principal can't use chrome origin!");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCString suffix;
+ BasePrincipal::Cast(aPrincipal)->OriginAttributesRef().CreateSuffix(suffix);
+
+ if (aSuffix)
+ {
+ aSuffix->Assign(suffix);
+ }
+
+ if (aGroup) {
+ nsCString baseDomain;
+ rv = aPrincipal->GetBaseDomain(baseDomain);
+ if (NS_FAILED(rv)) {
+ // A hack for JetPack.
+
+ nsCOMPtr<nsIURI> uri;
+ rv = aPrincipal->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isIndexedDBURI = false;
+ rv = uri->SchemeIs("indexedDB", &isIndexedDBURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (isIndexedDBURI) {
+ rv = NS_OK;
+ }
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (baseDomain.IsEmpty()) {
+ aGroup->Assign(origin);
+ } else {
+ aGroup->Assign(baseDomain + suffix);
+ }
+ }
+
+ if (aOrigin) {
+ aOrigin->Assign(origin);
+ }
+
+ if (aIsApp) {
+ *aIsApp = aPrincipal->GetAppStatus() !=
+ nsIPrincipal::APP_STATUS_NOT_INSTALLED;
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult
+QuotaManager::GetInfoFromWindow(nsPIDOMWindowOuter* aWindow,
+ nsACString* aSuffix,
+ nsACString* aGroup,
+ nsACString* aOrigin,
+ bool* aIsApp)
+{
+ 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, aSuffix, aGroup, aOrigin, aIsApp);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+// static
+void
+QuotaManager::GetInfoForChrome(nsACString* aSuffix,
+ nsACString* aGroup,
+ nsACString* aOrigin,
+ bool* aIsApp)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(nsContentUtils::LegacyIsCallerChromeOrNativeCode());
+
+ if (aSuffix) {
+ aSuffix->Assign(EmptyCString());
+ }
+ if (aGroup) {
+ ChromeOrigin(*aGroup);
+ }
+ if (aOrigin) {
+ ChromeOrigin(*aOrigin);
+ }
+ if (aIsApp) {
+ *aIsApp = false;
+ }
+}
+
+// static
+bool
+QuotaManager::IsOriginInternal(const nsACString& aOrigin)
+{
+ // The first prompt is not required for these origins.
+ if (aOrigin.EqualsLiteral(kChromeOrigin) ||
+ StringBeginsWith(aOrigin, nsDependentCString(kAboutHomeOriginPrefix)) ||
+ StringBeginsWith(aOrigin, nsDependentCString(kIndexedDBOriginPrefix)) ||
+ StringBeginsWith(aOrigin, nsDependentCString(kResourceOriginPrefix))) {
+ return true;
+ }
+
+ return false;
+}
+
+// static
+bool
+QuotaManager::IsFirstPromptRequired(PersistenceType aPersistenceType,
+ const nsACString& aOrigin,
+ bool aIsApp)
+{
+ if (IsTreatedAsTemporary(aPersistenceType, aIsApp)) {
+ return false;
+ }
+
+ return !IsOriginInternal(aOrigin);
+}
+
+// static
+bool
+QuotaManager::IsQuotaEnforced(PersistenceType aPersistenceType,
+ const nsACString& aOrigin,
+ bool aIsApp)
+{
+ return IsTreatedAsTemporary(aPersistenceType, aIsApp);
+}
+
+// static
+void
+QuotaManager::ChromeOrigin(nsACString& aOrigin)
+{
+ aOrigin.AssignLiteral(kChromeOrigin);
+}
+
+uint64_t
+QuotaManager::LockedCollectOriginsForEviction(
+ uint64_t aMinSizeToBeFreed,
+ nsTArray<RefPtr<DirectoryLockImpl>>& aLocks)
+{
+ mQuotaMutex.AssertCurrentThreadOwns();
+
+ RefPtr<CollectOriginsHelper> helper =
+ new CollectOriginsHelper(mQuotaMutex, aMinSizeToBeFreed);
+
+ // Unlock while calling out to XPCOM (code behind the dispatch method needs
+ // to acquire its own lock which can potentially lead to a deadlock and it
+ // also calls an observer that can do various stuff like IO, so it's better
+ // to not hold our mutex while that happens).
+ {
+ MutexAutoUnlock autoUnlock(mQuotaMutex);
+
+ MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(helper, NS_DISPATCH_NORMAL));
+ }
+
+ return helper->BlockAndReturnOriginsForEviction(aLocks);
+}
+
+void
+QuotaManager::LockedRemoveQuotaForOrigin(PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin)
+{
+ mQuotaMutex.AssertCurrentThreadOwns();
+ MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
+
+ GroupInfoPair* pair;
+ mGroupInfoPairs.Get(aGroup, &pair);
+
+ if (!pair) {
+ return;
+ }
+
+ RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
+ if (groupInfo) {
+ groupInfo->LockedRemoveOriginInfo(aOrigin);
+
+ if (!groupInfo->LockedHasOriginInfos()) {
+ pair->LockedClearGroupInfo(aPersistenceType);
+
+ if (!pair->LockedHasGroupInfos()) {
+ mGroupInfoPairs.Remove(aGroup);
+ }
+ }
+ }
+}
+
+void
+QuotaManager::CheckTemporaryStorageLimits()
+{
+ AssertIsOnIOThread();
+
+ nsTArray<OriginInfo*> doomedOriginInfos;
+ {
+ MutexAutoLock lock(mQuotaMutex);
+
+ for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) {
+ GroupInfoPair* pair = iter.UserData();
+
+ MOZ_ASSERT(!iter.Key().IsEmpty(), "Empty key!");
+ MOZ_ASSERT(pair, "Null pointer!");
+
+ uint64_t groupUsage = 0;
+
+ RefPtr<GroupInfo> temporaryGroupInfo =
+ pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
+ if (temporaryGroupInfo) {
+ groupUsage += temporaryGroupInfo->mUsage;
+ }
+
+ RefPtr<GroupInfo> defaultGroupInfo =
+ pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
+ if (defaultGroupInfo) {
+ groupUsage += defaultGroupInfo->mUsage;
+ }
+
+ if (groupUsage > 0) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ MOZ_ASSERT(quotaManager, "Shouldn't be null!");
+
+ if (groupUsage > quotaManager->GetGroupLimit()) {
+ nsTArray<OriginInfo*> originInfos;
+ if (temporaryGroupInfo) {
+ originInfos.AppendElements(temporaryGroupInfo->mOriginInfos);
+ }
+ if (defaultGroupInfo) {
+ originInfos.AppendElements(defaultGroupInfo->mOriginInfos);
+ }
+ originInfos.Sort(OriginInfoLRUComparator());
+
+ for (uint32_t i = 0; i < originInfos.Length(); i++) {
+ OriginInfo* originInfo = originInfos[i];
+
+ doomedOriginInfos.AppendElement(originInfo);
+ groupUsage -= originInfo->mUsage;
+
+ if (groupUsage <= quotaManager->GetGroupLimit()) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ uint64_t usage = 0;
+ for (uint32_t index = 0; index < doomedOriginInfos.Length(); index++) {
+ usage += doomedOriginInfos[index]->mUsage;
+ }
+
+ if (mTemporaryStorageUsage - usage > mTemporaryStorageLimit) {
+ nsTArray<OriginInfo*> originInfos;
+
+ for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) {
+ GroupInfoPair* pair = iter.UserData();
+
+ MOZ_ASSERT(!iter.Key().IsEmpty(), "Empty key!");
+ MOZ_ASSERT(pair, "Null pointer!");
+
+ RefPtr<GroupInfo> groupInfo =
+ pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
+ if (groupInfo) {
+ originInfos.AppendElements(groupInfo->mOriginInfos);
+ }
+
+ groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
+ if (groupInfo) {
+ originInfos.AppendElements(groupInfo->mOriginInfos);
+ }
+ }
+
+ for (uint32_t index = originInfos.Length(); index > 0; index--) {
+ if (doomedOriginInfos.Contains(originInfos[index - 1])) {
+ originInfos.RemoveElementAt(index - 1);
+ }
+ }
+
+ originInfos.Sort(OriginInfoLRUComparator());
+
+ for (uint32_t i = 0; i < originInfos.Length(); i++) {
+ if (mTemporaryStorageUsage - usage <= mTemporaryStorageLimit) {
+ originInfos.TruncateLength(i);
+ break;
+ }
+
+ usage += originInfos[i]->mUsage;
+ }
+
+ doomedOriginInfos.AppendElements(originInfos);
+ }
+ }
+
+ for (uint32_t index = 0; index < doomedOriginInfos.Length(); index++) {
+ OriginInfo* doomedOriginInfo = doomedOriginInfos[index];
+
+ DeleteFilesForOrigin(doomedOriginInfo->mGroupInfo->mPersistenceType,
+ doomedOriginInfo->mOrigin);
+ }
+
+ 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;
+ bool isApp = doomedOriginInfo->mIsApp;
+ LockedRemoveQuotaForOrigin(persistenceType, group, origin);
+
+#ifdef DEBUG
+ doomedOriginInfos[index] = nullptr;
+#endif
+
+ doomedOrigins.AppendElement(OriginParams(persistenceType, origin, isApp));
+ }
+ }
+
+ for (const OriginParams& doomedOrigin : doomedOrigins) {
+ OriginClearCompleted(doomedOrigin.mPersistenceType,
+ doomedOrigin.mOrigin,
+ doomedOrigin.mIsApp);
+ }
+}
+
+void
+QuotaManager::DeleteFilesForOrigin(PersistenceType aPersistenceType,
+ const nsACString& aOrigin)
+{
+ nsCOMPtr<nsIFile> directory;
+ 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<RefPtr<DirectoryLockImpl>>& aLocks)
+{
+ NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+ RefPtr<FinalizeOriginEvictionOp> op =
+ new FinalizeOriginEvictionOp(mOwningThread, aLocks);
+
+ if (IsOnIOThread()) {
+ op->RunOnIOThreadImmediately();
+ } else {
+ op->Dispatch();
+ }
+}
+
+void
+QuotaManager::ShutdownTimerCallback(nsITimer* aTimer, void* aClosure)
+{
+ AssertIsOnBackgroundThread();
+
+ auto quotaManager = static_cast<QuotaManager*>(aClosure);
+ MOZ_ASSERT(quotaManager);
+
+ NS_WARNING("Some storage operations are taking longer than expected "
+ "during shutdown and will be aborted!");
+
+ // Abort all operations.
+ for (RefPtr<Client>& client : quotaManager->mClients) {
+ client->AbortOperations(NullCString());
+ }
+}
+
+auto
+QuotaManager::GetDirectoryLockTable(PersistenceType aPersistenceType)
+ -> DirectoryLockTable&
+{
+ switch (aPersistenceType) {
+ case PERSISTENCE_TYPE_TEMPORARY:
+ return mTemporaryDirectoryLockTable;
+ case PERSISTENCE_TYPE_DEFAULT:
+ return mDefaultDirectoryLockTable;
+
+ case PERSISTENCE_TYPE_PERSISTENT:
+ case PERSISTENCE_TYPE_INVALID:
+ default:
+ MOZ_CRASH("Bad persistence type value!");
+ }
+}
+
+/*******************************************************************************
+ * Local class implementations
+ ******************************************************************************/
+
+void
+OriginInfo::LockedDecreaseUsage(int64_t aSize)
+{
+ AssertCurrentThreadOwnsQuotaMutex();
+
+ AssertNoUnderflow(mUsage, aSize);
+ mUsage -= aSize;
+
+ AssertNoUnderflow(mGroupInfo->mUsage, aSize);
+ mGroupInfo->mUsage -= aSize;
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, aSize);
+ quotaManager->mTemporaryStorageUsage -= aSize;
+}
+
+already_AddRefed<OriginInfo>
+GroupInfo::LockedGetOriginInfo(const nsACString& aOrigin)
+{
+ AssertCurrentThreadOwnsQuotaMutex();
+
+ for (RefPtr<OriginInfo>& originInfo : mOriginInfos) {
+ if (originInfo->mOrigin == aOrigin) {
+ RefPtr<OriginInfo> result = originInfo;
+ return result.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+void
+GroupInfo::LockedAddOriginInfo(OriginInfo* aOriginInfo)
+{
+ AssertCurrentThreadOwnsQuotaMutex();
+
+ NS_ASSERTION(!mOriginInfos.Contains(aOriginInfo),
+ "Replacing an existing entry!");
+ mOriginInfos.AppendElement(aOriginInfo);
+
+ AssertNoOverflow(mUsage, aOriginInfo->mUsage);
+ mUsage += aOriginInfo->mUsage;
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ AssertNoOverflow(quotaManager->mTemporaryStorageUsage, aOriginInfo->mUsage);
+ 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) {
+ AssertNoUnderflow(mUsage, mOriginInfos[index]->mUsage);
+ mUsage -= mOriginInfos[index]->mUsage;
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ AssertNoUnderflow(quotaManager->mTemporaryStorageUsage,
+ mOriginInfos[index]->mUsage);
+ quotaManager->mTemporaryStorageUsage -= mOriginInfos[index]->mUsage;
+
+ mOriginInfos.RemoveElementAt(index);
+
+ return;
+ }
+ }
+}
+
+void
+GroupInfo::LockedRemoveOriginInfos()
+{
+ AssertCurrentThreadOwnsQuotaMutex();
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ for (uint32_t index = mOriginInfos.Length(); index > 0; index--) {
+ OriginInfo* originInfo = mOriginInfos[index - 1];
+
+ AssertNoUnderflow(mUsage, originInfo->mUsage);
+ mUsage -= originInfo->mUsage;
+
+ AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, originInfo->mUsage);
+ quotaManager->mTemporaryStorageUsage -= originInfo->mUsage;
+
+ mOriginInfos.RemoveElementAt(index - 1);
+ }
+}
+
+RefPtr<GroupInfo>&
+GroupInfoPair::GetGroupInfoForPersistenceType(PersistenceType aPersistenceType)
+{
+ switch (aPersistenceType) {
+ case PERSISTENCE_TYPE_TEMPORARY:
+ return mTemporaryStorageGroupInfo;
+ case PERSISTENCE_TYPE_DEFAULT:
+ return mDefaultStorageGroupInfo;
+
+ case PERSISTENCE_TYPE_PERSISTENT:
+ case PERSISTENCE_TYPE_INVALID:
+ default:
+ MOZ_CRASH("Bad persistence type value!");
+ }
+}
+
+CollectOriginsHelper::CollectOriginsHelper(mozilla::Mutex& aMutex,
+ uint64_t aMinSizeToBeFreed)
+: mMinSizeToBeFreed(aMinSizeToBeFreed),
+ mMutex(aMutex),
+ mCondVar(aMutex, "CollectOriginsHelper::mCondVar"),
+ mSizeToBeFreed(0),
+ mWaiting(true)
+{
+ MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
+ mMutex.AssertCurrentThreadOwns();
+}
+
+int64_t
+CollectOriginsHelper::BlockAndReturnOriginsForEviction(
+ nsTArray<RefPtr<DirectoryLockImpl>>& aLocks)
+{
+ MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
+ mMutex.AssertCurrentThreadOwns();
+
+ while (mWaiting) {
+ mCondVar.Wait();
+ }
+
+ mLocks.SwapElements(aLocks);
+ return mSizeToBeFreed;
+}
+
+NS_IMETHODIMP
+CollectOriginsHelper::Run()
+{
+ AssertIsOnBackgroundThread();
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ NS_ASSERTION(quotaManager, "Shouldn't be null!");
+
+ // We use extra stack vars here to avoid race detector warnings (the same
+ // memory accessed with and without the lock held).
+ nsTArray<RefPtr<DirectoryLockImpl>> locks;
+ uint64_t sizeToBeFreed =
+ quotaManager->CollectOriginsForEviction(mMinSizeToBeFreed, locks);
+
+ MutexAutoLock lock(mMutex);
+
+ NS_ASSERTION(mWaiting, "Huh?!");
+
+ mLocks.SwapElements(locks);
+ mSizeToBeFreed = sizeToBeFreed;
+ mWaiting = false;
+ mCondVar.Notify();
+
+ return NS_OK;
+}
+
+/*******************************************************************************
+ * OriginOperationBase
+ ******************************************************************************/
+
+NS_IMETHODIMP
+OriginOperationBase::Run()
+{
+ nsresult rv;
+
+ switch (mState) {
+ case State_Initial: {
+ rv = Init();
+ break;
+ }
+
+ case State_Initializing: {
+ rv = InitOnMainThread();
+ break;
+ }
+
+ case State_FinishingInit: {
+ rv = FinishInit();
+ break;
+ }
+
+ case State_CreatingQuotaManager: {
+ rv = QuotaManagerOpen();
+ break;
+ }
+
+ case State_DirectoryOpenPending: {
+ rv = DirectoryOpen();
+ break;
+ }
+
+ case State_DirectoryWorkOpen: {
+ rv = DirectoryWork();
+ break;
+ }
+
+ case State_UnblockingOpen: {
+ UnblockOpen();
+ return NS_OK;
+ }
+
+ default:
+ MOZ_CRASH("Bad state!");
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv)) && mState != State_UnblockingOpen) {
+ Finish(rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+OriginOperationBase::DirectoryOpen()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State_DirectoryOpenPending);
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ if (NS_WARN_IF(!quotaManager)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Must set this before dispatching otherwise we will race with the IO thread.
+ AdvanceState();
+
+ nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+void
+OriginOperationBase::Finish(nsresult aResult)
+{
+ if (NS_SUCCEEDED(mResultCode)) {
+ mResultCode = aResult;
+ }
+
+ // Must set mState before dispatching otherwise we will race with the main
+ // thread.
+ mState = State_UnblockingOpen;
+
+ MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
+}
+
+nsresult
+OriginOperationBase::Init()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State_Initial);
+
+ AdvanceState();
+
+ if (mNeedsMainThreadInit) {
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
+ } else {
+ AdvanceState();
+ MOZ_ALWAYS_SUCCEEDS(Run());
+ }
+
+ return NS_OK;
+}
+
+nsresult
+OriginOperationBase::InitOnMainThread()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mState == State_Initializing);
+
+ nsresult rv = DoInitOnMainThread();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ AdvanceState();
+
+ MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
+
+ return NS_OK;
+}
+
+nsresult
+OriginOperationBase::FinishInit()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State_FinishingInit);
+
+ if (QuotaManager::IsShuttingDown()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ AdvanceState();
+
+ if (mNeedsQuotaManagerInit && !QuotaManager::Get()) {
+ QuotaManager::GetOrCreate(this);
+ } else {
+ Open();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+OriginOperationBase::QuotaManagerOpen()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState == State_CreatingQuotaManager);
+
+ if (NS_WARN_IF(!QuotaManager::Get())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ Open();
+
+ return NS_OK;
+}
+
+nsresult
+OriginOperationBase::DirectoryWork()
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(mState == State_DirectoryWorkOpen);
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ if (NS_WARN_IF(!quotaManager)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+
+ if (mNeedsQuotaManagerInit) {
+ rv = quotaManager->EnsureStorageIsInitialized();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ rv = DoDirectoryWork(quotaManager);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Must set mState before dispatching otherwise we will race with the owning
+ // thread.
+ AdvanceState();
+
+ MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
+
+ return NS_OK;
+}
+
+void
+FinalizeOriginEvictionOp::Dispatch()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(GetState() == State_Initial);
+
+ SetState(State_DirectoryOpenPending);
+
+ MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
+}
+
+void
+FinalizeOriginEvictionOp::RunOnIOThreadImmediately()
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(GetState() == State_Initial);
+
+ SetState(State_DirectoryWorkOpen);
+
+ MOZ_ALWAYS_SUCCEEDS(this->Run());
+}
+
+void
+FinalizeOriginEvictionOp::Open()
+{
+ MOZ_CRASH("Shouldn't get here!");
+}
+
+nsresult
+FinalizeOriginEvictionOp::DoDirectoryWork(QuotaManager* aQuotaManager)
+{
+ AssertIsOnIOThread();
+
+ PROFILER_LABEL("Quota", "FinalizeOriginEvictionOp::DoDirectoryWork",
+ js::ProfileEntry::Category::OTHER);
+
+ for (RefPtr<DirectoryLockImpl>& lock : mLocks) {
+ aQuotaManager->OriginClearCompleted(lock->GetPersistenceType().Value(),
+ lock->GetOriginScope().GetOrigin(),
+ lock->GetIsApp().Value());
+ }
+
+ return NS_OK;
+}
+
+void
+FinalizeOriginEvictionOp::UnblockOpen()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(GetState() == State_UnblockingOpen);
+
+#ifdef DEBUG
+ NoteActorDestroyed();
+#endif
+
+ mLocks.Clear();
+
+ AdvanceState();
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(NormalOriginOperationBase, Runnable)
+
+void
+NormalOriginOperationBase::Open()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(GetState() == State_CreatingQuotaManager);
+ MOZ_ASSERT(QuotaManager::Get());
+
+ AdvanceState();
+
+ QuotaManager::Get()->OpenDirectoryInternal(mPersistenceType,
+ mOriginScope,
+ Nullable<Client::Type>(),
+ mExclusive,
+ this);
+}
+
+void
+NormalOriginOperationBase::UnblockOpen()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(GetState() == State_UnblockingOpen);
+
+ SendResults();
+
+ mDirectoryLock = nullptr;
+
+ AdvanceState();
+}
+
+void
+NormalOriginOperationBase::DirectoryLockAcquired(DirectoryLock* aLock)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aLock);
+ MOZ_ASSERT(GetState() == State_DirectoryOpenPending);
+ MOZ_ASSERT(!mDirectoryLock);
+
+ mDirectoryLock = aLock;
+
+ nsresult rv = DirectoryOpen();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Finish(rv);
+ return;
+ }
+}
+
+void
+NormalOriginOperationBase::DirectoryLockFailed()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(GetState() == State_DirectoryOpenPending);
+ MOZ_ASSERT(!mDirectoryLock);
+
+ Finish(NS_ERROR_FAILURE);
+}
+
+nsresult
+SaveOriginAccessTimeOp::DoDirectoryWork(QuotaManager* aQuotaManager)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(!mPersistenceType.IsNull());
+ MOZ_ASSERT(mOriginScope.IsOrigin());
+
+ PROFILER_LABEL("Quota", "SaveOriginAccessTimeOp::DoDirectoryWork",
+ js::ProfileEntry::Category::OTHER);
+
+ nsCOMPtr<nsIFile> directory;
+ nsresult rv =
+ aQuotaManager->GetDirectoryForOrigin(mPersistenceType.Value(),
+ mOriginScope.GetOrigin(),
+ getter_AddRefs(directory));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIBinaryOutputStream> stream;
+ rv = GetBinaryOutputStream(directory,
+ NS_LITERAL_STRING(METADATA_V2_FILE_NAME),
+ kUpdateFileFlag,
+ getter_AddRefs(stream));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // The origin directory may not exist anymore.
+ if (stream) {
+ rv = stream->Write64(mTimestamp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+SaveOriginAccessTimeOp::SendResults()
+{
+#ifdef DEBUG
+ NoteActorDestroyed();
+#endif
+}
+
+/*******************************************************************************
+ * Quota
+ ******************************************************************************/
+
+Quota::Quota()
+#ifdef DEBUG
+ : mActorDestroyed(false)
+#endif
+{
+}
+
+Quota::~Quota()
+{
+ MOZ_ASSERT(mActorDestroyed);
+}
+
+void
+Quota::StartIdleMaintenance()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!QuotaManager::IsShuttingDown());
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ if (NS_WARN_IF(!quotaManager)) {
+ return;
+ }
+
+ quotaManager->StartIdleMaintenance();
+}
+
+void
+Quota::ActorDestroy(ActorDestroyReason aWhy)
+{
+ AssertIsOnBackgroundThread();
+#ifdef DEBUG
+ MOZ_ASSERT(!mActorDestroyed);
+ mActorDestroyed = true;
+#endif
+}
+
+PQuotaUsageRequestParent*
+Quota::AllocPQuotaUsageRequestParent(const UsageRequestParams& aParams)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParams.type() != UsageRequestParams::T__None);
+
+ RefPtr<QuotaUsageRequestBase> actor;
+
+ switch (aParams.type()) {
+ case UsageRequestParams::TAllUsageParams:
+ actor = new GetUsageOp(aParams);
+ break;
+
+ case UsageRequestParams::TOriginUsageParams:
+ actor = new GetOriginUsageOp(aParams);
+ break;
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ MOZ_ASSERT(actor);
+
+ // Transfer ownership to IPDL.
+ return actor.forget().take();
+}
+
+bool
+Quota::RecvPQuotaUsageRequestConstructor(PQuotaUsageRequestParent* aActor,
+ const UsageRequestParams& aParams)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(aParams.type() != UsageRequestParams::T__None);
+
+ auto* op = static_cast<QuotaUsageRequestBase*>(aActor);
+
+ if (NS_WARN_IF(!op->Init(this))) {
+ return false;
+ }
+
+ op->RunImmediately();
+ return true;
+}
+
+bool
+Quota::DeallocPQuotaUsageRequestParent(PQuotaUsageRequestParent* aActor)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ // Transfer ownership back from IPDL.
+ RefPtr<QuotaUsageRequestBase> actor =
+ dont_AddRef(static_cast<QuotaUsageRequestBase*>(aActor));
+ return true;
+}
+
+PQuotaRequestParent*
+Quota::AllocPQuotaRequestParent(const RequestParams& aParams)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParams.type() != RequestParams::T__None);
+
+ if (aParams.type() == RequestParams::TClearOriginsParams) {
+ PBackgroundParent* actor = Manager();
+ MOZ_ASSERT(actor);
+
+ if (BackgroundParent::IsOtherProcessActor(actor)) {
+ ASSERT_UNLESS_FUZZING();
+ return nullptr;
+ }
+ }
+
+ RefPtr<QuotaRequestBase> actor;
+
+ switch (aParams.type()) {
+ case RequestParams::TClearOriginParams:
+ case RequestParams::TClearOriginsParams:
+ actor = new OriginClearOp(aParams);
+ break;
+
+ case RequestParams::TClearAllParams:
+ actor = new ResetOrClearOp(/* aClear */ true);
+ break;
+
+ case RequestParams::TResetAllParams:
+ actor = new ResetOrClearOp(/* aClear */ false);
+ break;
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ MOZ_ASSERT(actor);
+
+ // Transfer ownership to IPDL.
+ return actor.forget().take();
+}
+
+bool
+Quota::RecvPQuotaRequestConstructor(PQuotaRequestParent* aActor,
+ const RequestParams& aParams)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(aParams.type() != RequestParams::T__None);
+
+ auto* op = static_cast<QuotaRequestBase*>(aActor);
+
+ if (NS_WARN_IF(!op->Init(this))) {
+ return false;
+ }
+
+ op->RunImmediately();
+ return true;
+}
+
+bool
+Quota::DeallocPQuotaRequestParent(PQuotaRequestParent* aActor)
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ // Transfer ownership back from IPDL.
+ RefPtr<QuotaRequestBase> actor =
+ dont_AddRef(static_cast<QuotaRequestBase*>(aActor));
+ return true;
+}
+
+bool
+Quota::RecvStartIdleMaintenance()
+{
+ AssertIsOnBackgroundThread();
+
+ PBackgroundParent* actor = Manager();
+ MOZ_ASSERT(actor);
+
+ if (BackgroundParent::IsOtherProcessActor(actor)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (QuotaManager::IsShuttingDown()) {
+ return true;
+ }
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ if (!quotaManager) {
+ nsCOMPtr<nsIRunnable> callback =
+ NewRunnableMethod(this, &Quota::StartIdleMaintenance);
+
+ QuotaManager::GetOrCreate(callback);
+ return true;
+ }
+
+ quotaManager->StartIdleMaintenance();
+
+ return true;
+}
+
+bool
+Quota::RecvStopIdleMaintenance()
+{
+ AssertIsOnBackgroundThread();
+
+ PBackgroundParent* actor = Manager();
+ MOZ_ASSERT(actor);
+
+ if (BackgroundParent::IsOtherProcessActor(actor)) {
+ ASSERT_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (QuotaManager::IsShuttingDown()) {
+ return true;
+ }
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ if (!quotaManager) {
+ return true;
+ }
+
+ quotaManager->StopIdleMaintenance();
+
+ return true;
+}
+
+bool
+QuotaUsageRequestBase::Init(Quota* aQuota)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aQuota);
+
+ mNeedsQuotaManagerInit = true;
+
+ return true;
+}
+
+nsresult
+QuotaUsageRequestBase::GetUsageForOrigin(QuotaManager* aQuotaManager,
+ PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ bool aIsApp,
+ UsageInfo* aUsageInfo)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aQuotaManager);
+ MOZ_ASSERT(aUsageInfo);
+ MOZ_ASSERT(aUsageInfo->TotalUsage() == 0);
+
+ nsCOMPtr<nsIFile> directory;
+ nsresult rv = aQuotaManager->GetDirectoryForOrigin(aPersistenceType,
+ aOrigin,
+ getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ 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 (IsTreatedAsPersistent(aPersistenceType, aIsApp)) {
+ nsCString originKey = OriginKey(aPersistenceType, aOrigin);
+ initialized = aQuotaManager->IsOriginInitialized(originKey);
+ } else {
+ initialized = aQuotaManager->IsTemporaryStorageInitialized();
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) &&
+ hasMore && !mCanceled) {
+ nsCOMPtr<nsISupports> entry;
+ rv = entries->GetNext(getter_AddRefs(entry));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
+ NS_ENSURE_TRUE(file, NS_NOINTERFACE);
+
+ nsString leafName;
+ rv = file->GetLeafName(leafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (leafName.EqualsLiteral(METADATA_FILE_NAME) ||
+ leafName.EqualsLiteral(METADATA_V2_FILE_NAME) ||
+ leafName.EqualsLiteral(DSSTORE_FILE_NAME)) {
+ continue;
+ }
+
+ if (!initialized) {
+ bool isDirectory;
+ rv = file->IsDirectory(&isDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!isDirectory) {
+ NS_WARNING("Unknown file found!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ if (MaybeRemoveCorruptDirectory(leafName, file)) {
+ continue;
+ }
+
+ Client::Type clientType;
+ rv = Client::TypeFromText(leafName, clientType);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Unknown directory found!");
+ if (!initialized) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ continue;
+ }
+
+ Client* client = aQuotaManager->GetClient(clientType);
+ MOZ_ASSERT(client);
+
+ if (initialized) {
+ rv = client->GetUsageForOrigin(aPersistenceType,
+ aGroup,
+ aOrigin,
+ mCanceled,
+ aUsageInfo);
+ }
+ else {
+ rv = client->InitOrigin(aPersistenceType,
+ aGroup,
+ aOrigin,
+ mCanceled,
+ aUsageInfo);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+QuotaUsageRequestBase::SendResults()
+{
+ AssertIsOnOwningThread();
+
+ if (IsActorDestroyed()) {
+ if (NS_SUCCEEDED(mResultCode)) {
+ mResultCode = NS_ERROR_FAILURE;
+ }
+ } else {
+ if (mCanceled) {
+ mResultCode = NS_ERROR_FAILURE;
+ }
+
+ UsageRequestResponse response;
+
+ if (NS_SUCCEEDED(mResultCode)) {
+ GetResponse(response);
+ } else {
+ response = mResultCode;
+ }
+
+ Unused << PQuotaUsageRequestParent::Send__delete__(this, response);
+ }
+}
+
+void
+QuotaUsageRequestBase::ActorDestroy(ActorDestroyReason aWhy)
+{
+ AssertIsOnOwningThread();
+
+ NoteActorDestroyed();
+}
+
+bool
+QuotaUsageRequestBase::RecvCancel()
+{
+ AssertIsOnOwningThread();
+
+ if (mCanceled.exchange(true)) {
+ NS_WARNING("Canceled more than once?!");
+ return false;
+ }
+
+ return true;
+}
+
+GetUsageOp::GetUsageOp(const UsageRequestParams& aParams)
+ : mGetAll(aParams.get_AllUsageParams().getAll())
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aParams.type() == UsageRequestParams::TAllUsageParams);
+}
+
+nsresult
+GetUsageOp::TraverseRepository(QuotaManager* aQuotaManager,
+ PersistenceType aPersistenceType)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aQuotaManager);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> directory =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = directory->InitWithPath(aQuotaManager->GetStoragePath(aPersistenceType));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool exists;
+ rv = directory->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!exists) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool persistent = aPersistenceType == PERSISTENCE_TYPE_PERSISTENT;
+
+ bool hasMore;
+ while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) &&
+ hasMore && !mCanceled) {
+ nsCOMPtr<nsISupports> entry;
+ rv = entries->GetNext(getter_AddRefs(entry));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIFile> originDir = do_QueryInterface(entry);
+ MOZ_ASSERT(originDir);
+
+ bool isDirectory;
+ rv = originDir->IsDirectory(&isDirectory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!isDirectory) {
+ nsString leafName;
+ rv = originDir->GetLeafName(leafName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!leafName.EqualsLiteral(DSSTORE_FILE_NAME)) {
+ QM_WARNING("Something (%s) in the repository that doesn't belong!",
+ NS_ConvertUTF16toUTF8(leafName).get());
+ }
+ continue;
+ }
+
+ int64_t timestamp;
+ nsCString suffix;
+ nsCString group;
+ nsCString origin;
+ bool isApp;
+ rv = aQuotaManager->GetDirectoryMetadata2WithRestore(originDir,
+ persistent,
+ &timestamp,
+ suffix,
+ group,
+ origin,
+ &isApp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!mGetAll && aQuotaManager->IsOriginInternal(origin)) {
+ continue;
+ }
+
+ OriginUsage* originUsage;
+
+ // We can't store pointers to OriginUsage objects in the hashtable
+ // since AppendElement() reallocates its internal array buffer as number
+ // of elements grows.
+ uint32_t index;
+ if (mOriginUsagesIndex.Get(origin, &index)) {
+ originUsage = &mOriginUsages[index];
+ } else {
+ index = mOriginUsages.Length();
+
+ originUsage = mOriginUsages.AppendElement();
+
+ originUsage->origin() = origin;
+ originUsage->persisted() = false;
+ originUsage->usage() = 0;
+
+ mOriginUsagesIndex.Put(origin, index);
+ }
+
+ UsageInfo usageInfo;
+ rv = GetUsageForOrigin(aQuotaManager,
+ aPersistenceType,
+ group,
+ origin,
+ isApp,
+ &usageInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ originUsage->usage() = originUsage->usage() + usageInfo.TotalUsage();
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+GetUsageOp::DoDirectoryWork(QuotaManager* aQuotaManager)
+{
+ AssertIsOnIOThread();
+
+ PROFILER_LABEL("Quota", "GetUsageOp::DoDirectoryWork",
+ js::ProfileEntry::Category::OTHER);
+
+ nsresult rv;
+
+ for (const PersistenceType type : kAllPersistenceTypes) {
+ rv = TraverseRepository(aQuotaManager, type);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+GetUsageOp::GetResponse(UsageRequestResponse& aResponse)
+{
+ AssertIsOnOwningThread();
+
+ aResponse = AllUsageResponse();
+
+ if (!mOriginUsages.IsEmpty()) {
+ nsTArray<OriginUsage>& originUsages =
+ aResponse.get_AllUsageResponse().originUsages();
+
+ mOriginUsages.SwapElements(originUsages);
+ }
+}
+
+GetOriginUsageOp::GetOriginUsageOp(const UsageRequestParams& aParams)
+ : mParams(aParams.get_OriginUsageParams())
+ , mGetGroupUsage(aParams.get_OriginUsageParams().getGroupUsage())
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aParams.type() == UsageRequestParams::TOriginUsageParams);
+}
+
+bool
+GetOriginUsageOp::Init(Quota* aQuota)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aQuota);
+
+ if (NS_WARN_IF(!QuotaUsageRequestBase::Init(aQuota))) {
+ return false;
+ }
+
+ mNeedsMainThreadInit = true;
+
+ return true;
+}
+
+nsresult
+GetOriginUsageOp::DoInitOnMainThread()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(GetState() == State_Initializing);
+ MOZ_ASSERT(mNeedsMainThreadInit);
+
+ const PrincipalInfo& principalInfo = mParams.principalInfo();
+
+ nsresult rv;
+ nsCOMPtr<nsIPrincipal> principal =
+ PrincipalInfoToPrincipal(principalInfo, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Figure out which origin we're dealing with.
+ nsCString origin;
+ rv = QuotaManager::GetInfoFromPrincipal(principal, &mSuffix, &mGroup,
+ &origin, &mIsApp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mOriginScope.SetFromOrigin(origin);
+
+ return NS_OK;
+}
+
+nsresult
+GetOriginUsageOp::DoDirectoryWork(QuotaManager* aQuotaManager)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(mUsageInfo.TotalUsage() == 0);
+
+ PROFILER_LABEL("Quota", "GetOriginUsageOp::DoDirectoryWork",
+ js::ProfileEntry::Category::OTHER);
+
+ nsresult rv;
+
+ if (mGetGroupUsage) {
+ nsCOMPtr<nsIFile> directory;
+
+ // Ensure origin is initialized first. It will initialize all origins for
+ // temporary storage including origins belonging to our group.
+ rv = aQuotaManager->EnsureOriginIsInitialized(PERSISTENCE_TYPE_TEMPORARY,
+ mSuffix, mGroup,
+ mOriginScope.GetOrigin(),
+ mIsApp,
+ getter_AddRefs(directory));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Get cached usage and limit (the method doesn't have to stat any files).
+ aQuotaManager->GetGroupUsageAndLimit(mGroup, &mUsageInfo);
+
+ return NS_OK;
+ }
+
+ // Add all the persistent/temporary/default storage files we care about.
+ for (const PersistenceType type : kAllPersistenceTypes) {
+ UsageInfo usageInfo;
+ rv = GetUsageForOrigin(aQuotaManager,
+ type,
+ mGroup,
+ mOriginScope.GetOrigin(),
+ mIsApp,
+ &usageInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mUsageInfo.Append(usageInfo);
+ }
+
+ return NS_OK;
+}
+
+void
+GetOriginUsageOp::GetResponse(UsageRequestResponse& aResponse)
+{
+ AssertIsOnOwningThread();
+
+ OriginUsageResponse usageResponse;
+
+ // We'll get the group usage when mGetGroupUsage is true and get the
+ // origin usage when mGetGroupUsage is false.
+ usageResponse.usage() = mUsageInfo.TotalUsage();
+
+ if (mGetGroupUsage) {
+ usageResponse.limit() = mUsageInfo.Limit();
+ } else {
+ usageResponse.fileUsage() = mUsageInfo.FileUsage();
+ }
+
+ aResponse = usageResponse;
+}
+
+bool
+QuotaRequestBase::Init(Quota* aQuota)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aQuota);
+
+ mNeedsQuotaManagerInit = true;
+
+ return true;
+}
+
+void
+QuotaRequestBase::SendResults()
+{
+ AssertIsOnOwningThread();
+
+ if (IsActorDestroyed()) {
+ if (NS_SUCCEEDED(mResultCode)) {
+ mResultCode = NS_ERROR_FAILURE;
+ }
+ } else {
+ RequestResponse response;
+
+ if (NS_SUCCEEDED(mResultCode)) {
+ GetResponse(response);
+ } else {
+ response = mResultCode;
+ }
+
+ Unused << PQuotaRequestParent::Send__delete__(this, response);
+ }
+}
+
+void
+QuotaRequestBase::ActorDestroy(ActorDestroyReason aWhy)
+{
+ AssertIsOnOwningThread();
+
+ NoteActorDestroyed();
+}
+
+void
+ResetOrClearOp::DeleteFiles(QuotaManager* aQuotaManager)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aQuotaManager);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> directory =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ rv = directory->InitWithPath(aQuotaManager->GetStoragePath());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ 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...
+ MOZ_ASSERT(false, "Failed to remove storage directory!");
+ }
+
+ nsCOMPtr<nsIFile> storageFile =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ rv = storageFile->InitWithPath(aQuotaManager->GetBasePath());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ rv = storageFile->Append(NS_LITERAL_STRING(STORAGE_FILE_NAME));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ rv = storageFile->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 the storage connection
+ // correctly...
+ MOZ_ASSERT(false, "Failed to remove storage file!");
+ }
+}
+
+nsresult
+ResetOrClearOp::DoDirectoryWork(QuotaManager* aQuotaManager)
+{
+ AssertIsOnIOThread();
+
+ PROFILER_LABEL("Quota", "ResetOrClearOp::DoDirectoryWork",
+ js::ProfileEntry::Category::OTHER);
+
+ if (mClear) {
+ DeleteFiles(aQuotaManager);
+ }
+
+ aQuotaManager->RemoveQuota();
+
+ aQuotaManager->ResetOrClearCompleted();
+
+ return NS_OK;
+}
+
+void
+ResetOrClearOp::GetResponse(RequestResponse& aResponse)
+{
+ AssertIsOnOwningThread();
+ if (mClear) {
+ aResponse = ClearAllResponse();
+ } else {
+ aResponse = ResetAllResponse();
+ }
+}
+
+OriginClearOp::OriginClearOp(const RequestParams& aParams)
+ : QuotaRequestBase(/* aExclusive */ true)
+ , mParams(aParams)
+ , mMultiple(aParams.type() == RequestParams::TClearOriginsParams)
+{
+ MOZ_ASSERT(aParams.type() == RequestParams::TClearOriginParams ||
+ aParams.type() == RequestParams::TClearOriginsParams);
+}
+
+bool
+OriginClearOp::Init(Quota* aQuota)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aQuota);
+
+ if (NS_WARN_IF(!QuotaRequestBase::Init(aQuota))) {
+ return false;
+ }
+
+ if (!mMultiple) {
+ const ClearOriginParams& params = mParams.get_ClearOriginParams();
+
+ if (params.persistenceTypeIsExplicit()) {
+ MOZ_ASSERT(params.persistenceType() != PERSISTENCE_TYPE_INVALID);
+
+ mPersistenceType.SetValue(params.persistenceType());
+ }
+ }
+
+ mNeedsMainThreadInit = true;
+
+ return true;
+}
+
+nsresult
+OriginClearOp::DoInitOnMainThread()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(GetState() == State_Initializing);
+ MOZ_ASSERT(mNeedsMainThreadInit);
+
+ if (mMultiple) {
+ const ClearOriginsParams& params = mParams.get_ClearOriginsParams();
+
+ mOriginScope.SetFromJSONPattern(params.pattern());
+ } else {
+ const ClearOriginParams& params = mParams.get_ClearOriginParams();
+
+ const PrincipalInfo& principalInfo = params.principalInfo();
+
+ nsresult rv;
+ nsCOMPtr<nsIPrincipal> principal =
+ PrincipalInfoToPrincipal(principalInfo, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Figure out which origin we're dealing with.
+ nsCString origin;
+ rv = QuotaManager::GetInfoFromPrincipal(principal, nullptr, nullptr, &origin,
+ nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ if (params.clearAll()) {
+ mOriginScope.SetFromPrefix(origin);
+ } else {
+ mOriginScope.SetFromOrigin(origin);
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+OriginClearOp::DeleteFiles(QuotaManager* aQuotaManager,
+ PersistenceType aPersistenceType)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aQuotaManager);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> directory =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ rv = directory->InitWithPath(aQuotaManager->GetStoragePath(aPersistenceType));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ if (NS_WARN_IF(NS_FAILED(
+ directory->GetDirectoryEntries(getter_AddRefs(entries)))) || !entries) {
+ return;
+ }
+
+ OriginScope originScope = mOriginScope.Clone();
+ if (originScope.IsOrigin()) {
+ nsCString originSanitized(originScope.GetOrigin());
+ SanitizeOriginString(originSanitized);
+ originScope.SetOrigin(originSanitized);
+ } else if (originScope.IsPrefix()) {
+ nsCString prefixSanitized(originScope.GetPrefix());
+ SanitizeOriginString(prefixSanitized);
+ originScope.SetPrefix(prefixSanitized);
+ }
+
+ 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;
+ }
+
+ nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
+ MOZ_ASSERT(file);
+
+ bool isDirectory;
+ rv = file->IsDirectory(&isDirectory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ nsString leafName;
+ rv = file->GetLeafName(leafName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ if (!isDirectory) {
+ if (!leafName.EqualsLiteral(DSSTORE_FILE_NAME)) {
+ QM_WARNING("Something (%s) in the repository that doesn't belong!",
+ NS_ConvertUTF16toUTF8(leafName).get());
+ }
+ continue;
+ }
+
+ // Skip the origin directory if it doesn't match the pattern.
+ if (!originScope.MatchesOrigin(OriginScope::FromOrigin(
+ NS_ConvertUTF16toUTF8(leafName)))) {
+ continue;
+ }
+
+ bool persistent = aPersistenceType == PERSISTENCE_TYPE_PERSISTENT;
+
+ int64_t timestamp;
+ nsCString suffix;
+ nsCString group;
+ nsCString origin;
+ bool isApp;
+ rv = aQuotaManager->GetDirectoryMetadata2WithRestore(file,
+ persistent,
+ &timestamp,
+ suffix,
+ group,
+ origin,
+ &isApp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ for (uint32_t index = 0; index < 10; index++) {
+ // We can't guarantee that this will always succeed on Windows...
+ if (NS_SUCCEEDED((rv = file->Remove(true)))) {
+ break;
+ }
+
+ NS_WARNING("Failed to remove directory, retrying after a short delay.");
+
+ PR_Sleep(PR_MillisecondsToInterval(200));
+ }
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to remove directory, giving up!");
+ }
+
+ if (aPersistenceType != PERSISTENCE_TYPE_PERSISTENT) {
+ aQuotaManager->RemoveQuotaForOrigin(aPersistenceType, group, origin);
+ }
+
+ aQuotaManager->OriginClearCompleted(aPersistenceType, origin, isApp);
+ }
+
+}
+
+nsresult
+OriginClearOp::DoDirectoryWork(QuotaManager* aQuotaManager)
+{
+ AssertIsOnIOThread();
+
+ PROFILER_LABEL("Quota", "OriginClearOp::DoDirectoryWork",
+ js::ProfileEntry::Category::OTHER);
+
+ if (mPersistenceType.IsNull()) {
+ for (const PersistenceType type : kAllPersistenceTypes) {
+ DeleteFiles(aQuotaManager, type);
+ }
+ } else {
+ DeleteFiles(aQuotaManager, mPersistenceType.Value());
+ }
+
+ return NS_OK;
+}
+
+void
+OriginClearOp::GetResponse(RequestResponse& aResponse)
+{
+ AssertIsOnOwningThread();
+
+ if (mMultiple) {
+ aResponse = ClearOriginsResponse();
+ } else {
+ aResponse = ClearOriginResponse();
+ }
+}
+
+nsresult
+StorageDirectoryHelper::AddOriginDirectory(nsIFile* aDirectory,
+ OriginProps** aOriginProps)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aDirectory);
+
+ OriginProps* originProps;
+
+ nsString leafName;
+ nsresult rv = aDirectory->GetLeafName(leafName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (leafName.EqualsLiteral(kChromeOrigin)) {
+ originProps = mOriginProps.AppendElement();
+ originProps->mDirectory = aDirectory;
+ originProps->mSpec = kChromeOrigin;
+ originProps->mType = OriginProps::eChrome;
+ } else {
+ nsCString spec;
+ PrincipalOriginAttributes attrs;
+ bool result = OriginParser::ParseOrigin(NS_ConvertUTF16toUTF8(leafName),
+ spec, &attrs);
+ if (NS_WARN_IF(!result)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ originProps = mOriginProps.AppendElement();
+ originProps->mDirectory = aDirectory;
+ originProps->mSpec = spec;
+ originProps->mAttrs = attrs;
+ originProps->mType = OriginProps::eContent;
+ }
+
+ if (aOriginProps) {
+ *aOriginProps = originProps;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+StorageDirectoryHelper::ProcessOriginDirectories()
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(!mOriginProps.IsEmpty());
+
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
+
+ {
+ mozilla::MutexAutoLock autolock(mMutex);
+ while (mWaiting) {
+ mCondVar.Wait();
+ }
+ }
+
+ if (NS_WARN_IF(NS_FAILED(mMainThreadResultCode))) {
+ return mMainThreadResultCode;
+ }
+
+ // Verify that the bounce to the main thread didn't start the shutdown
+ // sequence.
+ if (NS_WARN_IF(QuotaManager::IsShuttingDown())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = DoProcessOriginDirectories();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+StorageDirectoryHelper::RunOnMainThread()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mOriginProps.IsEmpty());
+
+ nsresult rv;
+
+ nsCOMPtr<nsIScriptSecurityManager> secMan =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ for (uint32_t count = mOriginProps.Length(), index = 0;
+ index < count;
+ index++) {
+ OriginProps& originProps = mOriginProps[index];
+
+ switch (originProps.mType) {
+ case OriginProps::eChrome: {
+ QuotaManager::GetInfoForChrome(&originProps.mSuffix,
+ &originProps.mGroup,
+ &originProps.mOrigin,
+ &originProps.mIsApp);
+ break;
+ }
+
+ case OriginProps::eContent: {
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), originProps.mSpec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::CreateCodebasePrincipal(uri, originProps.mAttrs);
+ if (NS_WARN_IF(!principal)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = QuotaManager::GetInfoFromPrincipal(principal,
+ &originProps.mSuffix,
+ &originProps.mGroup,
+ &originProps.mOrigin,
+ &originProps.mIsApp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Bad type!");
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StorageDirectoryHelper::Run()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv = RunOnMainThread();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mMainThreadResultCode = rv;
+ }
+
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mWaiting);
+
+ mWaiting = false;
+ mCondVar.Notify();
+
+ return NS_OK;
+}
+
+// static
+bool
+OriginParser::ParseOrigin(const nsACString& aOrigin,
+ nsCString& aSpec,
+ PrincipalOriginAttributes* aAttrs)
+{
+ MOZ_ASSERT(!aOrigin.IsEmpty());
+ MOZ_ASSERT(aAttrs);
+
+ PrincipalOriginAttributes originAttributes;
+
+ nsCString originNoSuffix;
+ bool ok = originAttributes.PopulateFromOrigin(aOrigin, originNoSuffix);
+ if (!ok) {
+ return false;
+ }
+
+ OriginParser parser(originNoSuffix, originAttributes);
+ return parser.Parse(aSpec, aAttrs);
+}
+
+bool
+OriginParser::Parse(nsACString& aSpec, PrincipalOriginAttributes* aAttrs)
+{
+ MOZ_ASSERT(aAttrs);
+
+ while (mTokenizer.hasMoreTokens()) {
+ const nsDependentCSubstring& token = mTokenizer.nextToken();
+
+ HandleToken(token);
+
+ if (mError) {
+ break;
+ }
+
+ if (!mHandledTokens.IsEmpty()) {
+ mHandledTokens.Append(NS_LITERAL_CSTRING(", "));
+ }
+ mHandledTokens.Append('\'');
+ mHandledTokens.Append(token);
+ mHandledTokens.Append('\'');
+ }
+
+ if (!mError && mTokenizer.separatorAfterCurrentToken()) {
+ HandleTrailingSeparator();
+ }
+
+ if (mError) {
+ QM_WARNING("Origin '%s' failed to parse, handled tokens: %s", mOrigin.get(),
+ mHandledTokens.get());
+
+ return false;
+ }
+
+ MOZ_ASSERT(mState == eComplete || mState == eHandledTrailingSeparator);
+
+ if (mAppId == kNoAppId) {
+ *aAttrs = mOriginAttributes;
+ } else {
+ MOZ_ASSERT(mOriginAttributes.mAppId == kNoAppId);
+
+ *aAttrs = PrincipalOriginAttributes(mAppId, mInIsolatedMozBrowser);
+ }
+
+ nsAutoCString spec(mSchema);
+
+ if (mSchemaType == eFile) {
+ spec.AppendLiteral("://");
+
+ for (uint32_t count = mPathnameComponents.Length(), index = 0;
+ index < count;
+ index++) {
+ spec.Append('/');
+ spec.Append(mPathnameComponents[index]);
+ }
+
+ aSpec = spec;
+
+ return true;
+ }
+
+ if (mSchemaType == eAbout) {
+ spec.Append(':');
+ } else {
+ spec.AppendLiteral("://");
+ }
+
+ spec.Append(mHost);
+
+ if (!mPort.IsNull()) {
+ spec.Append(':');
+ spec.AppendInt(mPort.Value());
+ }
+
+ aSpec = spec;
+
+ return true;
+}
+
+void
+OriginParser::HandleSchema(const nsDependentCSubstring& aToken)
+{
+ MOZ_ASSERT(!aToken.IsEmpty());
+ MOZ_ASSERT(mState == eExpectingAppIdOrSchema || mState == eExpectingSchema);
+
+ bool isAbout = false;
+ bool isFile = false;
+ if (aToken.EqualsLiteral("http") ||
+ aToken.EqualsLiteral("https") ||
+ (isAbout = aToken.EqualsLiteral("about") ||
+ aToken.EqualsLiteral("moz-safe-about")) ||
+ aToken.EqualsLiteral("indexeddb") ||
+ (isFile = aToken.EqualsLiteral("file")) ||
+ aToken.EqualsLiteral("app") ||
+ aToken.EqualsLiteral("resource")) {
+ mSchema = aToken;
+
+ if (isAbout) {
+ mSchemaType = eAbout;
+ mState = eExpectingHost;
+ } else {
+ if (isFile) {
+ mSchemaType = eFile;
+ }
+ mState = eExpectingEmptyToken1;
+ }
+
+ return;
+ }
+
+ QM_WARNING("'%s' is not a valid schema!", nsCString(aToken).get());
+
+ mError = true;
+}
+
+void
+OriginParser::HandlePathnameComponent(const nsDependentCSubstring& aToken)
+{
+ MOZ_ASSERT(!aToken.IsEmpty());
+ MOZ_ASSERT(mState == eExpectingEmptyTokenOrDriveLetterOrPathnameComponent ||
+ mState == eExpectingEmptyTokenOrPathnameComponent);
+ MOZ_ASSERT(mSchemaType == eFile);
+
+ mPathnameComponents.AppendElement(aToken);
+
+ mState = mTokenizer.hasMoreTokens() ? eExpectingEmptyTokenOrPathnameComponent
+ : eComplete;
+}
+
+void
+OriginParser::HandleToken(const nsDependentCSubstring& aToken)
+{
+ switch (mState) {
+ case eExpectingAppIdOrSchema: {
+ if (aToken.IsEmpty()) {
+ QM_WARNING("Expected an app id or schema (not an empty string)!");
+
+ mError = true;
+ return;
+ }
+
+ if (NS_IsAsciiDigit(aToken.First())) {
+ // nsDependentCSubstring doesn't provice ToInteger()
+ nsCString token(aToken);
+
+ nsresult rv;
+ uint32_t appId = token.ToInteger(&rv);
+ if (NS_SUCCEEDED(rv)) {
+ mAppId = appId;
+ mState = eExpectingInMozBrowser;
+ return;
+ }
+ }
+
+ HandleSchema(aToken);
+
+ return;
+ }
+
+ case eExpectingInMozBrowser: {
+ if (aToken.Length() != 1) {
+ QM_WARNING("'%d' is not a valid length for the inMozBrowser flag!",
+ aToken.Length());
+
+ mError = true;
+ return;
+ }
+
+ if (aToken.First() == 't') {
+ mInIsolatedMozBrowser = true;
+ } else if (aToken.First() == 'f') {
+ mInIsolatedMozBrowser = false;
+ } else {
+ QM_WARNING("'%s' is not a valid value for the inMozBrowser flag!",
+ nsCString(aToken).get());
+
+ mError = true;
+ return;
+ }
+
+ mState = eExpectingSchema;
+
+ return;
+ }
+
+ case eExpectingSchema: {
+ if (aToken.IsEmpty()) {
+ QM_WARNING("Expected a schema (not an empty string)!");
+
+ mError = true;
+ return;
+ }
+
+ HandleSchema(aToken);
+
+ return;
+ }
+
+ case eExpectingEmptyToken1: {
+ if (!aToken.IsEmpty()) {
+ QM_WARNING("Expected the first empty token!");
+
+ mError = true;
+ return;
+ }
+
+ mState = eExpectingEmptyToken2;
+
+ return;
+ }
+
+ case eExpectingEmptyToken2: {
+ if (!aToken.IsEmpty()) {
+ QM_WARNING("Expected the second empty token!");
+
+ mError = true;
+ return;
+ }
+
+ if (mSchemaType == eFile) {
+ mState = eExpectingEmptyToken3;
+ } else {
+ mState = eExpectingHost;
+ }
+
+ return;
+ }
+
+ case eExpectingEmptyToken3: {
+ MOZ_ASSERT(mSchemaType == eFile);
+
+ if (!aToken.IsEmpty()) {
+ QM_WARNING("Expected the third empty token!");
+
+ mError = true;
+ return;
+ }
+
+ mState = mTokenizer.hasMoreTokens()
+ ? eExpectingEmptyTokenOrDriveLetterOrPathnameComponent
+ : eComplete;
+
+ return;
+ }
+
+ case eExpectingHost: {
+ if (aToken.IsEmpty()) {
+ QM_WARNING("Expected a host (not an empty string)!");
+
+ mError = true;
+ return;
+ }
+
+ mHost = aToken;
+
+ mState = mTokenizer.hasMoreTokens() ? eExpectingPort : eComplete;
+
+ return;
+ }
+
+ case eExpectingPort: {
+ MOZ_ASSERT(mSchemaType == eNone);
+
+ if (aToken.IsEmpty()) {
+ QM_WARNING("Expected a port (not an empty string)!");
+
+ mError = true;
+ return;
+ }
+
+ // nsDependentCSubstring doesn't provice ToInteger()
+ nsCString token(aToken);
+
+ nsresult rv;
+ uint32_t port = token.ToInteger(&rv);
+ if (NS_SUCCEEDED(rv)) {
+ mPort.SetValue() = port;
+ } else {
+ QM_WARNING("'%s' is not a valid port number!", token.get());
+
+ mError = true;
+ return;
+ }
+
+ mState = eComplete;
+
+ return;
+ }
+
+ case eExpectingEmptyTokenOrDriveLetterOrPathnameComponent: {
+ MOZ_ASSERT(mSchemaType == eFile);
+
+ if (aToken.IsEmpty()) {
+ mPathnameComponents.AppendElement(EmptyCString());
+
+ mState =
+ mTokenizer.hasMoreTokens() ? eExpectingEmptyTokenOrPathnameComponent
+ : eComplete;
+
+ return;
+ }
+
+ if (aToken.Length() == 1 && NS_IsAsciiAlpha(aToken.First())) {
+ mMaybeDriveLetter = true;
+
+ mPathnameComponents.AppendElement(aToken);
+
+ mState =
+ mTokenizer.hasMoreTokens() ? eExpectingEmptyTokenOrPathnameComponent
+ : eComplete;
+
+ return;
+ }
+
+ HandlePathnameComponent(aToken);
+
+ return;
+ }
+
+ case eExpectingEmptyTokenOrPathnameComponent: {
+ MOZ_ASSERT(mSchemaType == eFile);
+
+ if (aToken.IsEmpty()) {
+ if (mMaybeDriveLetter) {
+ MOZ_ASSERT(mPathnameComponents.Length() == 1);
+
+ nsCString& pathnameComponent = mPathnameComponents[0];
+ pathnameComponent.Append(':');
+
+ mMaybeDriveLetter = false;
+ } else {
+ mPathnameComponents.AppendElement(EmptyCString());
+ }
+
+ mState =
+ mTokenizer.hasMoreTokens() ? eExpectingEmptyTokenOrPathnameComponent
+ : eComplete;
+
+ return;
+ }
+
+ HandlePathnameComponent(aToken);
+
+ return;
+ }
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+}
+
+void
+OriginParser::HandleTrailingSeparator()
+{
+ MOZ_ASSERT(mState == eComplete);
+ MOZ_ASSERT(mSchemaType == eFile);
+
+ mPathnameComponents.AppendElement(EmptyCString());
+
+ mState = eHandledTrailingSeparator;
+}
+
+nsresult
+CreateOrUpgradeDirectoryMetadataHelper::CreateOrUpgradeMetadataFiles()
+{
+ AssertIsOnIOThread();
+
+ bool exists;
+ nsresult rv = mDirectory->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!exists) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ rv = mDirectory->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> originDir = do_QueryInterface(entry);
+ MOZ_ASSERT(originDir);
+
+ nsString leafName;
+ rv = originDir->GetLeafName(leafName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool isDirectory;
+ rv = originDir->IsDirectory(&isDirectory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (isDirectory) {
+ if (leafName.EqualsLiteral("moz-safe-about+++home")) {
+ // This directory was accidentally created by a buggy nightly and can
+ // be safely removed.
+
+ QM_WARNING("Deleting accidental moz-safe-about+++home directory!");
+
+ rv = originDir->Remove(/* aRecursive */ true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ continue;
+ }
+ } else {
+ if (!leafName.EqualsLiteral(DSSTORE_FILE_NAME)) {
+ QM_WARNING("Something (%s) in the storage directory that doesn't belong!",
+ NS_ConvertUTF16toUTF8(leafName).get());
+
+ }
+ continue;
+ }
+
+ if (mPersistent) {
+ rv = MaybeUpgradeOriginDirectory(originDir);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ OriginProps* originProps;
+ rv = AddOriginDirectory(originDir, &originProps);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!mPersistent) {
+ int64_t timestamp;
+ nsCString group;
+ nsCString origin;
+ bool hasIsApp;
+ rv = GetDirectoryMetadata(originDir,
+ &timestamp,
+ group,
+ origin,
+ &hasIsApp);
+ if (NS_FAILED(rv)) {
+ timestamp = INT64_MIN;
+ rv = GetLastModifiedTime(originDir, &timestamp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ originProps->mTimestamp = timestamp;
+ originProps->mNeedsRestore = true;
+ } else if (hasIsApp) {
+ originProps->mIgnore = true;
+ }
+ }
+ else if (!QuotaManager::IsOriginInternal(originProps->mSpec)) {
+ int64_t timestamp = INT64_MIN;
+ rv = GetLastModifiedTime(originDir, &timestamp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ originProps->mTimestamp = timestamp;
+ }
+ }
+
+ if (mOriginProps.IsEmpty()) {
+ return NS_OK;
+ }
+
+ rv = ProcessOriginDirectories();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+CreateOrUpgradeDirectoryMetadataHelper::MaybeUpgradeOriginDirectory(
+ nsIFile* aDirectory)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aDirectory);
+
+ nsCOMPtr<nsIFile> metadataFile;
+ nsresult 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 (!exists) {
+ // Directory structure upgrade needed.
+ // Move all files to IDB specific directory.
+
+ nsString idbDirectoryName;
+ rv = Client::TypeToText(Client::IDB, idbDirectoryName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIFile> idbDirectory;
+ rv = aDirectory->Clone(getter_AddRefs(idbDirectory));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = idbDirectory->Append(idbDirectoryName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = idbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
+ NS_WARNING("IDB directory already exists!");
+
+ bool isDirectory;
+ rv = idbDirectory->IsDirectory(&isDirectory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(!isDirectory)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+ else {
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ rv = aDirectory->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> file = do_QueryInterface(entry);
+ if (NS_WARN_IF(!file)) {
+ return rv;
+ }
+
+ nsString leafName;
+ rv = file->GetLeafName(leafName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!leafName.Equals(idbDirectoryName)) {
+ rv = file->MoveTo(idbDirectory, EmptyString());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ }
+
+ rv = metadataFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+CreateOrUpgradeDirectoryMetadataHelper::GetDirectoryMetadata(
+ nsIFile* aDirectory,
+ int64_t* aTimestamp,
+ nsACString& aGroup,
+ nsACString& aOrigin,
+ bool* aHasIsApp)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aDirectory);
+ MOZ_ASSERT(aTimestamp);
+ MOZ_ASSERT(aHasIsApp);
+ MOZ_ASSERT(!mPersistent);
+
+ nsCOMPtr<nsIBinaryInputStream> binaryStream;
+ nsresult rv = GetBinaryInputStream(aDirectory,
+ NS_LITERAL_STRING(METADATA_FILE_NAME),
+ 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;
+ }
+
+ nsCString group;
+ rv = binaryStream->ReadCString(group);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCString origin;
+ rv = binaryStream->ReadCString(origin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool dummyIsApp;
+ bool hasIsApp = NS_SUCCEEDED(binaryStream->ReadBoolean(&dummyIsApp));
+
+ *aTimestamp = timestamp;
+ aGroup = group;
+ aOrigin = origin;
+ *aHasIsApp = hasIsApp;
+ return NS_OK;
+}
+
+nsresult
+CreateOrUpgradeDirectoryMetadataHelper::DoProcessOriginDirectories()
+{
+ AssertIsOnIOThread();
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> permanentStorageDir;
+
+ for (uint32_t count = mOriginProps.Length(), index = 0;
+ index < count;
+ index++) {
+ OriginProps& originProps = mOriginProps[index];
+
+ if (mPersistent) {
+ rv = CreateDirectoryMetadata(originProps.mDirectory,
+ originProps.mTimestamp,
+ originProps.mSuffix,
+ originProps.mGroup,
+ originProps.mOrigin,
+ originProps.mIsApp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Move internal origins to new persistent storage.
+ if (QuotaManager::IsOriginInternal(originProps.mSpec)) {
+ if (!permanentStorageDir) {
+ permanentStorageDir =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ const nsString& permanentStoragePath =
+ quotaManager->GetStoragePath(PERSISTENCE_TYPE_PERSISTENT);
+
+ rv = permanentStorageDir->InitWithPath(permanentStoragePath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ nsString leafName;
+ rv = originProps.mDirectory->GetLeafName(leafName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIFile> newDirectory;
+ rv = permanentStorageDir->Clone(getter_AddRefs(newDirectory));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = newDirectory->Append(leafName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool exists;
+ rv = newDirectory->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (exists) {
+ QM_WARNING("Found %s in storage/persistent and storage/permanent !",
+ NS_ConvertUTF16toUTF8(leafName).get());
+
+ rv = originProps.mDirectory->Remove(/* recursive */ true);
+ } else {
+ rv = originProps.mDirectory->MoveTo(permanentStorageDir, EmptyString());
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ } else if (originProps.mNeedsRestore) {
+ rv = CreateDirectoryMetadata(originProps.mDirectory,
+ originProps.mTimestamp,
+ originProps.mSuffix,
+ originProps.mGroup,
+ originProps.mOrigin,
+ originProps.mIsApp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else if (!originProps.mIgnore) {
+ nsCOMPtr<nsIBinaryOutputStream> stream;
+ rv = GetBinaryOutputStream(originProps.mDirectory,
+ NS_LITERAL_STRING(METADATA_FILE_NAME),
+ kAppendFileFlag,
+ getter_AddRefs(stream));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(stream);
+
+ rv = stream->WriteBoolean(originProps.mIsApp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+UpgradeDirectoryMetadataFrom1To2Helper::UpgradeMetadataFiles()
+{
+ AssertIsOnIOThread();
+
+ bool exists;
+ nsresult rv = mDirectory->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!exists) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ rv = mDirectory->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> originDir = do_QueryInterface(entry);
+ MOZ_ASSERT(originDir);
+
+ bool isDirectory;
+ rv = originDir->IsDirectory(&isDirectory);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!isDirectory) {
+ nsString leafName;
+ rv = originDir->GetLeafName(leafName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!leafName.EqualsLiteral(DSSTORE_FILE_NAME)) {
+ QM_WARNING("Something (%s) in the storage directory that doesn't belong!",
+ NS_ConvertUTF16toUTF8(leafName).get());
+
+ }
+ continue;
+ }
+
+ OriginProps* originProps;
+ rv = AddOriginDirectory(originDir, &originProps);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ int64_t timestamp;
+ nsCString group;
+ nsCString origin;
+ bool isApp;
+ nsresult rv = GetDirectoryMetadata(originDir,
+ &timestamp,
+ group,
+ origin,
+ &isApp);
+ if (NS_FAILED(rv)) {
+ if (!mPersistent) {
+ rv = GetLastModifiedTime(originDir, &timestamp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ originProps->mTimestamp = timestamp;
+ }
+ originProps->mNeedsRestore = true;
+ } else {
+ originProps->mTimestamp = timestamp;
+ }
+ }
+
+ if (mOriginProps.IsEmpty()) {
+ return NS_OK;
+ }
+
+ rv = ProcessOriginDirectories();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+UpgradeDirectoryMetadataFrom1To2Helper::GetDirectoryMetadata(
+ nsIFile* aDirectory,
+ int64_t* aTimestamp,
+ nsACString& aGroup,
+ nsACString& aOrigin,
+ bool* aIsApp)
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(aDirectory);
+ MOZ_ASSERT(aTimestamp);
+ MOZ_ASSERT(aIsApp);
+
+ nsCOMPtr<nsIBinaryInputStream> binaryStream;
+ nsresult rv = GetBinaryInputStream(aDirectory,
+ NS_LITERAL_STRING(METADATA_FILE_NAME),
+ 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;
+ }
+
+ nsCString group;
+ rv = binaryStream->ReadCString(group);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCString origin;
+ rv = binaryStream->ReadCString(origin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool isApp;
+ rv = binaryStream->ReadBoolean(&isApp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ *aTimestamp = timestamp;
+ aGroup = group;
+ aOrigin = origin;
+ *aIsApp = isApp;
+ return NS_OK;
+}
+
+nsresult
+UpgradeDirectoryMetadataFrom1To2Helper::DoProcessOriginDirectories()
+{
+ AssertIsOnIOThread();
+
+ for (uint32_t count = mOriginProps.Length(), index = 0;
+ index < count;
+ index++) {
+ OriginProps& originProps = mOriginProps[index];
+
+ nsresult rv;
+
+ if (originProps.mNeedsRestore) {
+ rv = CreateDirectoryMetadata(originProps.mDirectory,
+ originProps.mTimestamp,
+ originProps.mSuffix,
+ originProps.mGroup,
+ originProps.mOrigin,
+ originProps.mIsApp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ rv = CreateDirectoryMetadata2(originProps.mDirectory,
+ originProps.mTimestamp,
+ originProps.mSuffix,
+ originProps.mGroup,
+ originProps.mOrigin,
+ originProps.mIsApp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsString oldName;
+ rv = originProps.mDirectory->GetLeafName(oldName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoCString originSanitized(originProps.mOrigin);
+ SanitizeOriginString(originSanitized);
+
+ NS_ConvertASCIItoUTF16 newName(originSanitized);
+
+ if (!oldName.Equals(newName)) {
+ rv = originProps.mDirectory->RenameTo(nullptr, newName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+RestoreDirectoryMetadata2Helper::RestoreMetadata2File()
+{
+ AssertIsOnIOThread();
+
+ nsresult rv;
+
+ OriginProps* originProps;
+ rv = AddOriginDirectory(mDirectory, &originProps);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!mPersistent) {
+ int64_t timestamp = INT64_MIN;
+ rv = GetLastModifiedTime(mDirectory, &timestamp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ originProps->mTimestamp = timestamp;
+ }
+
+ rv = ProcessOriginDirectories();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+RestoreDirectoryMetadata2Helper::DoProcessOriginDirectories()
+{
+ AssertIsOnIOThread();
+ MOZ_ASSERT(mOriginProps.Length() == 1);
+
+ OriginProps& originProps = mOriginProps[0];
+
+ nsresult rv = CreateDirectoryMetadata2(originProps.mDirectory,
+ originProps.mTimestamp,
+ originProps.mSuffix,
+ originProps.mGroup,
+ originProps.mOrigin,
+ originProps.mIsApp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+} // namespace quota
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/quota/ActorsParent.h b/dom/quota/ActorsParent.h
new file mode 100644
index 000000000..06ed9d342
--- /dev/null
+++ b/dom/quota/ActorsParent.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_quota_ActorsParent_h
+#define mozilla_dom_quota_ActorsParent_h
+
+namespace mozilla {
+namespace dom {
+namespace quota {
+
+class PQuotaParent;
+
+PQuotaParent*
+AllocPQuotaParent();
+
+bool
+DeallocPQuotaParent(PQuotaParent* aActor);
+
+} // namespace quota
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_quota_ActorsParent_h
diff --git a/dom/quota/Client.h b/dom/quota/Client.h
new file mode 100644
index 000000000..ecbddebdf
--- /dev/null
+++ b/dom/quota/Client.h
@@ -0,0 +1,151 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_quota_client_h__
+#define mozilla_dom_quota_client_h__
+
+#include "mozilla/dom/quota/QuotaCommon.h"
+
+#include "mozilla/dom/ipc/IdType.h"
+
+#include "PersistenceType.h"
+
+class nsIRunnable;
+
+#define IDB_DIRECTORY_NAME "idb"
+#define ASMJSCACHE_DIRECTORY_NAME "asmjs"
+#define DOMCACHE_DIRECTORY_NAME "cache"
+
+BEGIN_QUOTA_NAMESPACE
+
+class QuotaManager;
+class UsageInfo;
+
+// An abstract interface for quota manager clients.
+// Each storage API must provide an implementation of this interface in order
+// to participate in centralized quota and storage handling.
+class Client
+{
+public:
+ typedef mozilla::Atomic<bool> AtomicBool;
+
+ NS_IMETHOD_(MozExternalRefCountType)
+ AddRef() = 0;
+
+ NS_IMETHOD_(MozExternalRefCountType)
+ Release() = 0;
+
+ enum Type {
+ IDB = 0,
+ //LS,
+ //APPCACHE,
+ ASMJS,
+ DOMCACHE,
+ TYPE_MAX
+ };
+
+ virtual Type
+ GetType() = 0;
+
+ static nsresult
+ TypeToText(Type aType, nsAString& aText)
+ {
+ switch (aType) {
+ case IDB:
+ aText.AssignLiteral(IDB_DIRECTORY_NAME);
+ break;
+
+ case ASMJS:
+ aText.AssignLiteral(ASMJSCACHE_DIRECTORY_NAME);
+ break;
+
+ case DOMCACHE:
+ aText.AssignLiteral(DOMCACHE_DIRECTORY_NAME);
+ break;
+
+ case TYPE_MAX:
+ default:
+ NS_NOTREACHED("Bad id value!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+ }
+
+ static nsresult
+ TypeFromText(const nsAString& aText, Type& aType)
+ {
+ if (aText.EqualsLiteral(IDB_DIRECTORY_NAME)) {
+ aType = IDB;
+ }
+ else if (aText.EqualsLiteral(ASMJSCACHE_DIRECTORY_NAME)) {
+ aType = ASMJS;
+ }
+ else if (aText.EqualsLiteral(DOMCACHE_DIRECTORY_NAME)) {
+ aType = DOMCACHE;
+ }
+ else {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+ }
+
+ // Methods which are called on the IO thred.
+ virtual nsresult
+ InitOrigin(PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ const AtomicBool& aCanceled,
+ UsageInfo* aUsageInfo) = 0;
+
+ virtual nsresult
+ GetUsageForOrigin(PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ const AtomicBool& aCanceled,
+ UsageInfo* aUsageInfo) = 0;
+
+ virtual void
+ OnOriginClearCompleted(PersistenceType aPersistenceType,
+ const nsACString& aOrigin) = 0;
+
+ virtual void
+ ReleaseIOThreadObjects() = 0;
+
+ // Methods which are called on the background thred.
+ virtual void
+ AbortOperations(const nsACString& aOrigin) = 0;
+
+ virtual void
+ AbortOperationsForProcess(ContentParentId aContentParentId) = 0;
+
+ virtual void
+ StartIdleMaintenance() = 0;
+
+ virtual void
+ StopIdleMaintenance() = 0;
+
+ virtual void
+ ShutdownWorkThreads() = 0;
+
+ // Methods which are called on the main thread.
+ virtual void
+ DidInitialize(QuotaManager* aQuotaManager)
+ { }
+
+ virtual void
+ WillShutdown()
+ { }
+
+protected:
+ virtual ~Client()
+ { }
+};
+
+END_QUOTA_NAMESPACE
+
+#endif // mozilla_dom_quota_client_h__
diff --git a/dom/quota/FileStreams.cpp b/dom/quota/FileStreams.cpp
new file mode 100644
index 000000000..785a7db2e
--- /dev/null
+++ b/dom/quota/FileStreams.cpp
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FileStreams.h"
+
+#include "QuotaManager.h"
+#include "prio.h"
+
+USING_QUOTA_NAMESPACE
+
+template <class FileStreamBase>
+NS_IMETHODIMP
+FileQuotaStream<FileStreamBase>::SetEOF()
+{
+ nsresult rv = FileStreamBase::SetEOF();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mQuotaObject) {
+ int64_t offset;
+ nsresult rv = FileStreamBase::Tell(&offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mQuotaObject->MaybeUpdateSize(offset, /* aTruncate */ true);
+ }
+
+ return NS_OK;
+}
+
+template <class FileStreamBase>
+NS_IMETHODIMP
+FileQuotaStream<FileStreamBase>::Close()
+{
+ nsresult rv = FileStreamBase::Close();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mQuotaObject = nullptr;
+
+ return NS_OK;
+}
+
+template <class FileStreamBase>
+nsresult
+FileQuotaStream<FileStreamBase>::DoOpen()
+{
+ QuotaManager* quotaManager = QuotaManager::Get();
+ NS_ASSERTION(quotaManager, "Shouldn't be null!");
+
+ NS_ASSERTION(!mQuotaObject, "Creating quota object more than once?");
+ mQuotaObject = quotaManager->GetQuotaObject(mPersistenceType, mGroup, mOrigin,
+ FileStreamBase::mOpenParams.localFile);
+
+ nsresult rv = FileStreamBase::DoOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mQuotaObject && (FileStreamBase::mOpenParams.ioFlags & PR_TRUNCATE)) {
+ mQuotaObject->MaybeUpdateSize(0, /* aTruncate */ true);
+ }
+
+ return NS_OK;
+}
+
+template <class FileStreamBase>
+NS_IMETHODIMP
+FileQuotaStreamWithWrite<FileStreamBase>::Write(const char* aBuf,
+ uint32_t aCount,
+ uint32_t* _retval)
+{
+ nsresult rv;
+
+ if (FileQuotaStreamWithWrite::mQuotaObject) {
+ int64_t offset;
+ rv = FileStreamBase::Tell(&offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_ASSERT(INT64_MAX - offset >= int64_t(aCount));
+
+ if (!FileQuotaStreamWithWrite::
+ mQuotaObject->MaybeUpdateSize(offset + int64_t(aCount),
+ /* aTruncate */ false)) {
+ return NS_ERROR_FILE_NO_DEVICE_SPACE;
+ }
+ }
+
+ rv = FileStreamBase::Write(aBuf, aCount, _retval);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(FileInputStream, nsFileInputStream)
+
+already_AddRefed<FileInputStream>
+FileInputStream::Create(PersistenceType aPersistenceType,
+ const nsACString& aGroup, const nsACString& aOrigin,
+ nsIFile* aFile, int32_t aIOFlags, int32_t aPerm,
+ int32_t aBehaviorFlags)
+{
+ RefPtr<FileInputStream> stream =
+ new FileInputStream(aPersistenceType, aGroup, aOrigin);
+ nsresult rv = stream->Init(aFile, aIOFlags, aPerm, aBehaviorFlags);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ return stream.forget();
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(FileOutputStream, nsFileOutputStream)
+
+already_AddRefed<FileOutputStream>
+FileOutputStream::Create(PersistenceType aPersistenceType,
+ const nsACString& aGroup, const nsACString& aOrigin,
+ nsIFile* aFile, int32_t aIOFlags, int32_t aPerm,
+ int32_t aBehaviorFlags)
+{
+ RefPtr<FileOutputStream> stream =
+ new FileOutputStream(aPersistenceType, aGroup, aOrigin);
+ nsresult rv = stream->Init(aFile, aIOFlags, aPerm, aBehaviorFlags);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ return stream.forget();
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(FileStream, nsFileStream)
+
+already_AddRefed<FileStream>
+FileStream::Create(PersistenceType aPersistenceType, const nsACString& aGroup,
+ const nsACString& aOrigin, nsIFile* aFile, int32_t aIOFlags,
+ int32_t aPerm, int32_t aBehaviorFlags)
+{
+ RefPtr<FileStream> stream =
+ new FileStream(aPersistenceType, aGroup, aOrigin);
+ nsresult rv = stream->Init(aFile, aIOFlags, aPerm, aBehaviorFlags);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ return stream.forget();
+}
diff --git a/dom/quota/FileStreams.h b/dom/quota/FileStreams.h
new file mode 100644
index 000000000..67b09fa7d
--- /dev/null
+++ b/dom/quota/FileStreams.h
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_quota_filestreams_h__
+#define mozilla_dom_quota_filestreams_h__
+
+#include "QuotaCommon.h"
+
+#include "nsFileStreams.h"
+
+#include "PersistenceType.h"
+#include "QuotaObject.h"
+
+BEGIN_QUOTA_NAMESPACE
+
+template <class FileStreamBase>
+class FileQuotaStream : public FileStreamBase
+{
+public:
+ // nsFileStreamBase override
+ NS_IMETHOD
+ SetEOF() override;
+
+ NS_IMETHOD
+ Close() override;
+
+protected:
+ FileQuotaStream(PersistenceType aPersistenceType, const nsACString& aGroup,
+ const nsACString& aOrigin)
+ : mPersistenceType(aPersistenceType), mGroup(aGroup), mOrigin(aOrigin)
+ { }
+
+ // nsFileStreamBase override
+ virtual nsresult
+ DoOpen() override;
+
+ PersistenceType mPersistenceType;
+ nsCString mGroup;
+ nsCString mOrigin;
+ RefPtr<QuotaObject> mQuotaObject;
+};
+
+template <class FileStreamBase>
+class FileQuotaStreamWithWrite : public FileQuotaStream<FileStreamBase>
+{
+public:
+ // nsFileStreamBase override
+ NS_IMETHOD
+ Write(const char* aBuf, uint32_t aCount, uint32_t* _retval) override;
+
+protected:
+ FileQuotaStreamWithWrite(PersistenceType aPersistenceType,
+ const nsACString& aGroup, const nsACString& aOrigin)
+ : FileQuotaStream<FileStreamBase>(aPersistenceType, aGroup, aOrigin)
+ { }
+};
+
+class FileInputStream : public FileQuotaStream<nsFileInputStream>
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ static already_AddRefed<FileInputStream>
+ Create(PersistenceType aPersistenceType, const nsACString& aGroup,
+ const nsACString& aOrigin, nsIFile* aFile, int32_t aIOFlags = -1,
+ int32_t aPerm = -1, int32_t aBehaviorFlags = 0);
+
+private:
+ FileInputStream(PersistenceType aPersistenceType, const nsACString& aGroup,
+ const nsACString& aOrigin)
+ : FileQuotaStream<nsFileInputStream>(aPersistenceType, aGroup, aOrigin)
+ { }
+
+ virtual ~FileInputStream() {
+ Close();
+ }
+};
+
+class FileOutputStream : public FileQuotaStreamWithWrite<nsFileOutputStream>
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ static already_AddRefed<FileOutputStream>
+ Create(PersistenceType aPersistenceType, const nsACString& aGroup,
+ const nsACString& aOrigin, nsIFile* aFile, int32_t aIOFlags = -1,
+ int32_t aPerm = -1, int32_t aBehaviorFlags = 0);
+
+private:
+ FileOutputStream(PersistenceType aPersistenceType, const nsACString& aGroup,
+ const nsACString& aOrigin)
+ : FileQuotaStreamWithWrite<nsFileOutputStream>(aPersistenceType, aGroup,
+ aOrigin)
+ { }
+
+ virtual ~FileOutputStream() {
+ Close();
+ }
+};
+
+class FileStream : public FileQuotaStreamWithWrite<nsFileStream>
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ static already_AddRefed<FileStream>
+ Create(PersistenceType aPersistenceType, const nsACString& aGroup,
+ const nsACString& aOrigin, nsIFile* aFile, int32_t aIOFlags = -1,
+ int32_t aPerm = -1, int32_t aBehaviorFlags = 0);
+
+private:
+ FileStream(PersistenceType aPersistenceType, const nsACString& aGroup,
+ const nsACString& aOrigin)
+ : FileQuotaStreamWithWrite<nsFileStream>(aPersistenceType, aGroup, aOrigin)
+ { }
+
+ virtual ~FileStream() {
+ Close();
+ }
+};
+
+END_QUOTA_NAMESPACE
+
+#endif /* mozilla_dom_quota_filestreams_h__ */
diff --git a/dom/quota/OriginScope.h b/dom/quota/OriginScope.h
new file mode 100644
index 000000000..e57f2dd76
--- /dev/null
+++ b/dom/quota/OriginScope.h
@@ -0,0 +1,428 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_quota_originorpatternstring_h__
+#define mozilla_dom_quota_originorpatternstring_h__
+
+#include "mozilla/dom/quota/QuotaCommon.h"
+
+#include "mozilla/BasePrincipal.h"
+
+BEGIN_QUOTA_NAMESPACE
+
+class OriginScope
+{
+public:
+ enum Type
+ {
+ eOrigin,
+ ePattern,
+ ePrefix,
+ eNull
+ };
+
+private:
+ struct OriginAndAttributes
+ {
+ nsCString mOrigin;
+ PrincipalOriginAttributes mAttributes;
+
+ OriginAndAttributes(const OriginAndAttributes& aOther)
+ : mOrigin(aOther.mOrigin)
+ , mAttributes(aOther.mAttributes)
+ {
+ MOZ_COUNT_CTOR(OriginAndAttributes);
+ }
+
+ explicit OriginAndAttributes(const nsACString& aOrigin)
+ : mOrigin(aOrigin)
+ {
+ nsCString originNoSuffix;
+ MOZ_ALWAYS_TRUE(mAttributes.PopulateFromOrigin(aOrigin, originNoSuffix));
+
+ MOZ_COUNT_CTOR(OriginAndAttributes);
+ }
+
+ ~OriginAndAttributes()
+ {
+ MOZ_COUNT_DTOR(OriginAndAttributes);
+ }
+ };
+
+ union {
+ // eOrigin
+ OriginAndAttributes* mOriginAndAttributes;
+
+ // ePattern
+ mozilla::OriginAttributesPattern* mPattern;
+
+ // ePrefix
+ nsCString* mPrefix;
+
+ // eNull
+ void* mDummy;
+ };
+
+ Type mType;
+
+public:
+ static OriginScope
+ FromOrigin(const nsACString& aOrigin)
+ {
+ return OriginScope(aOrigin, true);
+ }
+
+ static OriginScope
+ FromPattern(const mozilla::OriginAttributesPattern& aPattern)
+ {
+ return OriginScope(aPattern);
+ }
+
+ static OriginScope
+ FromJSONPattern(const nsAString& aJSONPattern)
+ {
+ return OriginScope(aJSONPattern);
+ }
+
+ static OriginScope
+ FromPrefix(const nsACString& aPrefix)
+ {
+ return OriginScope(aPrefix, false);
+ }
+
+ static OriginScope
+ FromNull()
+ {
+ return OriginScope();
+ }
+
+ OriginScope(const OriginScope& aOther)
+ {
+ if (aOther.IsOrigin()) {
+ mOriginAndAttributes =
+ new OriginAndAttributes(*aOther.mOriginAndAttributes);
+ } else if (aOther.IsPattern()) {
+ mPattern = new mozilla::OriginAttributesPattern(*aOther.mPattern);
+ } else if (aOther.IsPrefix()) {
+ mPrefix = new nsCString(*aOther.mPrefix);
+ } else {
+ mDummy = aOther.mDummy;
+ }
+
+ mType = aOther.mType;
+ }
+
+ ~OriginScope()
+ {
+ Destroy();
+ }
+
+ bool
+ IsOrigin() const
+ {
+ return mType == eOrigin;
+ }
+
+ bool
+ IsPattern() const
+ {
+ return mType == ePattern;
+ }
+
+ bool
+ IsPrefix() const
+ {
+ return mType == ePrefix;
+ }
+
+ bool
+ IsNull() const
+ {
+ return mType == eNull;
+ }
+
+ Type
+ GetType() const
+ {
+ return mType;
+ }
+
+ void
+ SetFromOrigin(const nsACString& aOrigin)
+ {
+ Destroy();
+
+ mOriginAndAttributes = new OriginAndAttributes(aOrigin);
+
+ mType = eOrigin;
+ }
+
+ void
+ SetFromPattern(const mozilla::OriginAttributesPattern& aPattern)
+ {
+ Destroy();
+
+ mPattern = new mozilla::OriginAttributesPattern(aPattern);
+
+ mType = ePattern;
+ }
+
+ void
+ SetFromJSONPattern(const nsAString& aJSONPattern)
+ {
+ Destroy();
+
+ mPattern = new mozilla::OriginAttributesPattern();
+ MOZ_ALWAYS_TRUE(mPattern->Init(aJSONPattern));
+
+ mType = ePattern;
+ }
+
+ void
+ SetFromPrefix(const nsACString& aPrefix)
+ {
+ Destroy();
+
+ mPrefix = new nsCString(aPrefix);
+
+ mType = ePrefix;
+ }
+
+ void
+ SetFromNull()
+ {
+ Destroy();
+
+ mDummy = nullptr;
+
+ mType = eNull;
+ }
+
+ const nsACString&
+ GetOrigin() const
+ {
+ MOZ_ASSERT(IsOrigin());
+ MOZ_ASSERT(mOriginAndAttributes);
+
+ return mOriginAndAttributes->mOrigin;
+ }
+
+ void
+ SetOrigin(const nsACString& aOrigin)
+ {
+ MOZ_ASSERT(IsOrigin());
+ MOZ_ASSERT(mOriginAndAttributes);
+ mOriginAndAttributes->mOrigin = aOrigin;
+ }
+
+ const mozilla::OriginAttributes&
+ GetOriginAttributes() const
+ {
+ MOZ_ASSERT(IsOrigin());
+ MOZ_ASSERT(mOriginAndAttributes);
+ return mOriginAndAttributes->mAttributes;
+ }
+
+ const mozilla::OriginAttributesPattern&
+ GetPattern() const
+ {
+ MOZ_ASSERT(IsPattern());
+ MOZ_ASSERT(mPattern);
+ return *mPattern;
+ }
+
+ const nsACString&
+ GetPrefix() const
+ {
+ MOZ_ASSERT(IsPrefix());
+ MOZ_ASSERT(mPrefix);
+
+ return *mPrefix;
+ }
+
+ void
+ SetPrefix(const nsACString& aPrefix)
+ {
+ MOZ_ASSERT(IsPrefix());
+ MOZ_ASSERT(mPrefix);
+
+ *mPrefix = aPrefix;
+ }
+
+ bool MatchesOrigin(const OriginScope& aOther) const
+ {
+ MOZ_ASSERT(aOther.IsOrigin());
+ MOZ_ASSERT(aOther.mOriginAndAttributes);
+
+ bool match;
+
+ if (IsOrigin()) {
+ MOZ_ASSERT(mOriginAndAttributes);
+ match = mOriginAndAttributes->mOrigin.Equals(
+ aOther.mOriginAndAttributes->mOrigin);
+ } else if (IsPattern()) {
+ MOZ_ASSERT(mPattern);
+ match = mPattern->Matches(aOther.mOriginAndAttributes->mAttributes);
+ } else if (IsPrefix()) {
+ MOZ_ASSERT(mPrefix);
+ match = StringBeginsWith(aOther.mOriginAndAttributes->mOrigin, *mPrefix);
+ } else {
+ match = true;
+ }
+
+ return match;
+ }
+
+ bool MatchesPattern(const OriginScope& aOther) const
+ {
+ MOZ_ASSERT(aOther.IsPattern());
+ MOZ_ASSERT(aOther.mPattern);
+
+ bool match;
+
+ if (IsOrigin()) {
+ MOZ_ASSERT(mOriginAndAttributes);
+ match = aOther.mPattern->Matches(mOriginAndAttributes->mAttributes);
+ } else if (IsPattern()) {
+ MOZ_ASSERT(mPattern);
+ match = mPattern->Overlaps(*aOther.mPattern);
+ } else if (IsPrefix()) {
+ MOZ_ASSERT(mPrefix);
+ // The match will be always true here because any origin attributes
+ // pattern overlaps any origin prefix (an origin prefix targets all
+ // origin attributes).
+ match = true;
+ } else {
+ match = true;
+ }
+
+ return match;
+ }
+
+ bool MatchesPrefix(const OriginScope& aOther) const
+ {
+ MOZ_ASSERT(aOther.IsPrefix());
+ MOZ_ASSERT(aOther.mPrefix);
+
+ bool match;
+
+ if (IsOrigin()) {
+ MOZ_ASSERT(mOriginAndAttributes);
+ match = StringBeginsWith(mOriginAndAttributes->mOrigin, *aOther.mPrefix);
+ } else if (IsPattern()) {
+ MOZ_ASSERT(mPattern);
+ // The match will be always true here because any origin attributes
+ // pattern overlaps any origin prefix (an origin prefix targets all
+ // origin attributes).
+ match = true;
+ } else if (IsPrefix()) {
+ MOZ_ASSERT(mPrefix);
+ match = mPrefix->Equals(*aOther.mPrefix);
+ } else {
+ match = true;
+ }
+
+ return match;
+ }
+
+ bool Matches(const OriginScope& aOther) const
+ {
+ bool match;
+
+ if (aOther.IsOrigin()) {
+ match = MatchesOrigin(aOther);
+ } else if (aOther.IsPattern()) {
+ match = MatchesPattern(aOther);
+ } else if (aOther.IsPrefix()) {
+ match = MatchesPrefix(aOther);
+ } else {
+ match = true;
+ }
+
+ return match;
+ }
+
+ OriginScope
+ Clone()
+ {
+ if (IsOrigin()) {
+ MOZ_ASSERT(mOriginAndAttributes);
+ return OriginScope(*mOriginAndAttributes);
+ }
+
+ if (IsPattern()) {
+ MOZ_ASSERT(mPattern);
+ return OriginScope(*mPattern);
+ }
+
+ if (IsPrefix()) {
+ MOZ_ASSERT(mPrefix);
+ return OriginScope(*mPrefix, false);
+ }
+
+ MOZ_ASSERT(IsNull());
+ return OriginScope();
+ }
+
+private:
+ explicit OriginScope(const OriginAndAttributes& aOriginAndAttributes)
+ : mOriginAndAttributes(new OriginAndAttributes(aOriginAndAttributes))
+ , mType(eOrigin)
+ { }
+
+ explicit OriginScope(const nsACString& aOriginOrPrefix, bool aOrigin)
+ {
+ if (aOrigin) {
+ mOriginAndAttributes = new OriginAndAttributes(aOriginOrPrefix);
+ mType = eOrigin;
+ } else {
+ mPrefix = new nsCString(aOriginOrPrefix);
+ mType = ePrefix;
+ }
+ }
+
+ explicit OriginScope(const mozilla::OriginAttributesPattern& aPattern)
+ : mPattern(new mozilla::OriginAttributesPattern(aPattern))
+ , mType(ePattern)
+ { }
+
+ explicit OriginScope(const nsAString& aJSONPattern)
+ : mPattern(new mozilla::OriginAttributesPattern())
+ , mType(ePattern)
+ {
+ MOZ_ALWAYS_TRUE(mPattern->Init(aJSONPattern));
+ }
+
+ OriginScope()
+ : mDummy(nullptr)
+ , mType(eNull)
+ { }
+
+ void
+ Destroy()
+ {
+ if (IsOrigin()) {
+ MOZ_ASSERT(mOriginAndAttributes);
+ delete mOriginAndAttributes;
+ mOriginAndAttributes = nullptr;
+ } else if (IsPattern()) {
+ MOZ_ASSERT(mPattern);
+ delete mPattern;
+ mPattern = nullptr;
+ } else if (IsPrefix()) {
+ MOZ_ASSERT(mPrefix);
+ delete mPrefix;
+ mPrefix = nullptr;
+ }
+ }
+
+ bool
+ operator==(const OriginScope& aOther) = delete;
+};
+
+END_QUOTA_NAMESPACE
+
+#endif // mozilla_dom_quota_originorpatternstring_h__
diff --git a/dom/quota/PQuota.ipdl b/dom/quota/PQuota.ipdl
new file mode 100644
index 000000000..b9a7a3b84
--- /dev/null
+++ b/dom/quota/PQuota.ipdl
@@ -0,0 +1,87 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PBackground;
+include protocol PQuotaRequest;
+include protocol PQuotaUsageRequest;
+
+include PBackgroundSharedTypes;
+
+include "mozilla/dom/quota/SerializationHelpers.h";
+
+using mozilla::dom::quota::PersistenceType
+ from "mozilla/dom/quota/PersistenceType.h";
+
+namespace mozilla {
+namespace dom {
+namespace quota {
+
+struct AllUsageParams
+{
+ bool getAll;
+};
+
+struct OriginUsageParams
+{
+ PrincipalInfo principalInfo;
+ bool getGroupUsage;
+};
+
+union UsageRequestParams
+{
+ AllUsageParams;
+ OriginUsageParams;
+};
+
+struct ClearOriginParams
+{
+ PrincipalInfo principalInfo;
+ PersistenceType persistenceType;
+ bool persistenceTypeIsExplicit;
+ bool clearAll;
+};
+
+struct ClearOriginsParams
+{
+ nsString pattern;
+};
+
+struct ClearAllParams
+{
+};
+
+struct ResetAllParams
+{
+};
+
+union RequestParams
+{
+ ClearOriginParams;
+ ClearOriginsParams;
+ ClearAllParams;
+ ResetAllParams;
+};
+
+protocol PQuota
+{
+ manager PBackground;
+
+ manages PQuotaRequest;
+ manages PQuotaUsageRequest;
+
+parent:
+ async __delete__();
+
+ async PQuotaUsageRequest(UsageRequestParams params);
+
+ async PQuotaRequest(RequestParams params);
+
+ async StartIdleMaintenance();
+
+ async StopIdleMaintenance();
+};
+
+} // namespace quota
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/quota/PQuotaRequest.ipdl b/dom/quota/PQuotaRequest.ipdl
new file mode 100644
index 000000000..212846929
--- /dev/null
+++ b/dom/quota/PQuotaRequest.ipdl
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PQuota;
+
+namespace mozilla {
+namespace dom {
+namespace quota {
+
+struct ClearOriginResponse
+{
+};
+
+struct ClearOriginsResponse
+{
+};
+
+struct ClearAllResponse
+{
+};
+
+struct ResetAllResponse
+{
+};
+
+union RequestResponse
+{
+ nsresult;
+ ClearOriginResponse;
+ ClearOriginsResponse;
+ ClearAllResponse;
+ ResetAllResponse;
+};
+
+protocol PQuotaRequest
+{
+ manager PQuota;
+
+child:
+ async __delete__(RequestResponse response);
+};
+
+} // namespace quota
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/quota/PQuotaUsageRequest.ipdl b/dom/quota/PQuotaUsageRequest.ipdl
new file mode 100644
index 000000000..16994e627
--- /dev/null
+++ b/dom/quota/PQuotaUsageRequest.ipdl
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PQuota;
+
+namespace mozilla {
+namespace dom {
+namespace quota {
+
+struct OriginUsage
+{
+ nsCString origin;
+ bool persisted;
+ uint64_t usage;
+};
+
+struct AllUsageResponse
+{
+ OriginUsage[] originUsages;
+};
+
+struct OriginUsageResponse
+{
+ uint64_t usage;
+ uint64_t fileUsage;
+ uint64_t limit;
+};
+
+union UsageRequestResponse
+{
+ nsresult;
+ AllUsageResponse;
+ OriginUsageResponse;
+};
+
+protocol PQuotaUsageRequest
+{
+ manager PQuota;
+
+parent:
+ async Cancel();
+
+child:
+ async __delete__(UsageRequestResponse response);
+};
+
+} // namespace quota
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/quota/PersistenceType.h b/dom/quota/PersistenceType.h
new file mode 100644
index 000000000..3e749e16c
--- /dev/null
+++ b/dom/quota/PersistenceType.h
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_quota_persistencetype_h__
+#define mozilla_dom_quota_persistencetype_h__
+
+#include "mozilla/dom/quota/QuotaCommon.h"
+
+#include "mozilla/dom/StorageTypeBinding.h"
+
+BEGIN_QUOTA_NAMESPACE
+
+enum PersistenceType
+{
+ PERSISTENCE_TYPE_PERSISTENT = 0,
+ PERSISTENCE_TYPE_TEMPORARY,
+ PERSISTENCE_TYPE_DEFAULT,
+
+ // Only needed for IPC serialization helper, should never be used in code.
+ PERSISTENCE_TYPE_INVALID
+};
+
+static const PersistenceType kAllPersistenceTypes[] = {
+ PERSISTENCE_TYPE_PERSISTENT,
+ PERSISTENCE_TYPE_TEMPORARY,
+ PERSISTENCE_TYPE_DEFAULT
+};
+
+inline void
+PersistenceTypeToText(PersistenceType aPersistenceType, nsACString& aText)
+{
+ switch (aPersistenceType) {
+ case PERSISTENCE_TYPE_PERSISTENT:
+ aText.AssignLiteral("persistent");
+ return;
+ case PERSISTENCE_TYPE_TEMPORARY:
+ aText.AssignLiteral("temporary");
+ return;
+ case PERSISTENCE_TYPE_DEFAULT:
+ aText.AssignLiteral("default");
+ return;
+
+ case PERSISTENCE_TYPE_INVALID:
+ default:
+ MOZ_CRASH("Bad persistence type value!");
+ }
+}
+
+inline PersistenceType
+PersistenceTypeFromText(const nsACString& aText)
+{
+ if (aText.EqualsLiteral("persistent")) {
+ return PERSISTENCE_TYPE_PERSISTENT;
+ }
+
+ if (aText.EqualsLiteral("temporary")) {
+ return PERSISTENCE_TYPE_TEMPORARY;
+ }
+
+ if (aText.EqualsLiteral("default")) {
+ return PERSISTENCE_TYPE_DEFAULT;
+ }
+
+ MOZ_CRASH("Should never get here!");
+}
+
+inline nsresult
+NullablePersistenceTypeFromText(const nsACString& aText,
+ Nullable<PersistenceType>* aPersistenceType)
+{
+ if (aText.IsVoid()) {
+ *aPersistenceType = Nullable<PersistenceType>();
+ return NS_OK;
+ }
+
+ if (aText.EqualsLiteral("persistent")) {
+ *aPersistenceType = Nullable<PersistenceType>(PERSISTENCE_TYPE_PERSISTENT);
+ return NS_OK;
+ }
+
+ if (aText.EqualsLiteral("temporary")) {
+ *aPersistenceType = Nullable<PersistenceType>(PERSISTENCE_TYPE_TEMPORARY);
+ return NS_OK;
+ }
+
+ if (aText.EqualsLiteral("default")) {
+ *aPersistenceType = Nullable<PersistenceType>(PERSISTENCE_TYPE_DEFAULT);
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+inline mozilla::dom::StorageType
+PersistenceTypeToStorage(PersistenceType aPersistenceType)
+{
+ return mozilla::dom::StorageType(static_cast<int>(aPersistenceType));
+}
+
+inline PersistenceType
+PersistenceTypeFromStorage(const Optional<mozilla::dom::StorageType>& aStorage)
+{
+ if (aStorage.WasPassed()) {
+ return PersistenceType(static_cast<int>(aStorage.Value()));
+ }
+
+ return PERSISTENCE_TYPE_DEFAULT;
+}
+
+inline PersistenceType
+ComplementaryPersistenceType(PersistenceType aPersistenceType)
+{
+ MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_DEFAULT ||
+ aPersistenceType == PERSISTENCE_TYPE_TEMPORARY);
+
+ if (aPersistenceType == PERSISTENCE_TYPE_DEFAULT) {
+ return PERSISTENCE_TYPE_TEMPORARY;
+ }
+
+ return PERSISTENCE_TYPE_DEFAULT;
+}
+
+END_QUOTA_NAMESPACE
+
+#endif // mozilla_dom_quota_persistencetype_h__
diff --git a/dom/quota/QuotaCommon.h b/dom/quota/QuotaCommon.h
new file mode 100644
index 000000000..d07d64293
--- /dev/null
+++ b/dom/quota/QuotaCommon.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_quota_quotacommon_h__
+#define mozilla_dom_quota_quotacommon_h__
+
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsPrintfCString.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+#define BEGIN_QUOTA_NAMESPACE \
+ namespace mozilla { namespace dom { namespace quota {
+#define END_QUOTA_NAMESPACE \
+ } /* namespace quota */ } /* namespace dom */ } /* namespace mozilla */
+#define USING_QUOTA_NAMESPACE \
+ using namespace mozilla::dom::quota;
+
+#define DSSTORE_FILE_NAME ".DS_Store"
+
+#define QM_WARNING(...) \
+ do { \
+ nsPrintfCString str(__VA_ARGS__); \
+ mozilla::dom::quota::ReportInternalError(__FILE__, __LINE__, str.get()); \
+ NS_WARNING(str.get()); \
+ } while (0)
+
+class nsIEventTarget;
+
+BEGIN_QUOTA_NAMESPACE
+
+class BackgroundThreadObject
+{
+protected:
+ nsCOMPtr<nsIEventTarget> mOwningThread;
+
+public:
+ void
+ AssertIsOnOwningThread() const
+#ifdef DEBUG
+ ;
+#else
+ { }
+#endif
+
+ nsIEventTarget*
+ OwningThread() const;
+
+protected:
+ BackgroundThreadObject();
+
+ explicit BackgroundThreadObject(nsIEventTarget* aOwningThread);
+};
+
+void
+AssertIsOnIOThread();
+
+void
+AssertCurrentThreadOwnsQuotaMutex();
+
+bool
+IsOnIOThread();
+
+void
+ReportInternalError(const char* aFile, uint32_t aLine, const char* aStr);
+
+END_QUOTA_NAMESPACE
+
+#endif // mozilla_dom_quota_quotacommon_h__
diff --git a/dom/quota/QuotaManager.h b/dom/quota/QuotaManager.h
new file mode 100644
index 000000000..206c3c665
--- /dev/null
+++ b/dom/quota/QuotaManager.h
@@ -0,0 +1,546 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_quota_quotamanager_h__
+#define mozilla_dom_quota_quotamanager_h__
+
+#include "QuotaCommon.h"
+
+#include "mozilla/dom/Nullable.h"
+#include "mozilla/dom/ipc/IdType.h"
+#include "mozilla/Mutex.h"
+
+#include "nsClassHashtable.h"
+#include "nsRefPtrHashtable.h"
+
+#include "Client.h"
+#include "PersistenceType.h"
+
+#include "prenv.h"
+
+#define QUOTA_MANAGER_CONTRACTID "@mozilla.org/dom/quota/manager;1"
+
+class mozIStorageConnection;
+class nsIEventTarget;
+class nsIPrincipal;
+class nsIThread;
+class nsITimer;
+class nsIURI;
+class nsPIDOMWindowOuter;
+class nsIRunnable;
+
+BEGIN_QUOTA_NAMESPACE
+
+class DirectoryLockImpl;
+class GroupInfo;
+class GroupInfoPair;
+class OriginInfo;
+class OriginScope;
+class QuotaObject;
+
+class NS_NO_VTABLE RefCountedObject
+{
+public:
+ NS_IMETHOD_(MozExternalRefCountType)
+ AddRef() = 0;
+
+ NS_IMETHOD_(MozExternalRefCountType)
+ Release() = 0;
+};
+
+class DirectoryLock
+ : public RefCountedObject
+{
+ friend class DirectoryLockImpl;
+
+private:
+ DirectoryLock()
+ { }
+
+ ~DirectoryLock()
+ { }
+};
+
+class NS_NO_VTABLE OpenDirectoryListener
+ : public RefCountedObject
+{
+public:
+ virtual void
+ DirectoryLockAcquired(DirectoryLock* aLock) = 0;
+
+ virtual void
+ DirectoryLockFailed() = 0;
+
+protected:
+ virtual ~OpenDirectoryListener()
+ { }
+};
+
+struct OriginParams
+{
+ OriginParams(PersistenceType aPersistenceType,
+ const nsACString& aOrigin,
+ bool aIsApp)
+ : mOrigin(aOrigin)
+ , mPersistenceType(aPersistenceType)
+ , mIsApp(aIsApp)
+ { }
+
+ nsCString mOrigin;
+ PersistenceType mPersistenceType;
+ bool mIsApp;
+};
+
+class QuotaManager final
+ : public BackgroundThreadObject
+{
+ friend class DirectoryLockImpl;
+ friend class GroupInfo;
+ friend class OriginInfo;
+ friend class QuotaObject;
+
+ typedef nsClassHashtable<nsCStringHashKey,
+ nsTArray<DirectoryLockImpl*>> DirectoryLockTable;
+
+public:
+ class CreateRunnable;
+
+private:
+ class ShutdownRunnable;
+ class ShutdownObserver;
+
+public:
+ NS_INLINE_DECL_REFCOUNTING(QuotaManager)
+
+ static bool IsRunningXPCShellTests()
+ {
+ static bool kRunningXPCShellTests = !!PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR");
+ return kRunningXPCShellTests;
+ }
+
+ static const char kReplaceChars[];
+
+ static void
+ GetOrCreate(nsIRunnable* aCallback);
+
+ // Returns a non-owning reference.
+ static QuotaManager*
+ Get();
+
+ // Returns true if we've begun the shutdown process.
+ static bool IsShuttingDown();
+
+ bool
+ IsOriginInitialized(const nsACString& aOrigin) const
+ {
+ AssertIsOnIOThread();
+
+ return mInitializedOrigins.Contains(aOrigin);
+ }
+
+ bool
+ IsTemporaryStorageInitialized() const
+ {
+ AssertIsOnIOThread();
+
+ return mTemporaryStorageInitialized;
+ }
+
+ void
+ InitQuotaForOrigin(PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ bool aIsApp,
+ uint64_t aUsageBytes,
+ int64_t aAccessTime);
+
+ void
+ DecreaseUsageForOrigin(PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ int64_t aSize);
+
+ void
+ UpdateOriginAccessTime(PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin);
+
+ void
+ RemoveQuota();
+
+ void
+ RemoveQuotaForOrigin(PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin)
+ {
+ MutexAutoLock lock(mQuotaMutex);
+ LockedRemoveQuotaForOrigin(aPersistenceType, aGroup, aOrigin);
+ }
+
+ already_AddRefed<QuotaObject>
+ GetQuotaObject(PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ nsIFile* aFile);
+
+ already_AddRefed<QuotaObject>
+ GetQuotaObject(PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ const nsAString& aPath);
+
+ // Called when a process is being shot down. Aborts any running operations
+ // for the given process.
+ void
+ AbortOperationsForProcess(ContentParentId aContentParentId);
+
+ nsresult
+ GetDirectoryForOrigin(PersistenceType aPersistenceType,
+ const nsACString& aASCIIOrigin,
+ nsIFile** aDirectory) const;
+
+ nsresult
+ RestoreDirectoryMetadata2(nsIFile* aDirectory, bool aPersistent);
+
+ nsresult
+ GetDirectoryMetadata2(nsIFile* aDirectory,
+ int64_t* aTimestamp,
+ nsACString& aSuffix,
+ nsACString& aGroup,
+ nsACString& aOrigin,
+ bool* aIsApp);
+
+ nsresult
+ GetDirectoryMetadata2WithRestore(nsIFile* aDirectory,
+ bool aPersistent,
+ int64_t* aTimestamp,
+ nsACString& aSuffix,
+ nsACString& aGroup,
+ nsACString& aOrigin,
+ bool* aIsApp);
+
+ nsresult
+ GetDirectoryMetadata2(nsIFile* aDirectory, int64_t* aTimestamp);
+
+ nsresult
+ GetDirectoryMetadata2WithRestore(nsIFile* aDirectory,
+ bool aPersistent,
+ int64_t* aTimestamp);
+
+ // This is the main entry point into the QuotaManager API.
+ // Any storage API implementation (quota client) that participates in
+ // centralized quota and storage handling should call this method to get
+ // a directory lock which will protect client's files from being deleted
+ // while they are still in use.
+ // After a lock is acquired, client is notified via the open listener's
+ // method DirectoryLockAcquired. If the lock couldn't be acquired, client
+ // gets DirectoryLockFailed notification.
+ // A lock is a reference counted object and at the time DirectoryLockAcquired
+ // is called, quota manager holds just one strong reference to it which is
+ // then immediatelly cleared by quota manager. So it's up to client to add
+ // a new reference in order to keep the lock alive.
+ // Unlocking is simply done by dropping all references to the lock object.
+ // In other words, protection which the lock represents dies with the lock
+ // object itself.
+ void
+ OpenDirectory(PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ bool aIsApp,
+ Client::Type aClientType,
+ bool aExclusive,
+ OpenDirectoryListener* aOpenListener);
+
+ // XXX RemoveMe once bug 1170279 gets fixed.
+ void
+ OpenDirectoryInternal(Nullable<PersistenceType> aPersistenceType,
+ const OriginScope& aOriginScope,
+ Nullable<Client::Type> aClientType,
+ bool aExclusive,
+ OpenDirectoryListener* aOpenListener);
+
+ // Collect inactive and the least recently used origins.
+ uint64_t
+ CollectOriginsForEviction(uint64_t aMinSizeToBeFreed,
+ nsTArray<RefPtr<DirectoryLockImpl>>& aLocks);
+
+ nsresult
+ EnsureStorageIsInitialized();
+
+ nsresult
+ EnsureOriginIsInitialized(PersistenceType aPersistenceType,
+ const nsACString& aSuffix,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ bool aIsApp,
+ nsIFile** aDirectory);
+
+ void
+ OriginClearCompleted(PersistenceType aPersistenceType,
+ const nsACString& aOrigin,
+ bool aIsApp);
+
+ void
+ ResetOrClearCompleted();
+
+ void
+ StartIdleMaintenance()
+ {
+ AssertIsOnOwningThread();
+
+ for (auto& client : mClients) {
+ client->StartIdleMaintenance();
+ }
+ }
+
+ void
+ StopIdleMaintenance()
+ {
+ AssertIsOnOwningThread();
+
+ for (auto& client : mClients) {
+ client->StopIdleMaintenance();
+ }
+ }
+
+ void
+ AssertCurrentThreadOwnsQuotaMutex()
+ {
+ mQuotaMutex.AssertCurrentThreadOwns();
+ }
+
+ nsIThread*
+ IOThread()
+ {
+ NS_ASSERTION(mIOThread, "This should never be null!");
+ return mIOThread;
+ }
+
+ Client*
+ GetClient(Client::Type aClientType);
+
+ const nsString&
+ GetBasePath() const
+ {
+ return mBasePath;
+ }
+
+ const nsString&
+ GetStoragePath() const
+ {
+ return mStoragePath;
+ }
+
+ const nsString&
+ GetStoragePath(PersistenceType aPersistenceType) const
+ {
+ if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
+ return mPermanentStoragePath;
+ }
+
+ if (aPersistenceType == PERSISTENCE_TYPE_TEMPORARY) {
+ return mTemporaryStoragePath;
+ }
+
+ MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_DEFAULT);
+
+ return mDefaultStoragePath;
+ }
+
+ uint64_t
+ GetGroupLimit() const;
+
+ void
+ GetGroupUsageAndLimit(const nsACString& aGroup,
+ UsageInfo* aUsageInfo);
+
+ static void
+ GetStorageId(PersistenceType aPersistenceType,
+ const nsACString& aOrigin,
+ Client::Type aClientType,
+ nsACString& aDatabaseId);
+
+ static nsresult
+ GetInfoFromPrincipal(nsIPrincipal* aPrincipal,
+ nsACString* aSuffix,
+ nsACString* aGroup,
+ nsACString* aOrigin,
+ bool* aIsApp);
+
+ static nsresult
+ GetInfoFromWindow(nsPIDOMWindowOuter* aWindow,
+ nsACString* aSuffix,
+ nsACString* aGroup,
+ nsACString* aOrigin,
+ bool* aIsApp);
+
+ static void
+ GetInfoForChrome(nsACString* aSuffix,
+ nsACString* aGroup,
+ nsACString* aOrigin,
+ bool* aIsApp);
+
+ static bool
+ IsOriginInternal(const nsACString& aOrigin);
+
+ static bool
+ IsFirstPromptRequired(PersistenceType aPersistenceType,
+ const nsACString& aOrigin,
+ bool aIsApp);
+
+ static bool
+ IsQuotaEnforced(PersistenceType aPersistenceType,
+ const nsACString& aOrigin,
+ bool aIsApp);
+
+ static void
+ ChromeOrigin(nsACString& aOrigin);
+
+private:
+ QuotaManager();
+
+ virtual ~QuotaManager();
+
+ nsresult
+ Init(const nsAString& aBaseDirPath);
+
+ void
+ Shutdown();
+
+ already_AddRefed<DirectoryLockImpl>
+ CreateDirectoryLock(Nullable<PersistenceType> aPersistenceType,
+ const nsACString& aGroup,
+ const OriginScope& aOriginScope,
+ Nullable<bool> aIsApp,
+ Nullable<Client::Type> aClientType,
+ bool aExclusive,
+ bool aInternal,
+ OpenDirectoryListener* aOpenListener);
+
+ already_AddRefed<DirectoryLockImpl>
+ CreateDirectoryLockForEviction(PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ bool aIsApp);
+
+ void
+ RegisterDirectoryLock(DirectoryLockImpl* aLock);
+
+ void
+ UnregisterDirectoryLock(DirectoryLockImpl* aLock);
+
+ void
+ RemovePendingDirectoryLock(DirectoryLockImpl* aLock);
+
+ uint64_t
+ LockedCollectOriginsForEviction(
+ uint64_t aMinSizeToBeFreed,
+ nsTArray<RefPtr<DirectoryLockImpl>>& aLocks);
+
+ void
+ LockedRemoveQuotaForOrigin(PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin);
+
+ nsresult
+ MaybeUpgradeIndexedDBDirectory();
+
+ nsresult
+ MaybeUpgradePersistentStorageDirectory();
+
+ nsresult
+ MaybeRemoveOldDirectories();
+
+ nsresult
+ UpgradeStorageFrom0ToCurrent(mozIStorageConnection* aConnection);
+
+#if 0
+ nsresult
+ UpgradeStorageFrom1To2(mozIStorageConnection* aConnection);
+#endif
+
+ nsresult
+ InitializeRepository(PersistenceType aPersistenceType);
+
+ nsresult
+ InitializeOrigin(PersistenceType aPersistenceType,
+ const nsACString& aGroup,
+ const nsACString& aOrigin,
+ bool aIsApp,
+ int64_t aAccessTime,
+ nsIFile* aDirectory);
+
+ void
+ CheckTemporaryStorageLimits();
+
+ void
+ DeleteFilesForOrigin(PersistenceType aPersistenceType,
+ const nsACString& aOrigin);
+
+ void
+ FinalizeOriginEviction(nsTArray<RefPtr<DirectoryLockImpl>>& aLocks);
+
+ void
+ ReleaseIOThreadObjects()
+ {
+ AssertIsOnIOThread();
+
+ for (uint32_t index = 0; index < Client::TYPE_MAX; index++) {
+ mClients[index]->ReleaseIOThreadObjects();
+ }
+ }
+
+ DirectoryLockTable&
+ GetDirectoryLockTable(PersistenceType aPersistenceType);
+
+ static void
+ ShutdownTimerCallback(nsITimer* aTimer, void* aClosure);
+
+ mozilla::Mutex mQuotaMutex;
+
+ nsClassHashtable<nsCStringHashKey, GroupInfoPair> mGroupInfoPairs;
+
+ // Maintains a list of directory locks that are queued.
+ nsTArray<RefPtr<DirectoryLockImpl>> mPendingDirectoryLocks;
+
+ // Maintains a list of directory locks that are acquired or queued.
+ nsTArray<DirectoryLockImpl*> mDirectoryLocks;
+
+ // Directory lock tables that are used to update origin access time.
+ DirectoryLockTable mTemporaryDirectoryLockTable;
+ DirectoryLockTable mDefaultDirectoryLockTable;
+
+ // 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;
+
+ // This array is populated at initialization time and then never modified, so
+ // it can be iterated on any thread.
+ AutoTArray<RefPtr<Client>, Client::TYPE_MAX> mClients;
+
+ nsString mBasePath;
+ nsString mIndexedDBPath;
+ nsString mStoragePath;
+ nsString mPermanentStoragePath;
+ nsString mTemporaryStoragePath;
+ nsString mDefaultStoragePath;
+
+ uint64_t mTemporaryStorageLimit;
+ uint64_t mTemporaryStorageUsage;
+ bool mTemporaryStorageInitialized;
+
+ bool mStorageInitialized;
+};
+
+END_QUOTA_NAMESPACE
+
+#endif /* mozilla_dom_quota_quotamanager_h__ */
diff --git a/dom/quota/QuotaManagerService.cpp b/dom/quota/QuotaManagerService.cpp
new file mode 100644
index 000000000..fb5f0f3a1
--- /dev/null
+++ b/dom/quota/QuotaManagerService.cpp
@@ -0,0 +1,845 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "QuotaManagerService.h"
+
+#include "ActorsChild.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Hal.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Unused.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "nsIIdleService.h"
+#include "nsIIPCBackgroundChildCreateCallback.h"
+#include "nsIObserverService.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsXULAppAPI.h"
+#include "QuotaManager.h"
+#include "QuotaRequests.h"
+
+#define PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID "profile-before-change-qm"
+
+namespace mozilla {
+namespace dom {
+namespace quota {
+
+using namespace mozilla::ipc;
+
+namespace {
+
+// Preference that is used to enable testing features.
+const char kTestingPref[] = "dom.quotaManager.testing";
+
+const char kIdleServiceContractId[] = "@mozilla.org/widget/idleservice;1";
+
+// The number of seconds we will wait after receiving the idle-daily
+// notification before beginning maintenance.
+const uint32_t kIdleObserverTimeSec = 1;
+
+mozilla::StaticRefPtr<QuotaManagerService> gQuotaManagerService;
+
+mozilla::Atomic<bool> gInitialized(false);
+mozilla::Atomic<bool> gClosed(false);
+mozilla::Atomic<bool> gTestingMode(false);
+
+void
+TestingPrefChangedCallback(const char* aPrefName,
+ void* aClosure)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!strcmp(aPrefName, kTestingPref));
+ MOZ_ASSERT(!aClosure);
+
+ gTestingMode = Preferences::GetBool(aPrefName);
+}
+
+class AbortOperationsRunnable final
+ : public Runnable
+{
+ ContentParentId mContentParentId;
+
+public:
+ explicit AbortOperationsRunnable(ContentParentId aContentParentId)
+ : mContentParentId(aContentParentId)
+ { }
+
+private:
+ NS_DECL_NSIRUNNABLE
+};
+
+} // namespace
+
+class QuotaManagerService::BackgroundCreateCallback final
+ : public nsIIPCBackgroundChildCreateCallback
+{
+ RefPtr<QuotaManagerService> mService;
+
+public:
+ explicit
+ BackgroundCreateCallback(QuotaManagerService* aService)
+ : mService(aService)
+ {
+ MOZ_ASSERT(aService);
+ }
+
+ NS_DECL_ISUPPORTS
+
+private:
+ ~BackgroundCreateCallback()
+ { }
+
+ NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK
+};
+
+class QuotaManagerService::PendingRequestInfo
+{
+protected:
+ RefPtr<RequestBase> mRequest;
+
+public:
+ explicit PendingRequestInfo(RequestBase* aRequest)
+ : mRequest(aRequest)
+ { }
+
+ virtual ~PendingRequestInfo()
+ { }
+
+ RequestBase*
+ GetRequest() const
+ {
+ return mRequest;
+ }
+
+ virtual nsresult
+ InitiateRequest(QuotaChild* aActor) = 0;
+};
+
+class QuotaManagerService::UsageRequestInfo
+ : public PendingRequestInfo
+{
+ UsageRequestParams mParams;
+
+public:
+ UsageRequestInfo(UsageRequest* aRequest,
+ const UsageRequestParams& aParams)
+ : PendingRequestInfo(aRequest)
+ , mParams(aParams)
+ {
+ MOZ_ASSERT(aRequest);
+ MOZ_ASSERT(aParams.type() != UsageRequestParams::T__None);
+ }
+
+ virtual nsresult
+ InitiateRequest(QuotaChild* aActor) override;
+};
+
+class QuotaManagerService::RequestInfo
+ : public PendingRequestInfo
+{
+ RequestParams mParams;
+
+public:
+ RequestInfo(Request* aRequest,
+ const RequestParams& aParams)
+ : PendingRequestInfo(aRequest)
+ , mParams(aParams)
+ {
+ MOZ_ASSERT(aRequest);
+ MOZ_ASSERT(aParams.type() != RequestParams::T__None);
+ }
+
+ virtual nsresult
+ InitiateRequest(QuotaChild* aActor) override;
+};
+
+class QuotaManagerService::IdleMaintenanceInfo
+ : public PendingRequestInfo
+{
+ const bool mStart;
+
+public:
+ explicit IdleMaintenanceInfo(bool aStart)
+ : PendingRequestInfo(nullptr)
+ , mStart(aStart)
+ { }
+
+ virtual nsresult
+ InitiateRequest(QuotaChild* aActor) override;
+};
+
+QuotaManagerService::QuotaManagerService()
+ : mBackgroundActor(nullptr)
+ , mBackgroundActorFailed(false)
+ , mIdleObserverRegistered(false)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+QuotaManagerService::~QuotaManagerService()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mIdleObserverRegistered);
+}
+
+// static
+QuotaManagerService*
+QuotaManagerService::GetOrCreate()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (gClosed) {
+ MOZ_ASSERT(false, "Calling GetOrCreate() after shutdown!");
+ return nullptr;
+ }
+
+ if (!gQuotaManagerService) {
+ RefPtr<QuotaManagerService> instance(new QuotaManagerService());
+
+ nsresult rv = instance->Init();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ if (gInitialized.exchange(true)) {
+ MOZ_ASSERT(false, "Initialized more than once?!");
+ }
+
+ gQuotaManagerService = instance;
+
+ ClearOnShutdown(&gQuotaManagerService);
+ }
+
+ return gQuotaManagerService;
+}
+
+// static
+QuotaManagerService*
+QuotaManagerService::Get()
+{
+ // Does not return an owning reference.
+ return gQuotaManagerService;
+}
+
+// static
+QuotaManagerService*
+QuotaManagerService::FactoryCreate()
+{
+ // Returns a raw pointer that carries an owning reference! Lame, but the
+ // singleton factory macros force this.
+ QuotaManagerService* quotaManagerService = GetOrCreate();
+ NS_IF_ADDREF(quotaManagerService);
+ return quotaManagerService;
+}
+
+void
+QuotaManagerService::ClearBackgroundActor()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mBackgroundActor = nullptr;
+}
+
+void
+QuotaManagerService::NoteLiveManager(QuotaManager* aManager)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aManager);
+
+ mBackgroundThread = aManager->OwningThread();
+}
+
+void
+QuotaManagerService::NoteShuttingDownManager()
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mBackgroundThread = nullptr;
+}
+
+void
+QuotaManagerService::AbortOperationsForProcess(ContentParentId aContentParentId)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mBackgroundThread) {
+ return;
+ }
+
+ RefPtr<AbortOperationsRunnable> runnable =
+ new AbortOperationsRunnable(aContentParentId);
+
+ MOZ_ALWAYS_SUCCEEDS(
+ mBackgroundThread->Dispatch(runnable, NS_DISPATCH_NORMAL));
+}
+
+nsresult
+QuotaManagerService::Init()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (NS_WARN_IF(!observerService)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv =
+ observerService->AddObserver(this,
+ PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID,
+ false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ Preferences::RegisterCallbackAndCall(TestingPrefChangedCallback,
+ kTestingPref);
+
+ return NS_OK;
+}
+
+void
+QuotaManagerService::Destroy()
+{
+ // Setting the closed flag prevents the service from being recreated.
+ // Don't set it though if there's no real instance created.
+ if (gInitialized && gClosed.exchange(true)) {
+ MOZ_ASSERT(false, "Shutdown more than once?!");
+ }
+
+ Preferences::UnregisterCallback(TestingPrefChangedCallback, kTestingPref);
+
+ delete this;
+}
+
+nsresult
+QuotaManagerService::InitiateRequest(nsAutoPtr<PendingRequestInfo>& aInfo)
+{
+ // Nothing can be done here if we have previously failed to create a
+ // background actor.
+ if (mBackgroundActorFailed) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mBackgroundActor && mPendingRequests.IsEmpty()) {
+ if (PBackgroundChild* actor = BackgroundChild::GetForCurrentThread()) {
+ BackgroundActorCreated(actor);
+ } else {
+ // We need to start the sequence to create a background actor for this
+ // thread.
+ RefPtr<BackgroundCreateCallback> cb = new BackgroundCreateCallback(this);
+ if (NS_WARN_IF(!BackgroundChild::GetOrCreateForCurrentThread(cb))) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+
+ // If we already have a background actor then we can start this request now.
+ if (mBackgroundActor) {
+ nsresult rv = aInfo->InitiateRequest(mBackgroundActor);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ mPendingRequests.AppendElement(aInfo.forget());
+ }
+
+ return NS_OK;
+}
+
+nsresult
+QuotaManagerService::BackgroundActorCreated(PBackgroundChild* aBackgroundActor)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aBackgroundActor);
+ MOZ_ASSERT(!mBackgroundActor);
+ MOZ_ASSERT(!mBackgroundActorFailed);
+
+ {
+ QuotaChild* actor = new QuotaChild(this);
+
+ mBackgroundActor =
+ static_cast<QuotaChild*>(aBackgroundActor->SendPQuotaConstructor(actor));
+ }
+
+ if (NS_WARN_IF(!mBackgroundActor)) {
+ BackgroundActorFailed();
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = NS_OK;
+
+ for (uint32_t index = 0, count = mPendingRequests.Length();
+ index < count;
+ index++) {
+ nsAutoPtr<PendingRequestInfo> info(mPendingRequests[index].forget());
+
+ nsresult rv2 = info->InitiateRequest(mBackgroundActor);
+
+ // Warn for every failure, but just return the first failure if there are
+ // multiple failures.
+ if (NS_WARN_IF(NS_FAILED(rv2)) && NS_SUCCEEDED(rv)) {
+ rv = rv2;
+ }
+ }
+
+ mPendingRequests.Clear();
+
+ return rv;
+}
+
+void
+QuotaManagerService::BackgroundActorFailed()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mPendingRequests.IsEmpty());
+ MOZ_ASSERT(!mBackgroundActor);
+ MOZ_ASSERT(!mBackgroundActorFailed);
+
+ mBackgroundActorFailed = true;
+
+ for (uint32_t index = 0, count = mPendingRequests.Length();
+ index < count;
+ index++) {
+ nsAutoPtr<PendingRequestInfo> info(mPendingRequests[index].forget());
+
+ RequestBase* request = info->GetRequest();
+ if (request) {
+ request->SetError(NS_ERROR_FAILURE);
+ }
+ }
+
+ mPendingRequests.Clear();
+}
+
+void
+QuotaManagerService::PerformIdleMaintenance()
+{
+ using namespace mozilla::hal;
+
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // If we're running on battery power then skip all idle maintenance since we
+ // would otherwise be doing lots of disk I/O.
+ BatteryInformation batteryInfo;
+
+#ifdef MOZ_WIDGET_ANDROID
+ // Android XPCShell doesn't load the AndroidBridge that is needed to make
+ // GetCurrentBatteryInformation work...
+ if (!QuotaManager::IsRunningXPCShellTests())
+#endif
+ {
+ GetCurrentBatteryInformation(&batteryInfo);
+ }
+
+ // If we're running XPCShell because we always want to be able to test this
+ // code so pretend that we're always charging.
+ if (QuotaManager::IsRunningXPCShellTests()) {
+ batteryInfo.level() = 100;
+ batteryInfo.charging() = true;
+ }
+
+ if (NS_WARN_IF(!batteryInfo.charging())) {
+ return;
+ }
+
+ if (QuotaManager::IsRunningXPCShellTests()) {
+ // We don't want user activity to impact this code if we're running tests.
+ Unused << Observe(nullptr, OBSERVER_TOPIC_IDLE, nullptr);
+ } else if (!mIdleObserverRegistered) {
+ nsCOMPtr<nsIIdleService> idleService =
+ do_GetService(kIdleServiceContractId);
+ MOZ_ASSERT(idleService);
+
+ MOZ_ALWAYS_SUCCEEDS(
+ idleService->AddIdleObserver(this, kIdleObserverTimeSec));
+
+ mIdleObserverRegistered = true;
+ }
+}
+
+void
+QuotaManagerService::RemoveIdleObserver()
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mIdleObserverRegistered) {
+ nsCOMPtr<nsIIdleService> idleService =
+ do_GetService(kIdleServiceContractId);
+ MOZ_ASSERT(idleService);
+
+ MOZ_ALWAYS_SUCCEEDS(
+ idleService->RemoveIdleObserver(this, kIdleObserverTimeSec));
+
+ mIdleObserverRegistered = false;
+ }
+}
+
+NS_IMPL_ADDREF(QuotaManagerService)
+NS_IMPL_RELEASE_WITH_DESTROY(QuotaManagerService, Destroy())
+NS_IMPL_QUERY_INTERFACE(QuotaManagerService,
+ nsIQuotaManagerService,
+ nsIObserver)
+
+NS_IMETHODIMP
+QuotaManagerService::GetUsage(nsIQuotaUsageCallback* aCallback,
+ bool aGetAll,
+ nsIQuotaUsageRequest** _retval)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aCallback);
+
+ RefPtr<UsageRequest> request = new UsageRequest(aCallback);
+
+ AllUsageParams params;
+
+ params.getAll() = aGetAll;
+
+ nsAutoPtr<PendingRequestInfo> info(new UsageRequestInfo(request, params));
+
+ nsresult rv = InitiateRequest(info);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ request.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+QuotaManagerService::GetUsageForPrincipal(nsIPrincipal* aPrincipal,
+ nsIQuotaUsageCallback* aCallback,
+ bool aGetGroupUsage,
+ nsIQuotaUsageRequest** _retval)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(aCallback);
+
+ RefPtr<UsageRequest> request = new UsageRequest(aPrincipal, aCallback);
+
+ OriginUsageParams params;
+
+ PrincipalInfo& principalInfo = params.principalInfo();
+ nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (principalInfo.type() != PrincipalInfo::TContentPrincipalInfo &&
+ principalInfo.type() != PrincipalInfo::TSystemPrincipalInfo) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ params.getGroupUsage() = aGetGroupUsage;
+
+ nsAutoPtr<PendingRequestInfo> info(new UsageRequestInfo(request, params));
+
+ rv = InitiateRequest(info);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ request.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+QuotaManagerService::Clear(nsIQuotaRequest** _retval)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(nsContentUtils::IsCallerChrome());
+
+ if (NS_WARN_IF(!gTestingMode)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<Request> request = new Request();
+
+ ClearAllParams params;
+
+ nsAutoPtr<PendingRequestInfo> info(new RequestInfo(request, params));
+
+ nsresult rv = InitiateRequest(info);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ request.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+QuotaManagerService::ClearStoragesForPrincipal(nsIPrincipal* aPrincipal,
+ const nsACString& aPersistenceType,
+ bool aClearAll,
+ nsIQuotaRequest** _retval)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(nsContentUtils::IsCallerChrome());
+
+ nsCString suffix;
+ BasePrincipal::Cast(aPrincipal)->OriginAttributesRef().CreateSuffix(suffix);
+
+ if (NS_WARN_IF(aClearAll && !suffix.IsEmpty())) {
+ // The originAttributes should be default originAttributes when the
+ // aClearAll flag is set.
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ RefPtr<Request> request = new Request(aPrincipal);
+
+ ClearOriginParams params;
+
+ PrincipalInfo& principalInfo = params.principalInfo();
+
+ nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (principalInfo.type() != PrincipalInfo::TContentPrincipalInfo &&
+ principalInfo.type() != PrincipalInfo::TSystemPrincipalInfo) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ Nullable<PersistenceType> persistenceType;
+ rv = NullablePersistenceTypeFromText(aPersistenceType, &persistenceType);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (persistenceType.IsNull()) {
+ params.persistenceTypeIsExplicit() = false;
+ } else {
+ params.persistenceType() = persistenceType.Value();
+ params.persistenceTypeIsExplicit() = true;
+ }
+
+ params.clearAll() = aClearAll;
+
+ nsAutoPtr<PendingRequestInfo> info(new RequestInfo(request, params));
+
+ rv = InitiateRequest(info);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ request.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+QuotaManagerService::Reset(nsIQuotaRequest** _retval)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(nsContentUtils::IsCallerChrome());
+
+ if (NS_WARN_IF(!gTestingMode)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<Request> request = new Request();
+
+ ResetAllParams params;
+
+ nsAutoPtr<PendingRequestInfo> info(new RequestInfo(request, params));
+
+ nsresult rv = InitiateRequest(info);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ request.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+QuotaManagerService::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!strcmp(aTopic, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID)) {
+ RemoveIdleObserver();
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "clear-origin-attributes-data")) {
+ RefPtr<Request> request = new Request();
+
+ ClearOriginsParams requestParams;
+ requestParams.pattern() = nsDependentString(aData);
+
+ nsAutoPtr<PendingRequestInfo> info(new RequestInfo(request, requestParams));
+
+ nsresult rv = InitiateRequest(info);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, OBSERVER_TOPIC_IDLE_DAILY)) {
+ PerformIdleMaintenance();
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, OBSERVER_TOPIC_IDLE)) {
+ nsAutoPtr<PendingRequestInfo> info(
+ new IdleMaintenanceInfo(/* aStart */ true));
+
+ nsresult rv = InitiateRequest(info);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, OBSERVER_TOPIC_ACTIVE)) {
+ RemoveIdleObserver();
+
+ nsAutoPtr<PendingRequestInfo> info(
+ new IdleMaintenanceInfo(/* aStart */ false));
+
+ nsresult rv = InitiateRequest(info);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Should never get here!");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AbortOperationsRunnable::Run()
+{
+ AssertIsOnBackgroundThread();
+
+ if (QuotaManager::IsShuttingDown()) {
+ return NS_OK;
+ }
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ if (!quotaManager) {
+ return NS_OK;
+ }
+
+ quotaManager->AbortOperationsForProcess(mContentParentId);
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(QuotaManagerService::BackgroundCreateCallback,
+ nsIIPCBackgroundChildCreateCallback)
+
+void
+QuotaManagerService::
+BackgroundCreateCallback::ActorCreated(PBackgroundChild* aActor)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(mService);
+
+ RefPtr<QuotaManagerService> service;
+ mService.swap(service);
+
+ service->BackgroundActorCreated(aActor);
+}
+
+void
+QuotaManagerService::
+BackgroundCreateCallback::ActorFailed()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mService);
+
+ RefPtr<QuotaManagerService> service;
+ mService.swap(service);
+
+ service->BackgroundActorFailed();
+}
+
+nsresult
+QuotaManagerService::
+UsageRequestInfo::InitiateRequest(QuotaChild* aActor)
+{
+ MOZ_ASSERT(aActor);
+
+ auto request = static_cast<UsageRequest*>(mRequest.get());
+
+ auto actor = new QuotaUsageRequestChild(request);
+
+ if (!aActor->SendPQuotaUsageRequestConstructor(actor, mParams)) {
+ request->SetError(NS_ERROR_FAILURE);
+ return NS_ERROR_FAILURE;
+ }
+
+ request->SetBackgroundActor(actor);
+
+ return NS_OK;
+}
+
+nsresult
+QuotaManagerService::
+RequestInfo::InitiateRequest(QuotaChild* aActor)
+{
+ MOZ_ASSERT(aActor);
+
+ auto request = static_cast<Request*>(mRequest.get());
+
+ auto actor = new QuotaRequestChild(request);
+
+ if (!aActor->SendPQuotaRequestConstructor(actor, mParams)) {
+ request->SetError(NS_ERROR_FAILURE);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+QuotaManagerService::
+IdleMaintenanceInfo::InitiateRequest(QuotaChild* aActor)
+{
+ MOZ_ASSERT(aActor);
+
+ bool result;
+
+ if (mStart) {
+ result = aActor->SendStartIdleMaintenance();
+ } else {
+ result = aActor->SendStopIdleMaintenance();
+ }
+
+ if (!result) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+} // namespace quota
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/quota/QuotaManagerService.h b/dom/quota/QuotaManagerService.h
new file mode 100644
index 000000000..bbf249e69
--- /dev/null
+++ b/dom/quota/QuotaManagerService.h
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_quota_QuotaManagerService_h
+#define mozilla_dom_quota_QuotaManagerService_h
+
+#include "mozilla/dom/ipc/IdType.h"
+#include "nsAutoPtr.h"
+#include "nsIObserver.h"
+#include "nsIQuotaManagerService.h"
+
+#define QUOTAMANAGER_SERVICE_CONTRACTID \
+ "@mozilla.org/dom/quota-manager-service;1"
+
+namespace mozilla {
+namespace ipc {
+
+class PBackgroundChild;
+
+} // namespace ipc
+
+namespace dom {
+namespace quota {
+
+class QuotaChild;
+class QuotaManager;
+
+class QuotaManagerService final
+ : public nsIQuotaManagerService
+ , public nsIObserver
+{
+ typedef mozilla::ipc::PBackgroundChild PBackgroundChild;
+
+ class BackgroundCreateCallback;
+ class PendingRequestInfo;
+ class UsageRequestInfo;
+ class RequestInfo;
+ class IdleMaintenanceInfo;
+
+ nsCOMPtr<nsIEventTarget> mBackgroundThread;
+
+ nsTArray<nsAutoPtr<PendingRequestInfo>> mPendingRequests;
+
+ QuotaChild* mBackgroundActor;
+
+ bool mBackgroundActorFailed;
+ bool mIdleObserverRegistered;
+
+public:
+ // Returns a non-owning reference.
+ static QuotaManagerService*
+ GetOrCreate();
+
+ // Returns a non-owning reference.
+ static QuotaManagerService*
+ Get();
+
+ // Returns an owning reference! No one should call this but the factory.
+ static QuotaManagerService*
+ FactoryCreate();
+
+ void
+ ClearBackgroundActor();
+
+ void
+ NoteLiveManager(QuotaManager* aManager);
+
+ void
+ NoteShuttingDownManager();
+
+ // Called when a process is being shot down. Aborts any running operations
+ // for the given process.
+ void
+ AbortOperationsForProcess(ContentParentId aContentParentId);
+
+private:
+ QuotaManagerService();
+ ~QuotaManagerService();
+
+ nsresult
+ Init();
+
+ void
+ Destroy();
+
+ nsresult
+ InitiateRequest(nsAutoPtr<PendingRequestInfo>& aInfo);
+
+ nsresult
+ BackgroundActorCreated(PBackgroundChild* aBackgroundActor);
+
+ void
+ BackgroundActorFailed();
+
+ void
+ PerformIdleMaintenance();
+
+ void
+ RemoveIdleObserver();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIQUOTAMANAGERSERVICE
+ NS_DECL_NSIOBSERVER
+};
+
+} // namespace quota
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_quota_QuotaManagerService_h */
diff --git a/dom/quota/QuotaObject.h b/dom/quota/QuotaObject.h
new file mode 100644
index 000000000..370d04da1
--- /dev/null
+++ b/dom/quota/QuotaObject.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_quota_quotaobject_h__
+#define mozilla_dom_quota_quotaobject_h__
+
+#include "mozilla/dom/quota/QuotaCommon.h"
+
+#include "nsDataHashtable.h"
+
+#include "PersistenceType.h"
+
+BEGIN_QUOTA_NAMESPACE
+
+class OriginInfo;
+class QuotaManager;
+
+class QuotaObject
+{
+ friend class OriginInfo;
+ friend class QuotaManager;
+
+public:
+ void
+ AddRef();
+
+ void
+ Release();
+
+ const nsAString&
+ Path() const
+ {
+ return mPath;
+ }
+
+ bool
+ MaybeUpdateSize(int64_t aSize, bool aTruncate);
+
+ void
+ DisableQuotaCheck();
+
+ void
+ EnableQuotaCheck();
+
+private:
+ QuotaObject(OriginInfo* aOriginInfo, const nsAString& aPath, int64_t aSize)
+ : mOriginInfo(aOriginInfo)
+ , mPath(aPath)
+ , mSize(aSize)
+ , mQuotaCheckDisabled(false)
+ {
+ MOZ_COUNT_CTOR(QuotaObject);
+ }
+
+ ~QuotaObject()
+ {
+ MOZ_COUNT_DTOR(QuotaObject);
+ }
+
+ already_AddRefed<QuotaObject>
+ LockedAddRef()
+ {
+ AssertCurrentThreadOwnsQuotaMutex();
+
+ ++mRefCnt;
+
+ RefPtr<QuotaObject> result = dont_AddRef(this);
+ return result.forget();
+ }
+
+ mozilla::ThreadSafeAutoRefCnt mRefCnt;
+
+ OriginInfo* mOriginInfo;
+ nsString mPath;
+ int64_t mSize;
+
+ bool mQuotaCheckDisabled;
+};
+
+END_QUOTA_NAMESPACE
+
+#endif // mozilla_dom_quota_quotaobject_h__
diff --git a/dom/quota/QuotaRequests.cpp b/dom/quota/QuotaRequests.cpp
new file mode 100644
index 000000000..369bc790c
--- /dev/null
+++ b/dom/quota/QuotaRequests.cpp
@@ -0,0 +1,291 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "QuotaRequests.h"
+
+#include "ActorsChild.h"
+#include "nsIQuotaCallbacks.h"
+
+namespace mozilla {
+namespace dom {
+namespace quota {
+
+RequestBase::RequestBase()
+ : mResultCode(NS_OK)
+ , mHaveResultOrErrorCode(false)
+{
+#ifdef DEBUG
+ mOwningThread = PR_GetCurrentThread();
+#endif
+ AssertIsOnOwningThread();
+}
+
+RequestBase::RequestBase(nsIPrincipal* aPrincipal)
+ : mPrincipal(aPrincipal)
+ , mResultCode(NS_OK)
+ , mHaveResultOrErrorCode(false)
+{
+#ifdef DEBUG
+ mOwningThread = PR_GetCurrentThread();
+#endif
+ AssertIsOnOwningThread();
+}
+
+#ifdef DEBUG
+
+void
+RequestBase::AssertIsOnOwningThread() const
+{
+ MOZ_ASSERT(mOwningThread);
+ MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
+}
+
+#endif // DEBUG
+
+void
+RequestBase::SetError(nsresult aRv)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mResultCode == NS_OK);
+ MOZ_ASSERT(!mHaveResultOrErrorCode);
+
+ mResultCode = aRv;
+ mHaveResultOrErrorCode = true;
+
+ FireCallback();
+}
+
+NS_IMPL_CYCLE_COLLECTION_0(RequestBase)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RequestBase)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(RequestBase)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(RequestBase)
+
+NS_IMETHODIMP
+RequestBase::GetPrincipal(nsIPrincipal** aPrincipal)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aPrincipal);
+
+ NS_IF_ADDREF(*aPrincipal = mPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestBase::GetResultCode(nsresult* aResultCode)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aResultCode);
+
+ if (!mHaveResultOrErrorCode) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aResultCode = mResultCode;
+ return NS_OK;
+}
+
+UsageRequest::UsageRequest(nsIQuotaUsageCallback* aCallback)
+ : mCallback(aCallback)
+ , mBackgroundActor(nullptr)
+ , mCanceled(false)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aCallback);
+}
+
+UsageRequest::UsageRequest(nsIPrincipal* aPrincipal,
+ nsIQuotaUsageCallback* aCallback)
+ : RequestBase(aPrincipal)
+ , mCallback(aCallback)
+ , mBackgroundActor(nullptr)
+ , mCanceled(false)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(aCallback);
+}
+
+UsageRequest::~UsageRequest()
+{
+ AssertIsOnOwningThread();
+}
+
+void
+UsageRequest::SetBackgroundActor(QuotaUsageRequestChild* aBackgroundActor)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aBackgroundActor);
+ MOZ_ASSERT(!mBackgroundActor);
+
+ mBackgroundActor = aBackgroundActor;
+
+ if (mCanceled) {
+ mBackgroundActor->SendCancel();
+ }
+}
+
+void
+UsageRequest::SetResult(nsIVariant* aResult)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aResult);
+ MOZ_ASSERT(!mHaveResultOrErrorCode);
+
+ mResult = aResult;
+
+ mHaveResultOrErrorCode = true;
+
+ FireCallback();
+}
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(UsageRequest, RequestBase, mCallback)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(UsageRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIQuotaUsageRequest)
+NS_INTERFACE_MAP_END_INHERITING(RequestBase)
+
+NS_IMPL_ADDREF_INHERITED(UsageRequest, RequestBase)
+NS_IMPL_RELEASE_INHERITED(UsageRequest, RequestBase)
+
+NS_IMETHODIMP
+UsageRequest::GetResult(nsIVariant** aResult)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aResult);
+
+ if (!mHaveResultOrErrorCode) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_IF_ADDREF(*aResult = mResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UsageRequest::GetCallback(nsIQuotaUsageCallback** aCallback)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aCallback);
+
+ NS_IF_ADDREF(*aCallback = mCallback);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UsageRequest::SetCallback(nsIQuotaUsageCallback* aCallback)
+{
+ AssertIsOnOwningThread();
+
+ mCallback = aCallback;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UsageRequest::Cancel()
+{
+ AssertIsOnOwningThread();
+
+ if (mCanceled) {
+ NS_WARNING("Canceled more than once?!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mBackgroundActor) {
+ mBackgroundActor->SendCancel();
+ }
+
+ mCanceled = true;
+
+ return NS_OK;
+}
+
+void
+UsageRequest::FireCallback()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mCallback);
+
+ mCallback->OnUsageResult(this);
+
+ // Clean up.
+ mCallback = nullptr;
+}
+
+Request::Request()
+{
+ AssertIsOnOwningThread();
+}
+
+Request::Request(nsIPrincipal* aPrincipal)
+ : RequestBase(aPrincipal)
+{
+ AssertIsOnOwningThread();
+}
+
+Request::~Request()
+{
+ AssertIsOnOwningThread();
+}
+
+void
+Request::SetResult()
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(!mHaveResultOrErrorCode);
+
+ mHaveResultOrErrorCode = true;
+
+ FireCallback();
+}
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(Request, RequestBase, mCallback)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Request)
+ NS_INTERFACE_MAP_ENTRY(nsIQuotaRequest)
+NS_INTERFACE_MAP_END_INHERITING(RequestBase)
+
+NS_IMPL_ADDREF_INHERITED(mozilla::dom::quota::Request, RequestBase)
+NS_IMPL_RELEASE_INHERITED(mozilla::dom::quota::Request, RequestBase)
+
+NS_IMETHODIMP
+Request::GetCallback(nsIQuotaCallback** aCallback)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aCallback);
+
+ NS_IF_ADDREF(*aCallback = mCallback);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Request::SetCallback(nsIQuotaCallback* aCallback)
+{
+ AssertIsOnOwningThread();
+
+ mCallback = aCallback;
+ return NS_OK;
+}
+
+void
+Request::FireCallback()
+{
+ AssertIsOnOwningThread();
+
+ if (mCallback) {
+ mCallback->OnComplete(this);
+
+ // Clean up.
+ mCallback = nullptr;
+ }
+}
+
+} // namespace quota
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/quota/QuotaRequests.h b/dom/quota/QuotaRequests.h
new file mode 100644
index 000000000..cb483753b
--- /dev/null
+++ b/dom/quota/QuotaRequests.h
@@ -0,0 +1,142 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_quota_UsageRequest_h
+#define mozilla_dom_quota_UsageRequest_h
+
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIQuotaRequests.h"
+
+class nsIPrincipal;
+class nsIQuotaCallback;
+class nsIQuotaUsageCallback;
+struct PRThread;
+
+namespace mozilla {
+namespace dom {
+namespace quota {
+
+class QuotaUsageRequestChild;
+
+class RequestBase
+ : public nsIQuotaRequestBase
+{
+protected:
+#ifdef DEBUG
+ PRThread* mOwningThread;
+#endif
+
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+
+ nsresult mResultCode;
+ bool mHaveResultOrErrorCode;
+
+public:
+ void
+ AssertIsOnOwningThread() const
+#ifdef DEBUG
+ ;
+#else
+ { }
+#endif
+
+ void
+ SetError(nsresult aRv);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_NSIQUOTAREQUESTBASE
+ NS_DECL_CYCLE_COLLECTION_CLASS(RequestBase)
+
+protected:
+ RequestBase();
+
+ RequestBase(nsIPrincipal* aPrincipal);
+
+ virtual ~RequestBase()
+ {
+ AssertIsOnOwningThread();
+ }
+
+ virtual void
+ FireCallback() = 0;
+};
+
+class UsageRequest final
+ : public RequestBase
+ , public nsIQuotaUsageRequest
+{
+ nsCOMPtr<nsIQuotaUsageCallback> mCallback;
+
+ nsCOMPtr<nsIVariant> mResult;
+
+ QuotaUsageRequestChild* mBackgroundActor;
+
+ bool mCanceled;
+
+public:
+ explicit UsageRequest(nsIQuotaUsageCallback* aCallback);
+
+ UsageRequest(nsIPrincipal* aPrincipal,
+ nsIQuotaUsageCallback* aCallback);
+
+ void
+ SetBackgroundActor(QuotaUsageRequestChild* aBackgroundActor);
+
+ void
+ ClearBackgroundActor()
+ {
+ AssertIsOnOwningThread();
+
+ mBackgroundActor = nullptr;
+ }
+
+ void
+ SetResult(nsIVariant* aResult);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_FORWARD_NSIQUOTAREQUESTBASE(RequestBase::)
+ NS_DECL_NSIQUOTAUSAGEREQUEST
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(UsageRequest, RequestBase)
+
+private:
+ ~UsageRequest();
+
+ virtual void
+ FireCallback() override;
+};
+
+class Request final
+ : public RequestBase
+ , public nsIQuotaRequest
+{
+ nsCOMPtr<nsIQuotaCallback> mCallback;
+
+public:
+ Request();
+
+ explicit Request(nsIPrincipal* aPrincipal);
+
+ void
+ SetResult();
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_FORWARD_NSIQUOTAREQUESTBASE(RequestBase::)
+ NS_DECL_NSIQUOTAREQUEST
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Request, RequestBase)
+
+private:
+ ~Request();
+
+ virtual void
+ FireCallback() override;
+};
+
+} // namespace quota
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_quota_UsageRequest_h
diff --git a/dom/quota/QuotaResults.cpp b/dom/quota/QuotaResults.cpp
new file mode 100644
index 000000000..f5dbbd657
--- /dev/null
+++ b/dom/quota/QuotaResults.cpp
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "QuotaResults.h"
+
+namespace mozilla {
+namespace dom {
+namespace quota {
+
+UsageResult::UsageResult(const nsACString& aOrigin,
+ bool aPersisted,
+ uint64_t aUsage)
+ : mOrigin(aOrigin)
+ , mUsage(aUsage)
+ , mPersisted(aPersisted)
+{
+}
+
+NS_IMPL_ISUPPORTS(UsageResult,
+ nsIQuotaUsageResult)
+
+NS_IMETHODIMP
+UsageResult::GetOrigin(nsACString& aOrigin)
+{
+ aOrigin = mOrigin;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UsageResult::GetPersisted(bool* aPersisted)
+{
+ MOZ_ASSERT(aPersisted);
+
+ *aPersisted = mPersisted;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UsageResult::GetUsage(uint64_t* aUsage)
+{
+ MOZ_ASSERT(aUsage);
+
+ *aUsage = mUsage;
+ return NS_OK;
+}
+
+OriginUsageResult::OriginUsageResult(uint64_t aUsage,
+ uint64_t aFileUsage,
+ uint64_t aLimit)
+ : mUsage(aUsage)
+ , mFileUsage(aFileUsage)
+ , mLimit(aLimit)
+{
+}
+
+NS_IMPL_ISUPPORTS(OriginUsageResult,
+ nsIQuotaOriginUsageResult)
+
+NS_IMETHODIMP
+OriginUsageResult::GetUsage(uint64_t* aUsage)
+{
+ MOZ_ASSERT(aUsage);
+
+ *aUsage = mUsage;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OriginUsageResult::GetFileUsage(uint64_t* aFileUsage)
+{
+ MOZ_ASSERT(aFileUsage);
+
+ *aFileUsage = mFileUsage;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OriginUsageResult::GetLimit(uint64_t* aLimit)
+{
+ MOZ_ASSERT(aLimit);
+
+ *aLimit = mLimit;
+ return NS_OK;
+}
+
+} // namespace quota
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/quota/QuotaResults.h b/dom/quota/QuotaResults.h
new file mode 100644
index 000000000..73fe6b790
--- /dev/null
+++ b/dom/quota/QuotaResults.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_quota_QuotaResults_h
+#define mozilla_dom_quota_QuotaResults_h
+
+#include "nsIQuotaResults.h"
+
+namespace mozilla {
+namespace dom {
+namespace quota {
+
+class UsageResult
+ : public nsIQuotaUsageResult
+{
+ nsCString mOrigin;
+ uint64_t mUsage;
+ bool mPersisted;
+
+public:
+ UsageResult(const nsACString& aOrigin,
+ bool aPersisted,
+ uint64_t aUsage);
+
+private:
+ virtual ~UsageResult()
+ { }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIQUOTAUSAGERESULT
+};
+
+class OriginUsageResult
+ : public nsIQuotaOriginUsageResult
+{
+ uint64_t mUsage;
+ uint64_t mFileUsage;
+ uint64_t mLimit;
+
+public:
+ OriginUsageResult(uint64_t aUsage,
+ uint64_t aFileUsage,
+ uint64_t aLimit);
+
+private:
+ virtual ~OriginUsageResult()
+ { }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIQUOTAORIGINUSAGERESULT
+};
+
+} // namespace quota
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_quota_QuotaResults_h
diff --git a/dom/quota/SerializationHelpers.h b/dom/quota/SerializationHelpers.h
new file mode 100644
index 000000000..e3a79e4e9
--- /dev/null
+++ b/dom/quota/SerializationHelpers.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_quota_SerializationHelpers_h
+#define mozilla_dom_quota_SerializationHelpers_h
+
+#include "ipc/IPCMessageUtils.h"
+
+#include "mozilla/dom/quota/PersistenceType.h"
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::dom::quota::PersistenceType> :
+ public ContiguousEnumSerializer<
+ mozilla::dom::quota::PersistenceType,
+ mozilla::dom::quota::PERSISTENCE_TYPE_PERSISTENT,
+ mozilla::dom::quota::PERSISTENCE_TYPE_INVALID>
+{ };
+
+} // namespace IPC
+
+#endif // mozilla_dom_quota_SerializationHelpers_h
diff --git a/dom/quota/StorageManager.cpp b/dom/quota/StorageManager.cpp
new file mode 100644
index 000000000..c8455f0fe
--- /dev/null
+++ b/dom/quota/StorageManager.cpp
@@ -0,0 +1,378 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "StorageManager.h"
+
+#include "mozilla/dom/PromiseWorkerProxy.h"
+#include "mozilla/dom/quota/QuotaManagerService.h"
+#include "mozilla/dom/StorageManagerBinding.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/ErrorResult.h"
+#include "nsIQuotaCallbacks.h"
+#include "nsIQuotaRequests.h"
+#include "nsPIDOMWindow.h"
+
+using namespace mozilla::dom::workers;
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+// This class is used to get quota usage callback.
+class EstimateResolver final
+ : public nsIQuotaUsageCallback
+{
+ class FinishWorkerRunnable;
+
+ // If this resolver was created for a window then mPromise must be non-null.
+ // Otherwise mProxy must be non-null.
+ RefPtr<Promise> mPromise;
+ RefPtr<PromiseWorkerProxy> mProxy;
+
+ nsresult mResultCode;
+ StorageEstimate mStorageEstimate;
+
+public:
+ explicit EstimateResolver(Promise* aPromise)
+ : mPromise(aPromise)
+ , mResultCode(NS_OK)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPromise);
+ }
+
+ explicit EstimateResolver(PromiseWorkerProxy* aProxy)
+ : mProxy(aProxy)
+ , mResultCode(NS_OK)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aProxy);
+ }
+
+ void
+ ResolveOrReject(Promise* aPromise);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_DECL_NSIQUOTAUSAGECALLBACK
+
+private:
+ ~EstimateResolver()
+ { }
+};
+
+// This class is used to return promise on worker thread.
+class EstimateResolver::FinishWorkerRunnable final
+ : public WorkerRunnable
+{
+ RefPtr<EstimateResolver> mResolver;
+
+public:
+ explicit FinishWorkerRunnable(EstimateResolver* aResolver)
+ : WorkerRunnable(aResolver->mProxy->GetWorkerPrivate())
+ , mResolver(aResolver)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aResolver);
+ }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
+};
+
+class EstimateWorkerMainThreadRunnable
+ : public WorkerMainThreadRunnable
+{
+ RefPtr<PromiseWorkerProxy> mProxy;
+
+public:
+ EstimateWorkerMainThreadRunnable(WorkerPrivate* aWorkerPrivate,
+ PromiseWorkerProxy* aProxy)
+ : WorkerMainThreadRunnable(aWorkerPrivate,
+ NS_LITERAL_CSTRING("StorageManager :: Estimate"))
+ , mProxy(aProxy)
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(aProxy);
+ }
+
+ virtual bool
+ MainThreadRun() override;
+};
+
+nsresult
+GetUsageForPrincipal(nsIPrincipal* aPrincipal,
+ nsIQuotaUsageCallback* aCallback,
+ nsIQuotaUsageRequest** aRequest)
+{
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(aCallback);
+ MOZ_ASSERT(aRequest);
+
+ nsCOMPtr<nsIQuotaManagerService> qms = QuotaManagerService::GetOrCreate();
+ if (NS_WARN_IF(!qms)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = qms->GetUsageForPrincipal(aPrincipal, aCallback, true, aRequest);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+};
+
+nsresult
+GetStorageEstimate(nsIQuotaUsageRequest* aRequest,
+ StorageEstimate& aStorageEstimate)
+{
+ MOZ_ASSERT(aRequest);
+
+ nsCOMPtr<nsIVariant> result;
+ nsresult rv = aRequest->GetResult(getter_AddRefs(result));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsID* iid;
+ nsCOMPtr<nsISupports> supports;
+ rv = result->GetAsInterface(&iid, getter_AddRefs(supports));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ free(iid);
+
+ nsCOMPtr<nsIQuotaOriginUsageResult> originUsageResult =
+ do_QueryInterface(supports);
+ MOZ_ASSERT(originUsageResult);
+
+ MOZ_ALWAYS_SUCCEEDS(
+ originUsageResult->GetUsage(&aStorageEstimate.mUsage.Construct()));
+
+ MOZ_ALWAYS_SUCCEEDS(
+ originUsageResult->GetLimit(&aStorageEstimate.mQuota.Construct()));
+
+ return NS_OK;
+}
+
+} // namespace
+
+/*******************************************************************************
+ * Local class implementations
+ ******************************************************************************/
+
+void
+EstimateResolver::ResolveOrReject(Promise* aPromise)
+{
+ MOZ_ASSERT(aPromise);
+
+ if (NS_SUCCEEDED(mResultCode)) {
+ aPromise->MaybeResolve(mStorageEstimate);
+ } else {
+ aPromise->MaybeReject(mResultCode);
+ }
+}
+
+NS_IMPL_ISUPPORTS(EstimateResolver, nsIQuotaUsageCallback)
+
+NS_IMETHODIMP
+EstimateResolver::OnUsageResult(nsIQuotaUsageRequest *aRequest)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aRequest);
+
+ nsresult rv = aRequest->GetResultCode(&mResultCode);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mResultCode = rv;
+ } else if (NS_SUCCEEDED(mResultCode)) {
+ rv = GetStorageEstimate(aRequest, mStorageEstimate);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mResultCode = rv;
+ }
+ }
+
+ // In a main thread request.
+ if (!mProxy) {
+ MOZ_ASSERT(mPromise);
+
+ ResolveOrReject(mPromise);
+ return NS_OK;
+ }
+
+ // In a worker thread request.
+ MutexAutoLock lock(mProxy->Lock());
+
+ if (NS_WARN_IF(mProxy->CleanedUp())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<FinishWorkerRunnable> runnable = new FinishWorkerRunnable(this);
+ if (NS_WARN_IF(!runnable->Dispatch())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+bool
+EstimateResolver::
+FinishWorkerRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+{
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<PromiseWorkerProxy> proxy = mResolver->mProxy;
+ MOZ_ASSERT(proxy);
+
+ RefPtr<Promise> promise = proxy->WorkerPromise();
+ MOZ_ASSERT(promise);
+
+ mResolver->ResolveOrReject(promise);
+
+ proxy->CleanUp();
+
+ return true;
+}
+
+bool
+EstimateWorkerMainThreadRunnable::MainThreadRun()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIPrincipal> principal;
+
+ {
+ MutexAutoLock lock(mProxy->Lock());
+ if (mProxy->CleanedUp()) {
+ return true;
+ }
+ principal = mProxy->GetWorkerPrivate()->GetPrincipal();
+ }
+
+ MOZ_ASSERT(principal);
+
+ RefPtr<EstimateResolver> resolver = new EstimateResolver(mProxy);
+
+ RefPtr<nsIQuotaUsageRequest> request;
+ nsresult rv =
+ GetUsageForPrincipal(principal, resolver, getter_AddRefs(request));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ return true;
+}
+
+/*******************************************************************************
+ * StorageManager
+ ******************************************************************************/
+
+StorageManager::StorageManager(nsIGlobalObject* aGlobal)
+ : mOwner(aGlobal)
+{
+ MOZ_ASSERT(aGlobal);
+}
+
+StorageManager::~StorageManager()
+{
+}
+
+already_AddRefed<Promise>
+StorageManager::Estimate(ErrorResult& aRv)
+{
+ MOZ_ASSERT(mOwner);
+
+ RefPtr<Promise> promise = Promise::Create(mOwner, aRv);
+ if (NS_WARN_IF(!promise)) {
+ return nullptr;
+ }
+
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mOwner);
+ if (NS_WARN_IF(!window)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
+ if (NS_WARN_IF(!doc)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
+ MOZ_ASSERT(principal);
+
+ RefPtr<EstimateResolver> resolver = new EstimateResolver(promise);
+
+ RefPtr<nsIQuotaUsageRequest> request;
+ nsresult rv =
+ GetUsageForPrincipal(principal, resolver, getter_AddRefs(request));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+
+ return promise.forget();
+ }
+
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ RefPtr<PromiseWorkerProxy> promiseProxy =
+ PromiseWorkerProxy::Create(workerPrivate, promise);
+ if (NS_WARN_IF(!promiseProxy)) {
+ return nullptr;
+ }
+
+ RefPtr<EstimateWorkerMainThreadRunnable> runnnable =
+ new EstimateWorkerMainThreadRunnable(promiseProxy->GetWorkerPrivate(),
+ promiseProxy);
+
+ runnnable->Dispatch(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return promise.forget();
+}
+
+// static
+bool
+StorageManager::PrefEnabled(JSContext* aCx, JSObject* aObj)
+{
+ if (NS_IsMainThread()) {
+ return Preferences::GetBool("dom.storageManager.enabled");
+ }
+
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
+ MOZ_ASSERT(workerPrivate);
+
+ return workerPrivate->StorageManagerEnabled();
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(StorageManager, mOwner)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(StorageManager)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(StorageManager)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(StorageManager)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+JSObject*
+StorageManager::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto)
+{
+ return StorageManagerBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/quota/StorageManager.h b/dom/quota/StorageManager.h
new file mode 100644
index 000000000..162390f48
--- /dev/null
+++ b/dom/quota/StorageManager.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_StorageManager_h
+#define mozilla_dom_StorageManager_h
+
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+class Promise;
+struct StorageEstimate;
+
+class StorageManager final
+ : public nsISupports
+ , public nsWrapperCache
+{
+ nsCOMPtr<nsIGlobalObject> mOwner;
+
+public:
+ // Return dom.quota.storageManager.enabled on main/worker thread.
+ static bool
+ PrefEnabled(JSContext* aCx, JSObject* aObj);
+
+ explicit
+ StorageManager(nsIGlobalObject* aGlobal);
+
+ nsIGlobalObject*
+ GetParentObject() const
+ {
+ return mOwner;
+ }
+
+ // WebIDL
+ already_AddRefed<Promise>
+ Estimate(ErrorResult& aRv);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(StorageManager)
+
+ // nsWrapperCache
+ virtual JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+private:
+ ~StorageManager();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_StorageManager_h
diff --git a/dom/quota/UsageInfo.h b/dom/quota/UsageInfo.h
new file mode 100644
index 000000000..9d34f1bff
--- /dev/null
+++ b/dom/quota/UsageInfo.h
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_quota_usageinfo_h__
+#define mozilla_dom_quota_usageinfo_h__
+
+#include "mozilla/dom/quota/QuotaCommon.h"
+
+#include "mozilla/Atomics.h"
+#include "mozilla/CheckedInt.h"
+
+BEGIN_QUOTA_NAMESPACE
+
+class UsageInfo
+{
+public:
+ UsageInfo()
+ : mDatabaseUsage(0)
+ , mFileUsage(0)
+ , mLimit(0)
+ { }
+
+ virtual ~UsageInfo()
+ { }
+
+ void
+ Append(const UsageInfo& aUsageInfo)
+ {
+ IncrementUsage(&mDatabaseUsage, aUsageInfo.mDatabaseUsage);
+ IncrementUsage(&mFileUsage, aUsageInfo.mFileUsage);
+ }
+
+ void
+ AppendToDatabaseUsage(uint64_t aUsage)
+ {
+ IncrementUsage(&mDatabaseUsage, aUsage);
+ }
+
+ void
+ AppendToFileUsage(uint64_t aUsage)
+ {
+ IncrementUsage(&mFileUsage, aUsage);
+ }
+
+ void
+ SetLimit(uint64_t aLimit)
+ {
+ mLimit = aLimit;
+ }
+
+ uint64_t
+ DatabaseUsage()
+ {
+ return mDatabaseUsage;
+ }
+
+ uint64_t
+ FileUsage()
+ {
+ return mFileUsage;
+ }
+
+ uint64_t
+ Limit()
+ {
+ return mLimit;
+ }
+
+ uint64_t
+ TotalUsage()
+ {
+ uint64_t totalUsage = mDatabaseUsage;
+ IncrementUsage(&totalUsage, mFileUsage);
+ return totalUsage;
+ }
+
+ void
+ ResetUsage()
+ {
+ mDatabaseUsage = 0;
+ mFileUsage = 0;
+ }
+
+ static void
+ IncrementUsage(uint64_t* aUsage, uint64_t aDelta)
+ {
+ MOZ_ASSERT(aUsage);
+ CheckedUint64 value = *aUsage;
+ value += aDelta;
+ if (value.isValid()) {
+ *aUsage = value.value();
+ } else {
+ *aUsage = UINT64_MAX;
+ }
+ }
+
+private:
+ uint64_t mDatabaseUsage;
+ uint64_t mFileUsage;
+ uint64_t mLimit;
+};
+
+END_QUOTA_NAMESPACE
+
+#endif // mozilla_dom_quota_usageinfo_h__
diff --git a/dom/quota/moz.build b/dom/quota/moz.build
new file mode 100644
index 000000000..66c4f4f45
--- /dev/null
+++ b/dom/quota/moz.build
@@ -0,0 +1,59 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsIQuotaCallbacks.idl',
+ 'nsIQuotaManagerService.idl',
+ 'nsIQuotaRequests.idl',
+ 'nsIQuotaResults.idl',
+]
+
+XPIDL_MODULE = 'dom_quota'
+
+EXPORTS.mozilla.dom += [
+ 'StorageManager.h',
+]
+
+EXPORTS.mozilla.dom.quota += [
+ 'ActorsParent.h',
+ 'Client.h',
+ 'FileStreams.h',
+ 'OriginScope.h',
+ 'PersistenceType.h',
+ 'QuotaCommon.h',
+ 'QuotaManager.h',
+ 'QuotaManagerService.h',
+ 'QuotaObject.h',
+ 'SerializationHelpers.h',
+ 'UsageInfo.h',
+]
+
+UNIFIED_SOURCES += [
+ 'ActorsChild.cpp',
+ 'ActorsParent.cpp',
+ 'FileStreams.cpp',
+ 'QuotaManagerService.cpp',
+ 'QuotaRequests.cpp',
+ 'QuotaResults.cpp',
+ 'StorageManager.cpp',
+]
+
+IPDL_SOURCES += [
+ 'PQuota.ipdl',
+ 'PQuotaRequest.ipdl',
+ 'PQuotaUsageRequest.ipdl',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+LOCAL_INCLUDES += [
+ '../workers',
+ '/caps',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/dom/quota/nsIQuotaCallbacks.idl b/dom/quota/nsIQuotaCallbacks.idl
new file mode 100644
index 000000000..7c53db20c
--- /dev/null
+++ b/dom/quota/nsIQuotaCallbacks.idl
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIQuotaRequest;
+interface nsIQuotaUsageRequest;
+
+[scriptable, function, uuid(c8a21a2a-17b9-4b63-ad95-e0fbcff5de18)]
+interface nsIQuotaUsageCallback : nsISupports
+{
+ void onUsageResult(in nsIQuotaUsageRequest aRequest);
+};
+
+[scriptable, function, uuid(a08a28e2-5a74-4c84-8070-ed45a07eb013)]
+interface nsIQuotaCallback : nsISupports
+{
+ void onComplete(in nsIQuotaRequest aRequest);
+};
diff --git a/dom/quota/nsIQuotaManagerService.idl b/dom/quota/nsIQuotaManagerService.idl
new file mode 100644
index 000000000..f24ce2833
--- /dev/null
+++ b/dom/quota/nsIQuotaManagerService.idl
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIPrincipal;
+interface nsIQuotaRequest;
+interface nsIQuotaUsageCallback;
+interface nsIQuotaUsageRequest;
+
+[scriptable, builtinclass, uuid(1b3d0a38-8151-4cf9-89fa-4f92c2ef0e7e)]
+interface nsIQuotaManagerService : nsISupports
+{
+ /**
+ * Schedules an asynchronous callback that will inspect all origins and
+ * return the total amount of disk space being used by storages for each
+ * origin separately.
+ *
+ * @param aCallback
+ * The callback that will be called when the usage is available.
+ * @param aGetAll
+ * An optional boolean to indicate inspection of all origins,
+ * including internal ones.
+ */
+ [must_use] nsIQuotaUsageRequest
+ getUsage(in nsIQuotaUsageCallback aCallback,
+ [optional] in boolean aGetAll);
+
+ /**
+ * Schedules an asynchronous callback that will return the total amount of
+ * disk space being used by storages for the given origin.
+ *
+ * @param aPrincipal
+ * A principal for the origin whose usage is being queried.
+ * @param aCallback
+ * The callback that will be called when the usage is available.
+ * @param aGetGroupUsage
+ * An optional flag to indicate whether getting group usage and limit
+ * or origin usage and file usage. The default value is false.
+ * Note: Origin usage here represents total usage of an origin. However,
+ * group usage here represents only non-persistent usage of a group.
+ */
+ [must_use] nsIQuotaUsageRequest
+ getUsageForPrincipal(in nsIPrincipal aPrincipal,
+ in nsIQuotaUsageCallback aCallback,
+ [optional] in boolean aGetGroupUsage);
+
+ /**
+ * Removes all storages. The files may not be deleted immediately depending
+ * on prohibitive concurrent operations.
+ * Be careful, this removes *all* the data that has ever been stored!
+ *
+ * If the dom.quotaManager.testing preference is not true the call will be
+ * a no-op.
+ */
+ [must_use] nsIQuotaRequest
+ clear();
+
+ /**
+ * Removes all storages stored for the given URI. The files may not be
+ * deleted immediately depending on prohibitive concurrent operations.
+ *
+ * @param aPrincipal
+ * A principal for the origin whose storages are to be cleared.
+ * @param aPersistenceType
+ * An optional string that tells what persistence type of storages
+ * will be cleared.
+ * @param aClearAll
+ * An optional boolean to indicate clearing all storages under the
+ * given origin.
+ */
+ [must_use] nsIQuotaRequest
+ clearStoragesForPrincipal(in nsIPrincipal aPrincipal,
+ [optional] in ACString aPersistenceType,
+ [optional] in boolean aClearAll);
+
+ /**
+ * Resets quota and storage management. This can be used to force
+ * reinitialization of the temp storage, for example when the pref for
+ * overriding the temp storage limit has changed.
+ * Be carefull, this invalidates all live storages!
+ *
+ * If the dom.quotaManager.testing preference is not true the call will be
+ * a no-op.
+ */
+ [must_use] nsIQuotaRequest
+ reset();
+};
diff --git a/dom/quota/nsIQuotaRequests.idl b/dom/quota/nsIQuotaRequests.idl
new file mode 100644
index 000000000..155486217
--- /dev/null
+++ b/dom/quota/nsIQuotaRequests.idl
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIPrincipal;
+interface nsIQuotaCallback;
+interface nsIQuotaUsageCallback;
+interface nsIVariant;
+
+[scriptable, uuid(9af54222-0407-48fd-a4ab-9457c986fc49)]
+interface nsIQuotaRequestBase : nsISupports
+{
+ readonly attribute nsIPrincipal principal;
+
+ [must_use] readonly attribute nsresult resultCode;
+};
+
+[scriptable, uuid(166e28e6-cf6d-4927-a6d7-b51bca9d3469)]
+interface nsIQuotaUsageRequest : nsIQuotaRequestBase
+{
+ // The result can contain one of these types:
+ // array of nsIQuotaUsageResult
+ // nsIQuotaOriginUsageResult
+ [must_use] readonly attribute nsIVariant result;
+
+ attribute nsIQuotaUsageCallback callback;
+
+ [must_use] void
+ cancel();
+};
+
+[scriptable, uuid(22890e3e-ff25-4372-9684-d901060e2f6c)]
+interface nsIQuotaRequest : nsIQuotaRequestBase
+{
+ attribute nsIQuotaCallback callback;
+};
diff --git a/dom/quota/nsIQuotaResults.idl b/dom/quota/nsIQuotaResults.idl
new file mode 100644
index 000000000..cd7ffd3a0
--- /dev/null
+++ b/dom/quota/nsIQuotaResults.idl
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, function, uuid(d8c9328b-9aa8-4f5d-90e6-482de4a6d5b8)]
+interface nsIQuotaUsageResult : nsISupports
+{
+ readonly attribute ACString origin;
+
+ readonly attribute boolean persisted;
+
+ readonly attribute unsigned long long usage;
+};
+
+[scriptable, function, uuid(96df03d2-116a-493f-bb0b-118c212a6b32)]
+interface nsIQuotaOriginUsageResult : nsISupports
+{
+ readonly attribute unsigned long long usage;
+
+ readonly attribute unsigned long long fileUsage;
+
+ readonly attribute unsigned long long limit;
+};