michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "QuotaManager.h" michael@0: michael@0: #include "mozIApplicationClearPrivateDataParams.h" michael@0: #include "nsIBinaryInputStream.h" michael@0: #include "nsIBinaryOutputStream.h" michael@0: #include "nsIFile.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsIOfflineStorage.h" michael@0: #include "nsIPrincipal.h" michael@0: #include "nsIQuotaRequest.h" michael@0: #include "nsIRunnable.h" michael@0: #include "nsISimpleEnumerator.h" michael@0: #include "nsIScriptObjectPrincipal.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsITimer.h" michael@0: #include "nsIURI.h" michael@0: #include "nsIUsageCallback.h" michael@0: michael@0: #include michael@0: #include "GeckoProfiler.h" michael@0: #include "mozilla/Atomics.h" michael@0: #include "mozilla/CondVar.h" michael@0: #include "mozilla/dom/asmjscache/AsmJSCache.h" michael@0: #include "mozilla/dom/file/FileService.h" michael@0: #include "mozilla/dom/indexedDB/Client.h" michael@0: #include "mozilla/Mutex.h" michael@0: #include "mozilla/LazyIdleThread.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/Services.h" michael@0: #include "nsAppDirectoryServiceDefs.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsCRTGlue.h" michael@0: #include "nsDirectoryServiceUtils.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsScriptSecurityManager.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsXULAppAPI.h" michael@0: #include "xpcpublic.h" michael@0: michael@0: #include "AcquireListener.h" michael@0: #include "CheckQuotaHelper.h" michael@0: #include "OriginCollection.h" michael@0: #include "OriginOrPatternString.h" michael@0: #include "QuotaObject.h" michael@0: #include "StorageMatcher.h" michael@0: #include "UsageInfo.h" michael@0: #include "Utilities.h" michael@0: michael@0: // The amount of time, in milliseconds, that our IO thread will stay alive michael@0: // after the last event it processes. michael@0: #define DEFAULT_THREAD_TIMEOUT_MS 30000 michael@0: michael@0: // The amount of time, in milliseconds, that we will wait for active storage michael@0: // transactions on shutdown before aborting them. michael@0: #define DEFAULT_SHUTDOWN_TIMER_MS 30000 michael@0: michael@0: // Preference that users can set to override DEFAULT_QUOTA_MB michael@0: #define PREF_STORAGE_QUOTA "dom.indexedDB.warningQuota" michael@0: michael@0: // Preference that users can set to override temporary storage smart limit michael@0: // calculation. michael@0: #define PREF_FIXED_LIMIT "dom.quotaManager.temporaryStorage.fixedLimit" michael@0: #define PREF_CHUNK_SIZE "dom.quotaManager.temporaryStorage.chunkSize" michael@0: michael@0: // Preference that is used to enable testing features michael@0: #define PREF_TESTING_FEATURES "dom.quotaManager.testing" michael@0: michael@0: // profile-before-change, when we need to shut down quota manager michael@0: #define PROFILE_BEFORE_CHANGE_OBSERVER_ID "profile-before-change" michael@0: michael@0: // The name of the file that we use to load/save the last access time of an michael@0: // origin. michael@0: #define METADATA_FILE_NAME ".metadata" michael@0: michael@0: #define PERMISSION_DEFAUT_PERSISTENT_STORAGE "default-persistent-storage" michael@0: michael@0: #define KB * 1024ULL michael@0: #define MB * 1024ULL KB michael@0: #define GB * 1024ULL MB michael@0: michael@0: USING_QUOTA_NAMESPACE michael@0: using namespace mozilla::dom; michael@0: using mozilla::dom::file::FileService; michael@0: michael@0: static_assert( michael@0: static_cast(StorageType::Persistent) == michael@0: static_cast(PERSISTENCE_TYPE_PERSISTENT), michael@0: "Enum values should match."); michael@0: michael@0: static_assert( michael@0: static_cast(StorageType::Temporary) == michael@0: static_cast(PERSISTENCE_TYPE_TEMPORARY), michael@0: "Enum values should match."); michael@0: michael@0: BEGIN_QUOTA_NAMESPACE michael@0: michael@0: // A struct that contains the information corresponding to a pending or michael@0: // running operation that requires synchronization (e.g. opening a db, michael@0: // clearing dbs for an origin, etc). michael@0: struct SynchronizedOp michael@0: { michael@0: SynchronizedOp(const OriginOrPatternString& aOriginOrPattern, michael@0: Nullable aPersistenceType, michael@0: const nsACString& aId); michael@0: michael@0: ~SynchronizedOp(); michael@0: michael@0: // Test whether this SynchronizedOp needs to wait for the given op. michael@0: bool michael@0: MustWaitFor(const SynchronizedOp& aOp); michael@0: michael@0: void michael@0: DelayRunnable(nsIRunnable* aRunnable); michael@0: michael@0: void michael@0: DispatchDelayedRunnables(); michael@0: michael@0: const OriginOrPatternString mOriginOrPattern; michael@0: Nullable mPersistenceType; michael@0: nsCString mId; michael@0: nsRefPtr mListener; michael@0: nsTArray > mDelayedRunnables; michael@0: ArrayCluster mStorages; michael@0: }; michael@0: michael@0: class CollectOriginsHelper MOZ_FINAL : public nsRunnable michael@0: { michael@0: public: michael@0: CollectOriginsHelper(mozilla::Mutex& aMutex, uint64_t aMinSizeToBeFreed); michael@0: michael@0: NS_IMETHOD michael@0: Run(); michael@0: michael@0: // Blocks the current thread until origins are collected on the main thread. michael@0: // The returned value contains an aggregate size of those origins. michael@0: int64_t michael@0: BlockAndReturnOriginsForEviction(nsTArray& aOriginInfos); michael@0: michael@0: private: michael@0: ~CollectOriginsHelper() michael@0: { } michael@0: michael@0: uint64_t mMinSizeToBeFreed; michael@0: michael@0: mozilla::Mutex& mMutex; michael@0: mozilla::CondVar mCondVar; michael@0: michael@0: // The members below are protected by mMutex. michael@0: nsTArray mOriginInfos; michael@0: uint64_t mSizeToBeFreed; michael@0: bool mWaiting; michael@0: }; michael@0: michael@0: // Responsible for clearing the storage files for a particular origin on the michael@0: // IO thread. Created when nsIQuotaManager::ClearStoragesForURI is called. michael@0: // Runs three times, first on the main thread, next on the IO thread, and then michael@0: // finally again on the main thread. While on the IO thread the runnable will michael@0: // actually remove the origin's storage files and the directory that contains michael@0: // them before dispatching itself back to the main thread. When back on the main michael@0: // thread the runnable will notify the QuotaManager that the job has been michael@0: // completed. michael@0: class OriginClearRunnable MOZ_FINAL : public nsRunnable, michael@0: public AcquireListener michael@0: { michael@0: enum CallbackState { michael@0: // Not yet run. michael@0: Pending = 0, michael@0: michael@0: // Running on the main thread in the callback for OpenAllowed. michael@0: OpenAllowed, michael@0: michael@0: // Running on the IO thread. michael@0: IO, michael@0: michael@0: // Running on the main thread after all work is done. michael@0: Complete michael@0: }; michael@0: michael@0: public: michael@0: NS_DECL_ISUPPORTS_INHERITED michael@0: michael@0: OriginClearRunnable(const OriginOrPatternString& aOriginOrPattern, michael@0: Nullable aPersistenceType) michael@0: : mOriginOrPattern(aOriginOrPattern), michael@0: mPersistenceType(aPersistenceType), michael@0: mCallbackState(Pending) michael@0: { } michael@0: michael@0: NS_IMETHOD michael@0: Run(); michael@0: michael@0: // AcquireListener override michael@0: virtual nsresult michael@0: OnExclusiveAccessAcquired() MOZ_OVERRIDE; michael@0: michael@0: void michael@0: AdvanceState() michael@0: { michael@0: switch (mCallbackState) { michael@0: case Pending: michael@0: mCallbackState = OpenAllowed; michael@0: return; michael@0: case OpenAllowed: michael@0: mCallbackState = IO; michael@0: return; michael@0: case IO: michael@0: mCallbackState = Complete; michael@0: return; michael@0: default: michael@0: NS_NOTREACHED("Can't advance past Complete!"); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: InvalidateOpenedStorages(nsTArray >& aStorages, michael@0: void* aClosure); michael@0: michael@0: void michael@0: DeleteFiles(QuotaManager* aQuotaManager, michael@0: PersistenceType aPersistenceType); michael@0: michael@0: private: michael@0: OriginOrPatternString mOriginOrPattern; michael@0: Nullable mPersistenceType; michael@0: CallbackState mCallbackState; michael@0: }; michael@0: michael@0: // Responsible for calculating the amount of space taken up by storages of a michael@0: // certain origin. Created when nsIQuotaManager::GetUsageForURI is called. michael@0: // May be canceled with nsIQuotaRequest::Cancel. Runs three times, first michael@0: // on the main thread, next on the IO thread, and then finally again on the main michael@0: // thread. While on the IO thread the runnable will calculate the size of all michael@0: // files in the origin's directory before dispatching itself back to the main michael@0: // thread. When on the main thread the runnable will call the callback and then michael@0: // notify the QuotaManager that the job has been completed. michael@0: class AsyncUsageRunnable MOZ_FINAL : public UsageInfo, michael@0: public nsRunnable, michael@0: public nsIQuotaRequest michael@0: { michael@0: enum CallbackState { michael@0: // Not yet run. michael@0: Pending = 0, michael@0: michael@0: // Running on the main thread in the callback for OpenAllowed. michael@0: OpenAllowed, michael@0: michael@0: // Running on the IO thread. michael@0: IO, michael@0: michael@0: // Running on the main thread after all work is done. michael@0: Complete, michael@0: michael@0: // Running on the main thread after skipping the work michael@0: Shortcut michael@0: }; michael@0: michael@0: public: michael@0: NS_DECL_ISUPPORTS_INHERITED michael@0: NS_DECL_NSIQUOTAREQUEST michael@0: michael@0: AsyncUsageRunnable(uint32_t aAppId, michael@0: bool aInMozBrowserOnly, michael@0: const nsACString& aGroup, michael@0: const OriginOrPatternString& aOrigin, michael@0: nsIURI* aURI, michael@0: nsIUsageCallback* aCallback); michael@0: michael@0: NS_IMETHOD michael@0: Run(); michael@0: michael@0: void michael@0: AdvanceState() michael@0: { michael@0: switch (mCallbackState) { michael@0: case Pending: michael@0: mCallbackState = OpenAllowed; michael@0: return; michael@0: case OpenAllowed: michael@0: mCallbackState = IO; michael@0: return; michael@0: case IO: michael@0: mCallbackState = Complete; michael@0: return; michael@0: default: michael@0: NS_NOTREACHED("Can't advance past Complete!"); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: TakeShortcut(); michael@0: michael@0: private: michael@0: // Run calls the RunInternal method and makes sure that we always dispatch michael@0: // to the main thread in case of an error. michael@0: inline nsresult michael@0: RunInternal(); michael@0: michael@0: nsresult michael@0: AddToUsage(QuotaManager* aQuotaManager, michael@0: PersistenceType aPersistenceType); michael@0: michael@0: nsCOMPtr mURI; michael@0: nsCOMPtr mCallback; michael@0: uint32_t mAppId; michael@0: nsCString mGroup; michael@0: OriginOrPatternString mOrigin; michael@0: CallbackState mCallbackState; michael@0: bool mInMozBrowserOnly; michael@0: }; michael@0: michael@0: class ResetOrClearRunnable MOZ_FINAL : public nsRunnable, michael@0: public AcquireListener michael@0: { michael@0: enum CallbackState { michael@0: // Not yet run. michael@0: Pending = 0, michael@0: michael@0: // Running on the main thread in the callback for OpenAllowed. michael@0: OpenAllowed, michael@0: michael@0: // Running on the IO thread. michael@0: IO, michael@0: michael@0: // Running on the main thread after all work is done. michael@0: Complete michael@0: }; michael@0: michael@0: public: michael@0: NS_DECL_ISUPPORTS_INHERITED michael@0: michael@0: ResetOrClearRunnable(bool aClear) michael@0: : mCallbackState(Pending), michael@0: mClear(aClear) michael@0: { } michael@0: michael@0: NS_IMETHOD michael@0: Run(); michael@0: michael@0: // AcquireListener override michael@0: virtual nsresult michael@0: OnExclusiveAccessAcquired() MOZ_OVERRIDE; michael@0: michael@0: void michael@0: AdvanceState() michael@0: { michael@0: switch (mCallbackState) { michael@0: case Pending: michael@0: mCallbackState = OpenAllowed; michael@0: return; michael@0: case OpenAllowed: michael@0: mCallbackState = IO; michael@0: return; michael@0: case IO: michael@0: mCallbackState = Complete; michael@0: return; michael@0: default: michael@0: NS_NOTREACHED("Can't advance past Complete!"); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: InvalidateOpenedStorages(nsTArray >& aStorages, michael@0: void* aClosure); michael@0: michael@0: void michael@0: DeleteFiles(QuotaManager* aQuotaManager, michael@0: PersistenceType aPersistenceType); michael@0: michael@0: private: michael@0: CallbackState mCallbackState; michael@0: bool mClear; michael@0: }; michael@0: michael@0: // Responsible for finalizing eviction of certian origins (storage files have michael@0: // been already cleared, we just need to release IO thread only objects and michael@0: // allow next synchronized ops for evicted origins). Created when michael@0: // QuotaManager::FinalizeOriginEviction is called. Runs three times, first michael@0: // on the main thread, next on the IO thread, and then finally again on the main michael@0: // thread. While on the IO thread the runnable will release IO thread only michael@0: // objects before dispatching itself back to the main thread. When back on the michael@0: // main thread the runnable will call QuotaManager::AllowNextSynchronizedOp. michael@0: // The runnable can also run in a shortened mode (runs only twice). michael@0: class FinalizeOriginEvictionRunnable MOZ_FINAL : public nsRunnable michael@0: { michael@0: enum CallbackState { michael@0: // Not yet run. michael@0: Pending = 0, michael@0: michael@0: // Running on the main thread in the callback for OpenAllowed. michael@0: OpenAllowed, michael@0: michael@0: // Running on the IO thread. michael@0: IO, michael@0: michael@0: // Running on the main thread after IO work is done. michael@0: Complete michael@0: }; michael@0: michael@0: public: michael@0: FinalizeOriginEvictionRunnable(nsTArray& aOrigins) michael@0: : mCallbackState(Pending) michael@0: { michael@0: mOrigins.SwapElements(aOrigins); michael@0: } michael@0: michael@0: NS_IMETHOD michael@0: Run(); michael@0: michael@0: void michael@0: AdvanceState() michael@0: { michael@0: switch (mCallbackState) { michael@0: case Pending: michael@0: mCallbackState = OpenAllowed; michael@0: return; michael@0: case OpenAllowed: michael@0: mCallbackState = IO; michael@0: return; michael@0: case IO: michael@0: mCallbackState = Complete; michael@0: return; michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("Can't advance past Complete!"); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: Dispatch(); michael@0: michael@0: nsresult michael@0: RunImmediately(); michael@0: michael@0: private: michael@0: CallbackState mCallbackState; michael@0: nsTArray mOrigins; michael@0: }; michael@0: michael@0: bool michael@0: IsOnIOThread() michael@0: { michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "Must have a manager here!"); michael@0: michael@0: bool currentThread; michael@0: return NS_SUCCEEDED(quotaManager->IOThread()-> michael@0: IsOnCurrentThread(¤tThread)) && currentThread; michael@0: } michael@0: michael@0: void michael@0: AssertIsOnIOThread() michael@0: { michael@0: NS_ASSERTION(IsOnIOThread(), "Running on the wrong thread!"); michael@0: } michael@0: michael@0: void michael@0: AssertCurrentThreadOwnsQuotaMutex() michael@0: { michael@0: #ifdef DEBUG michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "Must have a manager here!"); michael@0: michael@0: quotaManager->AssertCurrentThreadOwnsQuotaMutex(); michael@0: #endif michael@0: } michael@0: michael@0: END_QUOTA_NAMESPACE michael@0: michael@0: namespace { michael@0: michael@0: // Amount of space that storages may use by default in megabytes. michael@0: static const int32_t kDefaultQuotaMB = 50; michael@0: michael@0: michael@0: QuotaManager* gInstance = nullptr; michael@0: mozilla::Atomic gShutdown(false); michael@0: michael@0: int32_t gStorageQuotaMB = kDefaultQuotaMB; michael@0: michael@0: // Constants for temporary storage limit computing. michael@0: static const int32_t kDefaultFixedLimitKB = -1; michael@0: static const uint32_t kDefaultChunkSizeKB = 10 * 1024; michael@0: int32_t gFixedLimitKB = kDefaultFixedLimitKB; michael@0: uint32_t gChunkSizeKB = kDefaultChunkSizeKB; michael@0: michael@0: bool gTestingEnabled = false; michael@0: michael@0: // A callback runnable used by the TransactionPool when it's safe to proceed michael@0: // with a SetVersion/DeleteDatabase/etc. michael@0: class WaitForTransactionsToFinishRunnable MOZ_FINAL : public nsRunnable michael@0: { michael@0: public: michael@0: WaitForTransactionsToFinishRunnable(SynchronizedOp* aOp) michael@0: : mOp(aOp), mCountdown(1) michael@0: { michael@0: NS_ASSERTION(mOp, "Why don't we have a runnable?"); michael@0: NS_ASSERTION(mOp->mStorages.IsEmpty(), "We're here too early!"); michael@0: NS_ASSERTION(mOp->mListener, michael@0: "What are we supposed to do when we're done?"); michael@0: NS_ASSERTION(mCountdown, "Wrong countdown!"); michael@0: } michael@0: michael@0: NS_IMETHOD michael@0: Run(); michael@0: michael@0: void michael@0: AddRun() michael@0: { michael@0: mCountdown++; michael@0: } michael@0: michael@0: private: michael@0: // The QuotaManager holds this alive. michael@0: SynchronizedOp* mOp; michael@0: uint32_t mCountdown; michael@0: }; michael@0: michael@0: class WaitForLockedFilesToFinishRunnable MOZ_FINAL : public nsRunnable michael@0: { michael@0: public: michael@0: WaitForLockedFilesToFinishRunnable() michael@0: : mBusy(true) michael@0: { } michael@0: michael@0: NS_IMETHOD michael@0: Run(); michael@0: michael@0: bool michael@0: IsBusy() const michael@0: { michael@0: return mBusy; michael@0: } michael@0: michael@0: private: michael@0: bool mBusy; michael@0: }; michael@0: michael@0: class SaveOriginAccessTimeRunnable MOZ_FINAL : public nsRunnable michael@0: { michael@0: public: michael@0: SaveOriginAccessTimeRunnable(const nsACString& aOrigin, int64_t aTimestamp) michael@0: : mOrigin(aOrigin), mTimestamp(aTimestamp) michael@0: { } michael@0: michael@0: NS_IMETHOD michael@0: Run(); michael@0: michael@0: private: michael@0: nsCString mOrigin; michael@0: int64_t mTimestamp; michael@0: }; michael@0: michael@0: struct MOZ_STACK_CLASS RemoveQuotaInfo michael@0: { michael@0: RemoveQuotaInfo(PersistenceType aPersistenceType, const nsACString& aPattern) michael@0: : persistenceType(aPersistenceType), pattern(aPattern) michael@0: { } michael@0: michael@0: PersistenceType persistenceType; michael@0: nsCString pattern; michael@0: }; michael@0: michael@0: struct MOZ_STACK_CLASS InactiveOriginsInfo michael@0: { michael@0: InactiveOriginsInfo(OriginCollection& aCollection, michael@0: nsTArray& aOrigins) michael@0: : collection(aCollection), origins(aOrigins) michael@0: { } michael@0: michael@0: OriginCollection& collection; michael@0: nsTArray& origins; michael@0: }; michael@0: michael@0: bool michael@0: IsMainProcess() michael@0: { michael@0: return XRE_GetProcessType() == GeckoProcessType_Default; michael@0: } michael@0: michael@0: void michael@0: SanitizeOriginString(nsCString& aOrigin) michael@0: { michael@0: // We want profiles to be platform-independent so we always need to replace michael@0: // the same characters on every platform. Windows has the most extensive set michael@0: // of illegal characters so we use its FILE_ILLEGAL_CHARACTERS and michael@0: // FILE_PATH_SEPARATOR. michael@0: static const char kReplaceChars[] = CONTROL_CHARACTERS "/:*?\"<>|\\"; michael@0: michael@0: #ifdef XP_WIN michael@0: NS_ASSERTION(!strcmp(kReplaceChars, michael@0: FILE_ILLEGAL_CHARACTERS FILE_PATH_SEPARATOR), michael@0: "Illegal file characters have changed!"); michael@0: #endif michael@0: michael@0: aOrigin.ReplaceChar(kReplaceChars, '+'); michael@0: } michael@0: michael@0: nsresult michael@0: EnsureDirectory(nsIFile* aDirectory, bool* aCreated) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: michael@0: nsresult rv = aDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755); michael@0: if (rv == NS_ERROR_FILE_ALREADY_EXISTS) { michael@0: bool isDirectory; michael@0: rv = aDirectory->IsDirectory(&isDirectory); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(isDirectory, NS_ERROR_UNEXPECTED); michael@0: michael@0: *aCreated = false; michael@0: } michael@0: else { michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: *aCreated = true; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CreateDirectoryUpgradeStamp(nsIFile* aDirectory) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: michael@0: nsCOMPtr metadataFile; michael@0: nsresult rv = aDirectory->Clone(getter_AddRefs(metadataFile)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = metadataFile->Append(NS_LITERAL_STRING(METADATA_FILE_NAME)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = metadataFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: GetDirectoryMetadataStream(nsIFile* aDirectory, bool aUpdate, michael@0: nsIBinaryOutputStream** aStream) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: michael@0: nsCOMPtr metadataFile; michael@0: nsresult rv = aDirectory->Clone(getter_AddRefs(metadataFile)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = metadataFile->Append(NS_LITERAL_STRING(METADATA_FILE_NAME)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr outputStream; michael@0: if (aUpdate) { michael@0: bool exists; michael@0: rv = metadataFile->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!exists) { michael@0: *aStream = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr stream; michael@0: rv = NS_NewLocalFileStream(getter_AddRefs(stream), metadataFile); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: outputStream = do_QueryInterface(stream); michael@0: NS_ENSURE_TRUE(outputStream, NS_ERROR_FAILURE); michael@0: } michael@0: else { michael@0: rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), michael@0: metadataFile); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: nsCOMPtr binaryStream = michael@0: do_CreateInstance("@mozilla.org/binaryoutputstream;1"); michael@0: NS_ENSURE_TRUE(binaryStream, NS_ERROR_FAILURE); michael@0: michael@0: rv = binaryStream->SetOutputStream(outputStream); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: binaryStream.forget(aStream); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CreateDirectoryMetadata(nsIFile* aDirectory, int64_t aTimestamp, michael@0: const nsACString& aGroup, const nsACString& aOrigin) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: michael@0: nsCOMPtr stream; michael@0: nsresult rv = michael@0: GetDirectoryMetadataStream(aDirectory, false, getter_AddRefs(stream)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: NS_ASSERTION(stream, "This shouldn't be null!"); michael@0: michael@0: rv = stream->Write64(aTimestamp); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = stream->WriteStringZ(PromiseFlatCString(aGroup).get()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = stream->WriteStringZ(PromiseFlatCString(aOrigin).get()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: GetDirectoryMetadata(nsIFile* aDirectory, int64_t* aTimestamp, michael@0: nsACString& aGroup, nsACString& aOrigin) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: michael@0: nsCOMPtr metadataFile; michael@0: nsresult rv = aDirectory->Clone(getter_AddRefs(metadataFile)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = metadataFile->Append(NS_LITERAL_STRING(METADATA_FILE_NAME)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr stream; michael@0: rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), metadataFile); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr bufferedStream; michael@0: rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), stream, 512); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr binaryStream = michael@0: do_CreateInstance("@mozilla.org/binaryinputstream;1"); michael@0: NS_ENSURE_TRUE(binaryStream, NS_ERROR_FAILURE); michael@0: michael@0: rv = binaryStream->SetInputStream(bufferedStream); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint64_t timestamp; michael@0: rv = binaryStream->Read64(×tamp); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCString group; michael@0: rv = binaryStream->ReadCString(group); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCString origin; michael@0: rv = binaryStream->ReadCString(origin); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: *aTimestamp = timestamp; michael@0: aGroup = group; michael@0: aOrigin = origin; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: MaybeUpgradeOriginDirectory(nsIFile* aDirectory) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: NS_ASSERTION(aDirectory, "Null pointer!"); michael@0: michael@0: nsCOMPtr metadataFile; michael@0: nsresult rv = aDirectory->Clone(getter_AddRefs(metadataFile)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = metadataFile->Append(NS_LITERAL_STRING(METADATA_FILE_NAME)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool exists; michael@0: rv = metadataFile->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!exists) { michael@0: // Directory structure upgrade needed. michael@0: // Move all files to IDB specific directory. michael@0: michael@0: nsString idbDirectoryName; michael@0: rv = Client::TypeToText(Client::IDB, idbDirectoryName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr idbDirectory; michael@0: rv = aDirectory->Clone(getter_AddRefs(idbDirectory)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = idbDirectory->Append(idbDirectoryName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = idbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755); michael@0: if (rv == NS_ERROR_FILE_ALREADY_EXISTS) { michael@0: NS_WARNING("IDB directory already exists!"); michael@0: michael@0: bool isDirectory; michael@0: rv = idbDirectory->IsDirectory(&isDirectory); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(isDirectory, NS_ERROR_UNEXPECTED); michael@0: } michael@0: else { michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: nsCOMPtr entries; michael@0: rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool hasMore; michael@0: while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { michael@0: nsCOMPtr entry; michael@0: rv = entries->GetNext(getter_AddRefs(entry)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr file = do_QueryInterface(entry); michael@0: NS_ENSURE_TRUE(file, NS_NOINTERFACE); michael@0: michael@0: nsString leafName; michael@0: rv = file->GetLeafName(leafName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!leafName.Equals(idbDirectoryName)) { michael@0: rv = file->MoveTo(idbDirectory, EmptyString()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: michael@0: rv = metadataFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // This method computes and returns our best guess for the temporary storage michael@0: // limit (in bytes), based on the amount of space users have free on their hard michael@0: // drive and on given temporary storage usage (also in bytes). michael@0: nsresult michael@0: GetTemporaryStorageLimit(nsIFile* aDirectory, uint64_t aCurrentUsage, michael@0: uint64_t* aLimit) michael@0: { michael@0: // Check for free space on device where temporary storage directory lives. michael@0: int64_t bytesAvailable; michael@0: nsresult rv = aDirectory->GetDiskSpaceAvailable(&bytesAvailable); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: NS_ASSERTION(bytesAvailable >= 0, "Negative bytes available?!"); michael@0: michael@0: uint64_t availableKB = michael@0: static_cast((bytesAvailable + aCurrentUsage) / 1024); michael@0: michael@0: // Grow/shrink in gChunkSizeKB units, deliberately, so that in the common case michael@0: // we don't shrink temporary storage and evict origin data every time we michael@0: // initialize. michael@0: availableKB = (availableKB / gChunkSizeKB) * gChunkSizeKB; michael@0: michael@0: // Allow temporary storage to consume up to half the available space. michael@0: uint64_t resultKB = availableKB * .50; michael@0: michael@0: *aLimit = resultKB * 1024; michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: QuotaManager::QuotaManager() michael@0: : mCurrentWindowIndex(BAD_TLS_INDEX), michael@0: mQuotaMutex("QuotaManager.mQuotaMutex"), michael@0: mTemporaryStorageLimit(0), michael@0: mTemporaryStorageUsage(0), michael@0: mTemporaryStorageInitialized(false), michael@0: mStorageAreaInitialized(false) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(!gInstance, "More than one instance!"); michael@0: } michael@0: michael@0: QuotaManager::~QuotaManager() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(!gInstance || gInstance == this, "Different instances!"); michael@0: gInstance = nullptr; michael@0: } michael@0: michael@0: // static michael@0: QuotaManager* michael@0: QuotaManager::GetOrCreate() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: if (IsShuttingDown()) { michael@0: NS_ERROR("Calling GetOrCreate() after shutdown!"); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!gInstance) { michael@0: nsRefPtr instance(new QuotaManager()); michael@0: michael@0: nsresult rv = instance->Init(); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: nsCOMPtr obs = mozilla::services::GetObserverService(); michael@0: NS_ENSURE_TRUE(obs, nullptr); michael@0: michael@0: // We need this callback to know when to shut down all our threads. michael@0: rv = obs->AddObserver(instance, PROFILE_BEFORE_CHANGE_OBSERVER_ID, false); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: // The observer service will hold our last reference, don't AddRef here. michael@0: gInstance = instance; michael@0: } michael@0: michael@0: return gInstance; michael@0: } michael@0: michael@0: // static michael@0: QuotaManager* michael@0: QuotaManager::Get() michael@0: { michael@0: // Does not return an owning reference. michael@0: return gInstance; michael@0: } michael@0: michael@0: // static michael@0: QuotaManager* michael@0: QuotaManager::FactoryCreate() michael@0: { michael@0: // Returns a raw pointer that carries an owning reference! Lame, but the michael@0: // singleton factory macros force this. michael@0: QuotaManager* quotaManager = GetOrCreate(); michael@0: NS_IF_ADDREF(quotaManager); michael@0: return quotaManager; michael@0: } michael@0: michael@0: // static michael@0: bool michael@0: QuotaManager::IsShuttingDown() michael@0: { michael@0: return gShutdown; michael@0: } michael@0: michael@0: nsresult michael@0: QuotaManager::Init() michael@0: { michael@0: // We need a thread-local to hold the current window. michael@0: NS_ASSERTION(mCurrentWindowIndex == BAD_TLS_INDEX, "Huh?"); michael@0: michael@0: if (PR_NewThreadPrivateIndex(&mCurrentWindowIndex, nullptr) != PR_SUCCESS) { michael@0: NS_ERROR("PR_NewThreadPrivateIndex failed, QuotaManager disabled"); michael@0: mCurrentWindowIndex = BAD_TLS_INDEX; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsresult rv; michael@0: if (IsMainProcess()) { michael@0: nsCOMPtr baseDir; michael@0: rv = NS_GetSpecialDirectory(NS_APP_INDEXEDDB_PARENT_DIR, michael@0: getter_AddRefs(baseDir)); michael@0: if (NS_FAILED(rv)) { michael@0: rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, michael@0: getter_AddRefs(baseDir)); michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr indexedDBDir; michael@0: rv = baseDir->Clone(getter_AddRefs(indexedDBDir)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = indexedDBDir->Append(NS_LITERAL_STRING("indexedDB")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = indexedDBDir->GetPath(mIndexedDBPath); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = baseDir->Append(NS_LITERAL_STRING("storage")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr persistentStorageDir; michael@0: rv = baseDir->Clone(getter_AddRefs(persistentStorageDir)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = persistentStorageDir->Append(NS_LITERAL_STRING("persistent")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = persistentStorageDir->GetPath(mPersistentStoragePath); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr temporaryStorageDir; michael@0: rv = baseDir->Clone(getter_AddRefs(temporaryStorageDir)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = temporaryStorageDir->Append(NS_LITERAL_STRING("temporary")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = temporaryStorageDir->GetPath(mTemporaryStoragePath); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Make a lazy thread for any IO we need (like clearing or enumerating the michael@0: // contents of storage directories). michael@0: mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, michael@0: NS_LITERAL_CSTRING("Storage I/O"), michael@0: LazyIdleThread::ManualShutdown); michael@0: michael@0: // Make a timer here to avoid potential failures later. We don't actually michael@0: // initialize the timer until shutdown. michael@0: mShutdownTimer = do_CreateInstance(NS_TIMER_CONTRACTID); michael@0: NS_ENSURE_TRUE(mShutdownTimer, NS_ERROR_FAILURE); michael@0: } michael@0: michael@0: if (NS_FAILED(Preferences::AddIntVarCache(&gStorageQuotaMB, michael@0: PREF_STORAGE_QUOTA, michael@0: kDefaultQuotaMB))) { michael@0: NS_WARNING("Unable to respond to quota pref changes!"); michael@0: } michael@0: michael@0: if (NS_FAILED(Preferences::AddIntVarCache(&gFixedLimitKB, PREF_FIXED_LIMIT, michael@0: kDefaultFixedLimitKB)) || michael@0: NS_FAILED(Preferences::AddUintVarCache(&gChunkSizeKB, michael@0: PREF_CHUNK_SIZE, michael@0: kDefaultChunkSizeKB))) { michael@0: NS_WARNING("Unable to respond to temp storage pref changes!"); michael@0: } michael@0: michael@0: if (NS_FAILED(Preferences::AddBoolVarCache(&gTestingEnabled, michael@0: PREF_TESTING_FEATURES, false))) { michael@0: NS_WARNING("Unable to respond to testing pref changes!"); michael@0: } michael@0: michael@0: static_assert(Client::IDB == 0 && Client::ASMJS == 1 && Client::TYPE_MAX == 2, michael@0: "Fix the registration!"); michael@0: michael@0: NS_ASSERTION(mClients.Capacity() == Client::TYPE_MAX, michael@0: "Should be using an auto array with correct capacity!"); michael@0: michael@0: // Register IndexedDB michael@0: mClients.AppendElement(new indexedDB::Client()); michael@0: mClients.AppendElement(asmjscache::CreateClient()); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: QuotaManager::InitQuotaForOrigin(PersistenceType aPersistenceType, michael@0: const nsACString& aGroup, michael@0: const nsACString& aOrigin, michael@0: uint64_t aLimitBytes, michael@0: uint64_t aUsageBytes, michael@0: int64_t aAccessTime) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: MOZ_ASSERT(aLimitBytes > 0 || michael@0: aPersistenceType == PERSISTENCE_TYPE_TEMPORARY); michael@0: MOZ_ASSERT(aUsageBytes <= aLimitBytes || michael@0: aPersistenceType == PERSISTENCE_TYPE_TEMPORARY); michael@0: michael@0: MutexAutoLock lock(mQuotaMutex); michael@0: michael@0: GroupInfoPair* pair; michael@0: if (!mGroupInfoPairs.Get(aGroup, &pair)) { michael@0: pair = new GroupInfoPair(); michael@0: mGroupInfoPairs.Put(aGroup, pair); michael@0: // The hashtable is now responsible to delete the GroupInfoPair. michael@0: } michael@0: michael@0: nsRefPtr groupInfo = pair->LockedGetGroupInfo(aPersistenceType); michael@0: if (!groupInfo) { michael@0: groupInfo = new GroupInfo(aPersistenceType, aGroup); michael@0: pair->LockedSetGroupInfo(groupInfo); michael@0: } michael@0: michael@0: nsRefPtr originInfo = michael@0: new OriginInfo(groupInfo, aOrigin, aLimitBytes, aUsageBytes, aAccessTime); michael@0: groupInfo->LockedAddOriginInfo(originInfo); michael@0: } michael@0: michael@0: void michael@0: QuotaManager::DecreaseUsageForOrigin(PersistenceType aPersistenceType, michael@0: const nsACString& aGroup, michael@0: const nsACString& aOrigin, michael@0: int64_t aSize) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: michael@0: MutexAutoLock lock(mQuotaMutex); michael@0: michael@0: GroupInfoPair* pair; michael@0: if (!mGroupInfoPairs.Get(aGroup, &pair)) { michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr groupInfo = pair->LockedGetGroupInfo(aPersistenceType); michael@0: if (!groupInfo) { michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr originInfo = groupInfo->LockedGetOriginInfo(aOrigin); michael@0: if (originInfo) { michael@0: originInfo->LockedDecreaseUsage(aSize); michael@0: } michael@0: } michael@0: michael@0: void michael@0: QuotaManager::UpdateOriginAccessTime(PersistenceType aPersistenceType, michael@0: const nsACString& aGroup, michael@0: const nsACString& aOrigin) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: MutexAutoLock lock(mQuotaMutex); michael@0: michael@0: GroupInfoPair* pair; michael@0: if (!mGroupInfoPairs.Get(aGroup, &pair)) { michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr groupInfo = michael@0: pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY); michael@0: if (!groupInfo) { michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr originInfo = groupInfo->LockedGetOriginInfo(aOrigin); michael@0: if (originInfo) { michael@0: int64_t timestamp = PR_Now(); michael@0: originInfo->LockedUpdateAccessTime(timestamp); michael@0: michael@0: if (!groupInfo->IsForTemporaryStorage()) { michael@0: return; michael@0: } michael@0: michael@0: MutexAutoUnlock autoUnlock(mQuotaMutex); michael@0: michael@0: SaveOriginAccessTime(aOrigin, timestamp); michael@0: } michael@0: } michael@0: michael@0: // static michael@0: PLDHashOperator michael@0: QuotaManager::RemoveQuotaCallback(const nsACString& aKey, michael@0: nsAutoPtr& aValue, michael@0: void* aUserArg) michael@0: { michael@0: NS_ASSERTION(!aKey.IsEmpty(), "Empty key!"); michael@0: NS_ASSERTION(aValue, "Null pointer!"); michael@0: michael@0: nsRefPtr groupInfo = michael@0: aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY); michael@0: if (groupInfo) { michael@0: groupInfo->LockedRemoveOriginInfos(); michael@0: } michael@0: michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: void michael@0: QuotaManager::RemoveQuota() michael@0: { michael@0: MutexAutoLock lock(mQuotaMutex); michael@0: michael@0: mGroupInfoPairs.Enumerate(RemoveQuotaCallback, nullptr); michael@0: michael@0: NS_ASSERTION(mTemporaryStorageUsage == 0, "Should be zero!"); michael@0: } michael@0: michael@0: // static michael@0: PLDHashOperator michael@0: QuotaManager::RemoveQuotaForPersistenceTypeCallback( michael@0: const nsACString& aKey, michael@0: nsAutoPtr& aValue, michael@0: void* aUserArg) michael@0: { michael@0: NS_ASSERTION(!aKey.IsEmpty(), "Empty key!"); michael@0: NS_ASSERTION(aValue, "Null pointer!"); michael@0: NS_ASSERTION(aUserArg, "Null pointer!"); michael@0: michael@0: PersistenceType& persistenceType = *static_cast(aUserArg); michael@0: michael@0: if (persistenceType == PERSISTENCE_TYPE_TEMPORARY) { michael@0: nsRefPtr groupInfo = michael@0: aValue->LockedGetGroupInfo(persistenceType); michael@0: if (groupInfo) { michael@0: groupInfo->LockedRemoveOriginInfos(); michael@0: } michael@0: } michael@0: michael@0: aValue->LockedClearGroupInfo(persistenceType); michael@0: michael@0: return aValue->LockedHasGroupInfos() ? PL_DHASH_NEXT : PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: void michael@0: QuotaManager::RemoveQuotaForPersistenceType(PersistenceType aPersistenceType) michael@0: { michael@0: MutexAutoLock lock(mQuotaMutex); michael@0: michael@0: mGroupInfoPairs.Enumerate(RemoveQuotaForPersistenceTypeCallback, michael@0: &aPersistenceType); michael@0: michael@0: NS_ASSERTION(aPersistenceType == PERSISTENCE_TYPE_PERSISTENT || michael@0: mTemporaryStorageUsage == 0, "Should be zero!"); michael@0: } michael@0: michael@0: // static michael@0: PLDHashOperator michael@0: QuotaManager::RemoveQuotaForPatternCallback(const nsACString& aKey, michael@0: nsAutoPtr& aValue, michael@0: void* aUserArg) michael@0: { michael@0: NS_ASSERTION(!aKey.IsEmpty(), "Empty key!"); michael@0: NS_ASSERTION(aValue, "Null pointer!"); michael@0: NS_ASSERTION(aUserArg, "Null pointer!"); michael@0: michael@0: RemoveQuotaInfo* info = static_cast(aUserArg); michael@0: michael@0: nsRefPtr groupInfo = michael@0: aValue->LockedGetGroupInfo(info->persistenceType); michael@0: if (groupInfo) { michael@0: groupInfo->LockedRemoveOriginInfosForPattern(info->pattern); michael@0: michael@0: if (!groupInfo->LockedHasOriginInfos()) { michael@0: aValue->LockedClearGroupInfo(info->persistenceType); michael@0: michael@0: if (!aValue->LockedHasGroupInfos()) { michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: QuotaManager::RemoveQuotaForPattern(PersistenceType aPersistenceType, michael@0: const nsACString& aPattern) michael@0: { michael@0: NS_ASSERTION(!aPattern.IsEmpty(), "Empty pattern!"); michael@0: michael@0: RemoveQuotaInfo info(aPersistenceType, aPattern); michael@0: michael@0: MutexAutoLock lock(mQuotaMutex); michael@0: michael@0: mGroupInfoPairs.Enumerate(RemoveQuotaForPatternCallback, &info); michael@0: } michael@0: michael@0: already_AddRefed michael@0: QuotaManager::GetQuotaObject(PersistenceType aPersistenceType, michael@0: const nsACString& aGroup, michael@0: const nsACString& aOrigin, michael@0: nsIFile* aFile) michael@0: { michael@0: NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: nsString path; michael@0: nsresult rv = aFile->GetPath(path); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: int64_t fileSize; michael@0: michael@0: bool exists; michael@0: rv = aFile->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: if (exists) { michael@0: rv = aFile->GetFileSize(&fileSize); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: } michael@0: else { michael@0: fileSize = 0; michael@0: } michael@0: michael@0: nsRefPtr result; michael@0: { michael@0: MutexAutoLock lock(mQuotaMutex); michael@0: michael@0: GroupInfoPair* pair; michael@0: if (!mGroupInfoPairs.Get(aGroup, &pair)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRefPtr groupInfo = pair->LockedGetGroupInfo(aPersistenceType); michael@0: michael@0: if (!groupInfo) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRefPtr originInfo = groupInfo->LockedGetOriginInfo(aOrigin); michael@0: michael@0: if (!originInfo) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // We need this extra raw pointer because we can't assign to the smart michael@0: // pointer directly since QuotaObject::AddRef would try to acquire the same michael@0: // mutex. michael@0: QuotaObject* quotaObject; michael@0: if (!originInfo->mQuotaObjects.Get(path, "aObject)) { michael@0: // Create a new QuotaObject. michael@0: quotaObject = new QuotaObject(originInfo, path, fileSize); michael@0: michael@0: // Put it to the hashtable. The hashtable is not responsible to delete michael@0: // the QuotaObject. michael@0: originInfo->mQuotaObjects.Put(path, quotaObject); michael@0: } michael@0: michael@0: // Addref the QuotaObject and move the ownership to the result. This must michael@0: // happen before we unlock! michael@0: result = quotaObject->LockedAddRef(); michael@0: } michael@0: michael@0: // The caller becomes the owner of the QuotaObject, that is, the caller is michael@0: // is responsible to delete it when the last reference is removed. michael@0: return result.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: QuotaManager::GetQuotaObject(PersistenceType aPersistenceType, michael@0: const nsACString& aGroup, michael@0: const nsACString& aOrigin, michael@0: const nsAString& aPath) michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: rv = file->InitWithPath(aPath); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: return GetQuotaObject(aPersistenceType, aGroup, aOrigin, file); michael@0: } michael@0: michael@0: bool michael@0: QuotaManager::RegisterStorage(nsIOfflineStorage* aStorage) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(aStorage, "Null pointer!"); michael@0: michael@0: // Don't allow any new storages to be created after shutdown. michael@0: if (IsShuttingDown()) { michael@0: return false; michael@0: } michael@0: michael@0: // Add this storage to its origin info if it exists, create it otherwise. michael@0: const nsACString& origin = aStorage->Origin(); michael@0: ArrayCluster* cluster; michael@0: if (!mLiveStorages.Get(origin, &cluster)) { michael@0: cluster = new ArrayCluster(); michael@0: mLiveStorages.Put(origin, cluster); michael@0: michael@0: UpdateOriginAccessTime(aStorage->Type(), aStorage->Group(), origin); michael@0: } michael@0: (*cluster)[aStorage->GetClient()->GetType()].AppendElement(aStorage); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: QuotaManager::UnregisterStorage(nsIOfflineStorage* aStorage) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(aStorage, "Null pointer!"); michael@0: michael@0: // Remove this storage from its origin array, maybe remove the array if it michael@0: // is then empty. michael@0: const nsACString& origin = aStorage->Origin(); michael@0: ArrayCluster* cluster; michael@0: if (mLiveStorages.Get(origin, &cluster) && michael@0: (*cluster)[aStorage->GetClient()->GetType()].RemoveElement(aStorage)) { michael@0: if (cluster->IsEmpty()) { michael@0: mLiveStorages.Remove(origin); michael@0: michael@0: UpdateOriginAccessTime(aStorage->Type(), aStorage->Group(), origin); michael@0: } michael@0: return; michael@0: } michael@0: NS_ERROR("Didn't know anything about this storage!"); michael@0: } michael@0: michael@0: void michael@0: QuotaManager::OnStorageClosed(nsIOfflineStorage* aStorage) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(aStorage, "Null pointer!"); michael@0: michael@0: // Check through the list of SynchronizedOps to see if any are waiting for michael@0: // this storage to close before proceeding. michael@0: SynchronizedOp* op = michael@0: FindSynchronizedOp(aStorage->Origin(), michael@0: Nullable(aStorage->Type()), michael@0: aStorage->Id()); michael@0: if (op) { michael@0: Client::Type clientType = aStorage->GetClient()->GetType(); michael@0: michael@0: // This storage is in the scope of this SynchronizedOp. Remove it michael@0: // from the list if necessary. michael@0: if (op->mStorages[clientType].RemoveElement(aStorage)) { michael@0: // Now set up the helper if there are no more live storages. michael@0: NS_ASSERTION(op->mListener, michael@0: "How did we get rid of the listener before removing the " michael@0: "last storage?"); michael@0: if (op->mStorages[clientType].IsEmpty()) { michael@0: // At this point, all storages are closed, so no new transactions michael@0: // can be started. There may, however, still be outstanding michael@0: // transactions that have not completed. We need to wait for those michael@0: // before we dispatch the helper. michael@0: if (NS_FAILED(RunSynchronizedOp(aStorage, op))) { michael@0: NS_WARNING("Failed to run synchronized op!"); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: QuotaManager::AbortCloseStoragesForWindow(nsPIDOMWindow* aWindow) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(aWindow, "Null pointer!"); michael@0: michael@0: FileService* service = FileService::Get(); michael@0: michael@0: StorageMatcher > liveStorages; michael@0: liveStorages.Find(mLiveStorages); michael@0: michael@0: for (uint32_t i = 0; i < Client::TYPE_MAX; i++) { michael@0: nsRefPtr& client = mClients[i]; michael@0: bool utilized = service && client->IsFileServiceUtilized(); michael@0: bool activated = client->IsTransactionServiceActivated(); michael@0: michael@0: nsTArray& array = liveStorages[i]; michael@0: for (uint32_t j = 0; j < array.Length(); j++) { michael@0: nsIOfflineStorage*& storage = array[j]; michael@0: michael@0: if (storage->IsOwned(aWindow)) { michael@0: if (NS_FAILED(storage->Close())) { michael@0: NS_WARNING("Failed to close storage for dying window!"); michael@0: } michael@0: michael@0: if (utilized) { michael@0: service->AbortLockedFilesForStorage(storage); michael@0: } michael@0: michael@0: if (activated) { michael@0: client->AbortTransactionsForStorage(storage); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: QuotaManager::HasOpenTransactions(nsPIDOMWindow* aWindow) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(aWindow, "Null pointer!"); michael@0: michael@0: FileService* service = FileService::Get(); michael@0: michael@0: nsAutoPtr > > liveStorages; michael@0: michael@0: for (uint32_t i = 0; i < Client::TYPE_MAX; i++) { michael@0: nsRefPtr& client = mClients[i]; michael@0: bool utilized = service && client->IsFileServiceUtilized(); michael@0: bool activated = client->IsTransactionServiceActivated(); michael@0: michael@0: if (utilized || activated) { michael@0: if (!liveStorages) { michael@0: liveStorages = new StorageMatcher >(); michael@0: liveStorages->Find(mLiveStorages); michael@0: } michael@0: michael@0: nsTArray& storages = liveStorages->ArrayAt(i); michael@0: for (uint32_t j = 0; j < storages.Length(); j++) { michael@0: nsIOfflineStorage*& storage = storages[j]; michael@0: michael@0: if (storage->IsOwned(aWindow) && michael@0: ((utilized && service->HasLockedFilesForStorage(storage)) || michael@0: (activated && client->HasTransactionsForStorage(storage)))) { michael@0: return true; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: nsresult michael@0: QuotaManager::WaitForOpenAllowed(const OriginOrPatternString& aOriginOrPattern, michael@0: Nullable aPersistenceType, michael@0: const nsACString& aId, nsIRunnable* aRunnable) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(!aOriginOrPattern.IsEmpty() || aOriginOrPattern.IsNull(), michael@0: "Empty pattern!"); michael@0: NS_ASSERTION(aRunnable, "Null pointer!"); michael@0: michael@0: nsAutoPtr op(new SynchronizedOp(aOriginOrPattern, michael@0: aPersistenceType, aId)); michael@0: michael@0: // See if this runnable needs to wait. michael@0: bool delayed = false; michael@0: for (uint32_t index = mSynchronizedOps.Length(); index > 0; index--) { michael@0: nsAutoPtr& existingOp = mSynchronizedOps[index - 1]; michael@0: if (op->MustWaitFor(*existingOp)) { michael@0: existingOp->DelayRunnable(aRunnable); michael@0: delayed = true; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // Otherwise, dispatch it immediately. michael@0: if (!delayed) { michael@0: nsresult rv = NS_DispatchToCurrentThread(aRunnable); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Adding this to the synchronized ops list will block any additional michael@0: // ops from proceeding until this one is done. michael@0: mSynchronizedOps.AppendElement(op.forget()); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: QuotaManager::AddSynchronizedOp(const OriginOrPatternString& aOriginOrPattern, michael@0: Nullable aPersistenceType) michael@0: { michael@0: nsAutoPtr op(new SynchronizedOp(aOriginOrPattern, michael@0: aPersistenceType, michael@0: EmptyCString())); michael@0: michael@0: #ifdef DEBUG michael@0: for (uint32_t index = mSynchronizedOps.Length(); index > 0; index--) { michael@0: nsAutoPtr& existingOp = mSynchronizedOps[index - 1]; michael@0: NS_ASSERTION(!op->MustWaitFor(*existingOp), "What?"); michael@0: } michael@0: #endif michael@0: michael@0: mSynchronizedOps.AppendElement(op.forget()); michael@0: } michael@0: michael@0: void michael@0: QuotaManager::AllowNextSynchronizedOp( michael@0: const OriginOrPatternString& aOriginOrPattern, michael@0: Nullable aPersistenceType, michael@0: const nsACString& aId) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(!aOriginOrPattern.IsEmpty() || aOriginOrPattern.IsNull(), michael@0: "Empty origin/pattern!"); michael@0: michael@0: uint32_t count = mSynchronizedOps.Length(); michael@0: for (uint32_t index = 0; index < count; index++) { michael@0: nsAutoPtr& op = mSynchronizedOps[index]; michael@0: if (op->mOriginOrPattern.IsOrigin() == aOriginOrPattern.IsOrigin() && michael@0: op->mOriginOrPattern == aOriginOrPattern && michael@0: op->mPersistenceType == aPersistenceType) { michael@0: if (op->mId == aId) { michael@0: NS_ASSERTION(op->mStorages.IsEmpty(), "How did this happen?"); michael@0: michael@0: op->DispatchDelayedRunnables(); michael@0: michael@0: mSynchronizedOps.RemoveElementAt(index); michael@0: return; michael@0: } michael@0: michael@0: // If one or the other is for an origin clear, we should have matched michael@0: // solely on origin. michael@0: NS_ASSERTION(!op->mId.IsEmpty() && !aId.IsEmpty(), michael@0: "Why didn't we match earlier?"); michael@0: } michael@0: } michael@0: michael@0: NS_NOTREACHED("Why didn't we find a SynchronizedOp?"); michael@0: } michael@0: michael@0: nsresult michael@0: QuotaManager::GetDirectoryForOrigin(PersistenceType aPersistenceType, michael@0: const nsACString& aASCIIOrigin, michael@0: nsIFile** aDirectory) const michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr directory = michael@0: do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = directory->InitWithPath(GetStoragePath(aPersistenceType)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoCString originSanitized(aASCIIOrigin); michael@0: SanitizeOriginString(originSanitized); michael@0: michael@0: rv = directory->Append(NS_ConvertASCIItoUTF16(originSanitized)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: directory.forget(aDirectory); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: QuotaManager::InitializeOrigin(PersistenceType aPersistenceType, michael@0: const nsACString& aGroup, michael@0: const nsACString& aOrigin, michael@0: bool aTrackQuota, michael@0: int64_t aAccessTime, michael@0: nsIFile* aDirectory) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: michael@0: nsresult rv; michael@0: michael@0: bool temporaryStorage = aPersistenceType == PERSISTENCE_TYPE_TEMPORARY; michael@0: if (!temporaryStorage) { michael@0: rv = MaybeUpgradeOriginDirectory(aDirectory); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // We need to initialize directories of all clients if they exists and also michael@0: // get the total usage to initialize the quota. michael@0: nsAutoPtr usageInfo; michael@0: if (aTrackQuota) { michael@0: usageInfo = new UsageInfo(); michael@0: } michael@0: michael@0: nsCOMPtr entries; michael@0: rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool hasMore; michael@0: while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { michael@0: nsCOMPtr entry; michael@0: rv = entries->GetNext(getter_AddRefs(entry)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr file = do_QueryInterface(entry); michael@0: NS_ENSURE_TRUE(file, NS_NOINTERFACE); michael@0: michael@0: nsString leafName; michael@0: rv = file->GetLeafName(leafName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (leafName.EqualsLiteral(METADATA_FILE_NAME) || michael@0: leafName.EqualsLiteral(DSSTORE_FILE_NAME)) { michael@0: continue; michael@0: } michael@0: michael@0: bool isDirectory; michael@0: rv = file->IsDirectory(&isDirectory); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!isDirectory) { michael@0: NS_WARNING("Unknown file found!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: Client::Type clientType; michael@0: rv = Client::TypeFromText(leafName, clientType); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Unknown directory found!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: rv = mClients[clientType]->InitOrigin(aPersistenceType, aGroup, aOrigin, michael@0: usageInfo); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: if (aTrackQuota) { michael@0: uint64_t quotaMaxBytes; michael@0: uint64_t totalUsageBytes = usageInfo->TotalUsage(); michael@0: michael@0: if (temporaryStorage) { michael@0: // Temporary storage has no limit for origin usage (there's a group and michael@0: // the global limit though). michael@0: quotaMaxBytes = 0; michael@0: } michael@0: else { michael@0: quotaMaxBytes = GetStorageQuotaMB() * 1024 * 1024; michael@0: if (totalUsageBytes > quotaMaxBytes) { michael@0: NS_WARNING("Origin is already using more storage than allowed!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: } michael@0: michael@0: InitQuotaForOrigin(aPersistenceType, aGroup, aOrigin, quotaMaxBytes, michael@0: totalUsageBytes, aAccessTime); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: QuotaManager::MaybeUpgradeIndexedDBDirectory() michael@0: { michael@0: AssertIsOnIOThread(); michael@0: michael@0: if (mStorageAreaInitialized) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr indexedDBDir = michael@0: do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = indexedDBDir->InitWithPath(mIndexedDBPath); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool exists; michael@0: rv = indexedDBDir->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!exists) { michael@0: // Nothing to upgrade. michael@0: mStorageAreaInitialized = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool isDirectory; michael@0: rv = indexedDBDir->IsDirectory(&isDirectory); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!isDirectory) { michael@0: NS_WARNING("indexedDB entry is not a directory!"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr persistentStorageDir = michael@0: do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = persistentStorageDir->InitWithPath(mPersistentStoragePath); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = persistentStorageDir->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (exists) { michael@0: NS_WARNING("indexedDB directory shouldn't exist after the upgrade!"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr storageDir; michael@0: rv = persistentStorageDir->GetParent(getter_AddRefs(storageDir)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsString persistentStorageName; michael@0: rv = persistentStorageDir->GetLeafName(persistentStorageName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // MoveTo() is atomic if the move happens on the same volume which should michael@0: // be our case, so even if we crash in the middle of the operation nothing michael@0: // breaks next time we try to initialize. michael@0: // However there's a theoretical possibility that the indexedDB directory michael@0: // is on different volume, but it should be rare enough that we don't have michael@0: // to worry about it. michael@0: rv = indexedDBDir->MoveTo(storageDir, persistentStorageName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mStorageAreaInitialized = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: QuotaManager::EnsureOriginIsInitialized(PersistenceType aPersistenceType, michael@0: const nsACString& aGroup, michael@0: const nsACString& aOrigin, michael@0: bool aTrackQuota, michael@0: nsIFile** aDirectory) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: michael@0: nsresult rv = MaybeUpgradeIndexedDBDirectory(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Get directory for this origin and persistence type. michael@0: nsCOMPtr directory; michael@0: rv = GetDirectoryForOrigin(aPersistenceType, aOrigin, michael@0: getter_AddRefs(directory)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) { michael@0: if (mInitializedOrigins.Contains(aOrigin)) { michael@0: NS_ADDREF(*aDirectory = directory); michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool created; michael@0: rv = EnsureDirectory(directory, &created); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (created) { michael@0: rv = CreateDirectoryUpgradeStamp(directory); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: rv = InitializeOrigin(aPersistenceType, aGroup, aOrigin, aTrackQuota, 0, michael@0: directory); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mInitializedOrigins.AppendElement(aOrigin); michael@0: michael@0: directory.forget(aDirectory); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_ASSERTION(aPersistenceType == PERSISTENCE_TYPE_TEMPORARY, "Huh?"); michael@0: NS_ASSERTION(aTrackQuota, "Huh?"); michael@0: michael@0: if (!mTemporaryStorageInitialized) { michael@0: nsCOMPtr parentDirectory; michael@0: rv = directory->GetParent(getter_AddRefs(parentDirectory)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool created; michael@0: rv = EnsureDirectory(parentDirectory, &created); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr entries; michael@0: rv = parentDirectory->GetDirectoryEntries(getter_AddRefs(entries)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool hasMore; michael@0: while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { michael@0: nsCOMPtr entry; michael@0: rv = entries->GetNext(getter_AddRefs(entry)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr childDirectory = do_QueryInterface(entry); michael@0: NS_ENSURE_TRUE(childDirectory, NS_NOINTERFACE); michael@0: michael@0: bool isDirectory; michael@0: rv = childDirectory->IsDirectory(&isDirectory); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(isDirectory, NS_ERROR_UNEXPECTED); michael@0: michael@0: int64_t timestamp; michael@0: nsCString group; michael@0: nsCString origin; michael@0: rv = GetDirectoryMetadata(childDirectory, ×tamp, group, origin); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = InitializeOrigin(aPersistenceType, group, origin, aTrackQuota, michael@0: timestamp, childDirectory); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Failed to initialize origin!"); michael@0: michael@0: // We have to cleanup partially initialized quota for temporary storage. michael@0: RemoveQuotaForPersistenceType(aPersistenceType); michael@0: michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: if (gFixedLimitKB >= 0) { michael@0: mTemporaryStorageLimit = gFixedLimitKB * 1024; michael@0: } michael@0: else { michael@0: rv = GetTemporaryStorageLimit(parentDirectory, mTemporaryStorageUsage, michael@0: &mTemporaryStorageLimit); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: mTemporaryStorageInitialized = true; michael@0: michael@0: CheckTemporaryStorageLimits(); michael@0: } michael@0: michael@0: bool created; michael@0: rv = EnsureDirectory(directory, &created); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (created) { michael@0: int64_t timestamp = PR_Now(); michael@0: michael@0: rv = CreateDirectoryMetadata(directory, timestamp, aGroup, aOrigin); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = InitializeOrigin(aPersistenceType, aGroup, aOrigin, aTrackQuota, michael@0: timestamp, directory); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: directory.forget(aDirectory); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: QuotaManager::OriginClearCompleted( michael@0: PersistenceType aPersistenceType, michael@0: const OriginOrPatternString& aOriginOrPattern) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: michael@0: if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) { michael@0: if (aOriginOrPattern.IsOrigin()) { michael@0: mInitializedOrigins.RemoveElement(aOriginOrPattern); michael@0: } michael@0: else { michael@0: for (uint32_t index = mInitializedOrigins.Length(); index > 0; index--) { michael@0: if (PatternMatchesOrigin(aOriginOrPattern, michael@0: mInitializedOrigins[index - 1])) { michael@0: mInitializedOrigins.RemoveElementAt(index - 1); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: for (uint32_t index = 0; index < Client::TYPE_MAX; index++) { michael@0: mClients[index]->OnOriginClearCompleted(aPersistenceType, aOriginOrPattern); michael@0: } michael@0: } michael@0: michael@0: void michael@0: QuotaManager::ResetOrClearCompleted() michael@0: { michael@0: AssertIsOnIOThread(); michael@0: michael@0: mInitializedOrigins.Clear(); michael@0: mTemporaryStorageInitialized = false; michael@0: michael@0: ReleaseIOThreadObjects(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: QuotaManager::GetClient(Client::Type aClientType) michael@0: { michael@0: nsRefPtr client = mClients.SafeElementAt(aClientType); michael@0: return client.forget(); michael@0: } michael@0: michael@0: uint64_t michael@0: QuotaManager::GetGroupLimit() const michael@0: { michael@0: MOZ_ASSERT(mTemporaryStorageInitialized); michael@0: michael@0: // To avoid one group evicting all the rest, limit the amount any one group michael@0: // can use to 20%. To prevent individual sites from using exorbitant amounts michael@0: // of storage where there is a lot of free space, cap the group limit to 2GB. michael@0: uint64_t x = std::min(mTemporaryStorageLimit * .20, 2 GB); michael@0: michael@0: // In low-storage situations, make an exception (while not exceeding the total michael@0: // storage limit). michael@0: return std::min(mTemporaryStorageLimit, michael@0: std::max(x, 10 MB)); michael@0: } michael@0: michael@0: // static michael@0: uint32_t michael@0: QuotaManager::GetStorageQuotaMB() michael@0: { michael@0: return uint32_t(std::max(gStorageQuotaMB, 0)); michael@0: } michael@0: michael@0: // static michael@0: void michael@0: QuotaManager::GetStorageId(PersistenceType aPersistenceType, michael@0: const nsACString& aOrigin, michael@0: Client::Type aClientType, michael@0: const nsAString& aName, michael@0: nsACString& aDatabaseId) michael@0: { michael@0: nsAutoCString str; michael@0: str.AppendInt(aPersistenceType); michael@0: str.Append('*'); michael@0: str.Append(aOrigin); michael@0: str.Append('*'); michael@0: str.AppendInt(aClientType); michael@0: str.Append('*'); michael@0: str.Append(NS_ConvertUTF16toUTF8(aName)); michael@0: michael@0: aDatabaseId = str; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: QuotaManager::GetInfoFromURI(nsIURI* aURI, michael@0: uint32_t aAppId, michael@0: bool aInMozBrowser, michael@0: nsACString* aGroup, michael@0: nsACString* aASCIIOrigin, michael@0: StoragePrivilege* aPrivilege, michael@0: PersistenceType* aDefaultPersistenceType) michael@0: { michael@0: NS_ASSERTION(aURI, "Null uri!"); michael@0: michael@0: nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); michael@0: NS_ENSURE_TRUE(secMan, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMPtr principal; michael@0: nsresult rv = secMan->GetAppCodebasePrincipal(aURI, aAppId, aInMozBrowser, michael@0: getter_AddRefs(principal)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = GetInfoFromPrincipal(principal, aGroup, aASCIIOrigin, aPrivilege, michael@0: aDefaultPersistenceType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: QuotaManager::GetInfoFromPrincipal(nsIPrincipal* aPrincipal, michael@0: nsACString* aGroup, michael@0: nsACString* aASCIIOrigin, michael@0: StoragePrivilege* aPrivilege, michael@0: PersistenceType* aDefaultPersistenceType) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(aPrincipal, "Don't hand me a null principal!"); michael@0: michael@0: if (nsContentUtils::IsSystemPrincipal(aPrincipal)) { michael@0: GetInfoForChrome(aGroup, aASCIIOrigin, aPrivilege, aDefaultPersistenceType); michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool isNullPrincipal; michael@0: nsresult rv = aPrincipal->GetIsNullPrincipal(&isNullPrincipal); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (isNullPrincipal) { michael@0: NS_WARNING("IndexedDB not supported from this principal!"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsCString origin; michael@0: rv = aPrincipal->GetOrigin(getter_Copies(origin)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (origin.EqualsLiteral("chrome")) { michael@0: NS_WARNING("Non-chrome principal can't use chrome origin!"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsCString jarPrefix; michael@0: if (aGroup || aASCIIOrigin) { michael@0: rv = aPrincipal->GetJarPrefix(jarPrefix); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: if (aGroup) { michael@0: nsCString baseDomain; michael@0: rv = aPrincipal->GetBaseDomain(baseDomain); michael@0: if (NS_FAILED(rv)) { michael@0: // A hack for JetPack. michael@0: michael@0: nsCOMPtr uri; michael@0: rv = aPrincipal->GetURI(getter_AddRefs(uri)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool isIndexedDBURI = false; michael@0: rv = uri->SchemeIs("indexedDB", &isIndexedDBURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (isIndexedDBURI) { michael@0: rv = NS_OK; michael@0: } michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (baseDomain.IsEmpty()) { michael@0: aGroup->Assign(jarPrefix + origin); michael@0: } michael@0: else { michael@0: aGroup->Assign(jarPrefix + baseDomain); michael@0: } michael@0: } michael@0: michael@0: if (aASCIIOrigin) { michael@0: aASCIIOrigin->Assign(jarPrefix + origin); michael@0: } michael@0: michael@0: if (aPrivilege) { michael@0: *aPrivilege = Content; michael@0: } michael@0: michael@0: if (aDefaultPersistenceType) { michael@0: *aDefaultPersistenceType = PERSISTENCE_TYPE_PERSISTENT; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: QuotaManager::GetInfoFromWindow(nsPIDOMWindow* aWindow, michael@0: nsACString* aGroup, michael@0: nsACString* aASCIIOrigin, michael@0: StoragePrivilege* aPrivilege, michael@0: PersistenceType* aDefaultPersistenceType) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), michael@0: "We're about to touch a window off the main thread!"); michael@0: NS_ASSERTION(aWindow, "Don't hand me a null window!"); michael@0: michael@0: nsCOMPtr sop = do_QueryInterface(aWindow); michael@0: NS_ENSURE_TRUE(sop, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMPtr principal = sop->GetPrincipal(); michael@0: NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE); michael@0: michael@0: nsresult rv = GetInfoFromPrincipal(principal, aGroup, aASCIIOrigin, michael@0: aPrivilege, aDefaultPersistenceType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: QuotaManager::GetInfoForChrome(nsACString* aGroup, michael@0: nsACString* aASCIIOrigin, michael@0: StoragePrivilege* aPrivilege, michael@0: PersistenceType* aDefaultPersistenceType) michael@0: { michael@0: NS_ASSERTION(nsContentUtils::IsCallerChrome(), "Only for chrome!"); michael@0: michael@0: static const char kChromeOrigin[] = "chrome"; michael@0: michael@0: if (aGroup) { michael@0: aGroup->AssignLiteral(kChromeOrigin); michael@0: } michael@0: if (aASCIIOrigin) { michael@0: aASCIIOrigin->AssignLiteral(kChromeOrigin); michael@0: } michael@0: if (aPrivilege) { michael@0: *aPrivilege = Chrome; michael@0: } michael@0: if (aDefaultPersistenceType) { michael@0: *aDefaultPersistenceType = PERSISTENCE_TYPE_PERSISTENT; michael@0: } michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(QuotaManager, nsIQuotaManager, nsIObserver) michael@0: michael@0: NS_IMETHODIMP michael@0: QuotaManager::GetUsageForURI(nsIURI* aURI, michael@0: nsIUsageCallback* aCallback, michael@0: uint32_t aAppId, michael@0: bool aInMozBrowserOnly, michael@0: uint8_t aOptionalArgCount, michael@0: nsIQuotaRequest** _retval) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: NS_ENSURE_ARG_POINTER(aURI); michael@0: NS_ENSURE_ARG_POINTER(aCallback); michael@0: michael@0: // This only works from the main process. michael@0: NS_ENSURE_TRUE(IsMainProcess(), NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: if (!aOptionalArgCount) { michael@0: aAppId = nsIScriptSecurityManager::NO_APP_ID; michael@0: } michael@0: michael@0: // Figure out which origin we're dealing with. michael@0: nsCString group; michael@0: nsCString origin; michael@0: nsresult rv = GetInfoFromURI(aURI, aAppId, aInMozBrowserOnly, &group, &origin, michael@0: nullptr, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: OriginOrPatternString oops = OriginOrPatternString::FromOrigin(origin); michael@0: michael@0: nsRefPtr runnable = michael@0: new AsyncUsageRunnable(aAppId, aInMozBrowserOnly, group, oops, aURI, michael@0: aCallback); michael@0: michael@0: // Put the computation runnable in the queue. michael@0: rv = WaitForOpenAllowed(oops, Nullable(), EmptyCString(), michael@0: runnable); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: runnable->AdvanceState(); michael@0: michael@0: runnable.forget(_retval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: QuotaManager::Clear() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: if (!gTestingEnabled) { michael@0: NS_WARNING("Testing features are not enabled!"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: OriginOrPatternString oops = OriginOrPatternString::FromNull(); michael@0: michael@0: nsRefPtr runnable = new ResetOrClearRunnable(true); michael@0: michael@0: // Put the clear runnable in the queue. michael@0: nsresult rv = michael@0: WaitForOpenAllowed(oops, Nullable(), EmptyCString(), michael@0: runnable); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: runnable->AdvanceState(); michael@0: michael@0: // Give the runnable some help by invalidating any storages in the way. michael@0: StorageMatcher > matches; michael@0: matches.Find(mLiveStorages); michael@0: michael@0: for (uint32_t index = 0; index < matches.Length(); index++) { michael@0: // We need to grab references to any live storages here to prevent them michael@0: // from dying while we invalidate them. michael@0: nsCOMPtr storage = matches[index]; michael@0: storage->Invalidate(); michael@0: } michael@0: michael@0: // After everything has been invalidated the helper should be dispatched to michael@0: // the end of the event queue. michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: QuotaManager::ClearStoragesForURI(nsIURI* aURI, michael@0: uint32_t aAppId, michael@0: bool aInMozBrowserOnly, michael@0: const nsACString& aPersistenceType, michael@0: uint8_t aOptionalArgCount) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: NS_ENSURE_ARG_POINTER(aURI); michael@0: michael@0: // This only works from the main process. michael@0: NS_ENSURE_TRUE(IsMainProcess(), NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: if (!aOptionalArgCount) { michael@0: aAppId = nsIScriptSecurityManager::NO_APP_ID; michael@0: } michael@0: michael@0: // Figure out which origin we're dealing with. michael@0: nsCString origin; michael@0: nsresult rv = GetInfoFromURI(aURI, aAppId, aInMozBrowserOnly, nullptr, &origin, michael@0: nullptr, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoCString pattern; michael@0: GetOriginPatternString(aAppId, aInMozBrowserOnly, origin, pattern); michael@0: michael@0: Nullable persistenceType; michael@0: rv = NullablePersistenceTypeFromText(aPersistenceType, &persistenceType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // If there is a pending or running clear operation for this origin, return michael@0: // immediately. michael@0: if (IsClearOriginPending(pattern, persistenceType)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: OriginOrPatternString oops = OriginOrPatternString::FromPattern(pattern); michael@0: michael@0: // Queue up the origin clear runnable. michael@0: nsRefPtr runnable = michael@0: new OriginClearRunnable(oops, persistenceType); michael@0: michael@0: rv = WaitForOpenAllowed(oops, persistenceType, EmptyCString(), runnable); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: runnable->AdvanceState(); michael@0: michael@0: // Give the runnable some help by invalidating any storages in the way. michael@0: StorageMatcher > matches; michael@0: matches.Find(mLiveStorages, pattern); michael@0: michael@0: for (uint32_t index = 0; index < matches.Length(); index++) { michael@0: if (persistenceType.IsNull() || michael@0: matches[index]->Type() == persistenceType.Value()) { michael@0: // We need to grab references to any live storages here to prevent them michael@0: // from dying while we invalidate them. michael@0: nsCOMPtr storage = matches[index]; michael@0: storage->Invalidate(); michael@0: } michael@0: } michael@0: michael@0: // After everything has been invalidated the helper should be dispatched to michael@0: // the end of the event queue. michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: QuotaManager::Reset() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: if (!gTestingEnabled) { michael@0: NS_WARNING("Testing features are not enabled!"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: OriginOrPatternString oops = OriginOrPatternString::FromNull(); michael@0: michael@0: nsRefPtr runnable = new ResetOrClearRunnable(false); michael@0: michael@0: // Put the reset runnable in the queue. michael@0: nsresult rv = michael@0: WaitForOpenAllowed(oops, Nullable(), EmptyCString(), michael@0: runnable); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: runnable->AdvanceState(); michael@0: michael@0: // Give the runnable some help by invalidating any storages in the way. michael@0: StorageMatcher > matches; michael@0: matches.Find(mLiveStorages); michael@0: michael@0: for (uint32_t index = 0; index < matches.Length(); index++) { michael@0: // We need to grab references to any live storages here to prevent them michael@0: // from dying while we invalidate them. michael@0: nsCOMPtr storage = matches[index]; michael@0: storage->Invalidate(); michael@0: } michael@0: michael@0: // After everything has been invalidated the helper should be dispatched to michael@0: // the end of the event queue. michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: QuotaManager::Observe(nsISupports* aSubject, michael@0: const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: if (!strcmp(aTopic, PROFILE_BEFORE_CHANGE_OBSERVER_ID)) { michael@0: // Setting this flag prevents the service from being recreated and prevents michael@0: // further storagess from being created. michael@0: if (gShutdown.exchange(true)) { michael@0: NS_ERROR("Shutdown more than once?!"); michael@0: } michael@0: michael@0: if (IsMainProcess()) { michael@0: FileService* service = FileService::Get(); michael@0: if (service) { michael@0: // This should only wait for storages registered in this manager michael@0: // to complete. Other storages may still have running locked files. michael@0: // If the necko service (thread pool) gets the shutdown notification michael@0: // first then the sync loop won't be processed at all, otherwise it will michael@0: // lock the main thread until all storages registered in this manager michael@0: // are finished. michael@0: michael@0: nsTArray indexes; michael@0: for (uint32_t index = 0; index < Client::TYPE_MAX; index++) { michael@0: if (mClients[index]->IsFileServiceUtilized()) { michael@0: indexes.AppendElement(index); michael@0: } michael@0: } michael@0: michael@0: StorageMatcher > > liveStorages; michael@0: liveStorages.Find(mLiveStorages, &indexes); michael@0: michael@0: if (!liveStorages.IsEmpty()) { michael@0: nsRefPtr runnable = michael@0: new WaitForLockedFilesToFinishRunnable(); michael@0: michael@0: service->WaitForStoragesToComplete(liveStorages, runnable); michael@0: michael@0: nsIThread* thread = NS_GetCurrentThread(); michael@0: while (runnable->IsBusy()) { michael@0: if (!NS_ProcessNextEvent(thread)) { michael@0: NS_ERROR("Failed to process next event!"); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Kick off the shutdown timer. michael@0: if (NS_FAILED(mShutdownTimer->Init(this, DEFAULT_SHUTDOWN_TIMER_MS, michael@0: nsITimer::TYPE_ONE_SHOT))) { michael@0: NS_WARNING("Failed to initialize shutdown timer!"); michael@0: } michael@0: michael@0: // Each client will spin the event loop while we wait on all the threads michael@0: // to close. Our timer may fire during that loop. michael@0: for (uint32_t index = 0; index < Client::TYPE_MAX; index++) { michael@0: mClients[index]->ShutdownTransactionService(); michael@0: } michael@0: michael@0: // Cancel the timer regardless of whether it actually fired. michael@0: if (NS_FAILED(mShutdownTimer->Cancel())) { michael@0: NS_WARNING("Failed to cancel shutdown timer!"); michael@0: } michael@0: michael@0: // Give clients a chance to cleanup IO thread only objects. michael@0: nsCOMPtr runnable = michael@0: NS_NewRunnableMethod(this, &QuotaManager::ReleaseIOThreadObjects); michael@0: if (!runnable) { michael@0: NS_WARNING("Failed to create runnable!"); michael@0: } michael@0: michael@0: if (NS_FAILED(mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL))) { michael@0: NS_WARNING("Failed to dispatch runnable!"); michael@0: } michael@0: michael@0: // Make sure to join with our IO thread. michael@0: if (NS_FAILED(mIOThread->Shutdown())) { michael@0: NS_WARNING("Failed to shutdown IO thread!"); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC)) { michael@0: NS_ASSERTION(IsMainProcess(), "Should only happen in the main process!"); michael@0: michael@0: NS_WARNING("Some storage operations are taking longer than expected " michael@0: "during shutdown and will be aborted!"); michael@0: michael@0: // Grab all live storages, for all origins. michael@0: StorageMatcher > liveStorages; michael@0: liveStorages.Find(mLiveStorages); michael@0: michael@0: // Invalidate them all. michael@0: if (!liveStorages.IsEmpty()) { michael@0: uint32_t count = liveStorages.Length(); michael@0: for (uint32_t index = 0; index < count; index++) { michael@0: liveStorages[index]->Invalidate(); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!strcmp(aTopic, TOPIC_WEB_APP_CLEAR_DATA)) { michael@0: nsCOMPtr params = michael@0: do_QueryInterface(aSubject); michael@0: NS_ENSURE_TRUE(params, NS_ERROR_UNEXPECTED); michael@0: michael@0: uint32_t appId; michael@0: nsresult rv = params->GetAppId(&appId); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool browserOnly; michael@0: rv = params->GetBrowserOnly(&browserOnly); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = ClearStoragesForApp(appId, browserOnly); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_NOTREACHED("Unknown topic!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: void michael@0: QuotaManager::SetCurrentWindowInternal(nsPIDOMWindow* aWindow) michael@0: { michael@0: NS_ASSERTION(mCurrentWindowIndex != BAD_TLS_INDEX, michael@0: "Should have a valid TLS storage index!"); michael@0: michael@0: if (aWindow) { michael@0: NS_ASSERTION(!PR_GetThreadPrivate(mCurrentWindowIndex), michael@0: "Somebody forgot to clear the current window!"); michael@0: PR_SetThreadPrivate(mCurrentWindowIndex, aWindow); michael@0: } michael@0: else { michael@0: // We cannot assert PR_GetThreadPrivate(mCurrentWindowIndex) here because michael@0: // there are some cases where we did not already have a window. michael@0: PR_SetThreadPrivate(mCurrentWindowIndex, nullptr); michael@0: } michael@0: } michael@0: michael@0: void michael@0: QuotaManager::CancelPromptsForWindowInternal(nsPIDOMWindow* aWindow) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: nsRefPtr helper; michael@0: michael@0: MutexAutoLock autoLock(mQuotaMutex); michael@0: michael@0: if (mCheckQuotaHelpers.Get(aWindow, getter_AddRefs(helper))) { michael@0: helper->Cancel(); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: QuotaManager::LockedQuotaIsLifted() michael@0: { michael@0: mQuotaMutex.AssertCurrentThreadOwns(); michael@0: michael@0: NS_ASSERTION(mCurrentWindowIndex != BAD_TLS_INDEX, michael@0: "Should have a valid TLS storage index!"); michael@0: michael@0: nsPIDOMWindow* window = michael@0: static_cast(PR_GetThreadPrivate(mCurrentWindowIndex)); michael@0: michael@0: // Quota is not enforced in chrome contexts (e.g. for components and JSMs) michael@0: // so we must have a window here. michael@0: NS_ASSERTION(window, "Why don't we have a Window here?"); michael@0: michael@0: bool createdHelper = false; michael@0: michael@0: nsRefPtr helper; michael@0: if (!mCheckQuotaHelpers.Get(window, getter_AddRefs(helper))) { michael@0: helper = new CheckQuotaHelper(window, mQuotaMutex); michael@0: createdHelper = true; michael@0: michael@0: mCheckQuotaHelpers.Put(window, helper); michael@0: michael@0: // Unlock while calling out to XPCOM (code behind the dispatch method needs michael@0: // to acquire its own lock which can potentially lead to a deadlock and it michael@0: // also calls an observer that can do various stuff like IO, so it's better michael@0: // to not hold our mutex while that happens). michael@0: { michael@0: MutexAutoUnlock autoUnlock(mQuotaMutex); michael@0: michael@0: nsresult rv = NS_DispatchToMainThread(helper); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: } michael@0: michael@0: // Relocked. If any other threads hit the quota limit on the same Window, michael@0: // they are using the helper we created here and are now blocking in michael@0: // PromptAndReturnQuotaDisabled. michael@0: } michael@0: michael@0: bool result = helper->PromptAndReturnQuotaIsDisabled(); michael@0: michael@0: // If this thread created the helper and added it to the hash, this thread michael@0: // must remove it. michael@0: if (createdHelper) { michael@0: mCheckQuotaHelpers.Remove(window); michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: uint64_t michael@0: QuotaManager::LockedCollectOriginsForEviction( michael@0: uint64_t aMinSizeToBeFreed, michael@0: nsTArray& aOriginInfos) michael@0: { michael@0: mQuotaMutex.AssertCurrentThreadOwns(); michael@0: michael@0: nsRefPtr helper = michael@0: new CollectOriginsHelper(mQuotaMutex, aMinSizeToBeFreed); michael@0: michael@0: // Unlock while calling out to XPCOM (see the detailed comment in michael@0: // LockedQuotaIsLifted) michael@0: { michael@0: MutexAutoUnlock autoUnlock(mQuotaMutex); michael@0: michael@0: if (NS_FAILED(NS_DispatchToMainThread(helper))) { michael@0: NS_WARNING("Failed to dispatch to the main thread!"); michael@0: } michael@0: } michael@0: michael@0: return helper->BlockAndReturnOriginsForEviction(aOriginInfos); michael@0: } michael@0: michael@0: void michael@0: QuotaManager::LockedRemoveQuotaForOrigin(PersistenceType aPersistenceType, michael@0: const nsACString& aGroup, michael@0: const nsACString& aOrigin) michael@0: { michael@0: mQuotaMutex.AssertCurrentThreadOwns(); michael@0: michael@0: GroupInfoPair* pair; michael@0: mGroupInfoPairs.Get(aGroup, &pair); michael@0: michael@0: if (!pair) { michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr groupInfo = pair->LockedGetGroupInfo(aPersistenceType); michael@0: if (groupInfo) { michael@0: groupInfo->LockedRemoveOriginInfo(aOrigin); michael@0: michael@0: if (!groupInfo->LockedHasOriginInfos()) { michael@0: pair->LockedClearGroupInfo(aPersistenceType); michael@0: michael@0: if (!pair->LockedHasGroupInfos()) { michael@0: mGroupInfoPairs.Remove(aGroup); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: QuotaManager::AcquireExclusiveAccess(const nsACString& aPattern, michael@0: Nullable aPersistenceType, michael@0: nsIOfflineStorage* aStorage, michael@0: AcquireListener* aListener, michael@0: WaitingOnStoragesCallback aCallback, michael@0: void* aClosure) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(aListener, "Need a listener!"); michael@0: michael@0: // Find the right SynchronizedOp. michael@0: SynchronizedOp* op = michael@0: FindSynchronizedOp(aPattern, aPersistenceType, michael@0: aStorage ? aStorage->Id() : EmptyCString()); michael@0: michael@0: NS_ASSERTION(op, "We didn't find a SynchronizedOp?"); michael@0: NS_ASSERTION(!op->mListener, "SynchronizedOp already has a listener?!?"); michael@0: michael@0: nsTArray > liveStorages; michael@0: michael@0: if (aStorage) { michael@0: // We need to wait for the storages to go away. michael@0: // Hold on to all storage objects that represent the same storage file michael@0: // (except the one that is requesting this version change). michael@0: michael@0: Client::Type clientType = aStorage->GetClient()->GetType(); michael@0: michael@0: StorageMatcher > matches; michael@0: matches.Find(mLiveStorages, aPattern, clientType); michael@0: michael@0: if (!matches.IsEmpty()) { michael@0: // Grab all storages that are not yet closed but whose storage id match michael@0: // the one we're looking for. michael@0: for (uint32_t index = 0; index < matches.Length(); index++) { michael@0: nsIOfflineStorage*& storage = matches[index]; michael@0: if (!storage->IsClosed() && michael@0: storage != aStorage && michael@0: storage->Id() == aStorage->Id()) { michael@0: liveStorages.AppendElement(storage); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!liveStorages.IsEmpty()) { michael@0: NS_ASSERTION(op->mStorages[clientType].IsEmpty(), michael@0: "How do we already have storages here?"); michael@0: op->mStorages[clientType].AppendElements(liveStorages); michael@0: } michael@0: } michael@0: else { michael@0: StorageMatcher > matches; michael@0: if (aPattern.IsVoid()) { michael@0: matches.Find(mLiveStorages); michael@0: } michael@0: else { michael@0: matches.Find(mLiveStorages, aPattern); michael@0: } michael@0: michael@0: if (!matches.IsEmpty()) { michael@0: // We want *all* storages, even those that are closed, when we're going to michael@0: // clear the origin. michael@0: matches.AppendElementsTo(liveStorages); michael@0: michael@0: NS_ASSERTION(op->mStorages.IsEmpty(), michael@0: "How do we already have storages here?"); michael@0: matches.SwapElements(op->mStorages); michael@0: } michael@0: } michael@0: michael@0: op->mListener = aListener; michael@0: michael@0: if (!liveStorages.IsEmpty()) { michael@0: // Give our callback the storages so it can decide what to do with them. michael@0: aCallback(liveStorages, aClosure); michael@0: michael@0: NS_ASSERTION(liveStorages.IsEmpty(), michael@0: "Should have done something with the array!"); michael@0: michael@0: if (aStorage) { michael@0: // Wait for those storages to close. michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // If we're trying to open a storage and nothing blocks it, or if we're michael@0: // clearing an origin, then go ahead and schedule the op. michael@0: nsresult rv = RunSynchronizedOp(aStorage, op); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: QuotaManager::RunSynchronizedOp(nsIOfflineStorage* aStorage, michael@0: SynchronizedOp* aOp) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(aOp, "Null pointer!"); michael@0: NS_ASSERTION(aOp->mListener, "No listener on this op!"); michael@0: NS_ASSERTION(!aStorage || michael@0: aOp->mStorages[aStorage->GetClient()->GetType()].IsEmpty(), michael@0: "This op isn't ready to run!"); michael@0: michael@0: ArrayCluster storages; michael@0: michael@0: uint32_t startIndex; michael@0: uint32_t endIndex; michael@0: michael@0: if (aStorage) { michael@0: Client::Type clientType = aStorage->GetClient()->GetType(); michael@0: michael@0: storages[clientType].AppendElement(aStorage); michael@0: michael@0: startIndex = clientType; michael@0: endIndex = clientType + 1; michael@0: } michael@0: else { michael@0: aOp->mStorages.SwapElements(storages); michael@0: michael@0: startIndex = 0; michael@0: endIndex = Client::TYPE_MAX; michael@0: } michael@0: michael@0: nsRefPtr runnable = michael@0: new WaitForTransactionsToFinishRunnable(aOp); michael@0: michael@0: // Ask the file service to call us back when it's done with this storage. michael@0: FileService* service = FileService::Get(); michael@0: michael@0: if (service) { michael@0: // Have to copy here in case a transaction service needs a list too. michael@0: nsTArray > array; michael@0: michael@0: for (uint32_t index = startIndex; index < endIndex; index++) { michael@0: if (!storages[index].IsEmpty() && michael@0: mClients[index]->IsFileServiceUtilized()) { michael@0: array.AppendElements(storages[index]); michael@0: } michael@0: } michael@0: michael@0: if (!array.IsEmpty()) { michael@0: runnable->AddRun(); michael@0: michael@0: service->WaitForStoragesToComplete(array, runnable); michael@0: } michael@0: } michael@0: michael@0: // Ask each transaction service to call us back when they're done with this michael@0: // storage. michael@0: for (uint32_t index = startIndex; index < endIndex; index++) { michael@0: nsRefPtr& client = mClients[index]; michael@0: if (!storages[index].IsEmpty() && client->IsTransactionServiceActivated()) { michael@0: runnable->AddRun(); michael@0: michael@0: client->WaitForStoragesToComplete(storages[index], runnable); michael@0: } michael@0: } michael@0: michael@0: nsresult rv = runnable->Run(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: SynchronizedOp* michael@0: QuotaManager::FindSynchronizedOp(const nsACString& aPattern, michael@0: Nullable aPersistenceType, michael@0: const nsACString& aId) michael@0: { michael@0: for (uint32_t index = 0; index < mSynchronizedOps.Length(); index++) { michael@0: const nsAutoPtr& currentOp = mSynchronizedOps[index]; michael@0: if (PatternMatchesOrigin(aPattern, currentOp->mOriginOrPattern) && michael@0: (currentOp->mPersistenceType.IsNull() || michael@0: currentOp->mPersistenceType == aPersistenceType) && michael@0: (currentOp->mId.IsEmpty() || currentOp->mId == aId)) { michael@0: return currentOp; michael@0: } michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: nsresult michael@0: QuotaManager::ClearStoragesForApp(uint32_t aAppId, bool aBrowserOnly) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID, michael@0: "Bad appId!"); michael@0: michael@0: // This only works from the main process. michael@0: NS_ENSURE_TRUE(IsMainProcess(), NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: nsAutoCString pattern; michael@0: GetOriginPatternStringMaybeIgnoreBrowser(aAppId, aBrowserOnly, pattern); michael@0: michael@0: // Clear both temporary and persistent storages. michael@0: Nullable persistenceType; michael@0: michael@0: // If there is a pending or running clear operation for this app, return michael@0: // immediately. michael@0: if (IsClearOriginPending(pattern, persistenceType)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: OriginOrPatternString oops = OriginOrPatternString::FromPattern(pattern); michael@0: michael@0: // Queue up the origin clear runnable. michael@0: nsRefPtr runnable = michael@0: new OriginClearRunnable(oops, persistenceType); michael@0: michael@0: nsresult rv = michael@0: WaitForOpenAllowed(oops, persistenceType, EmptyCString(), runnable); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: runnable->AdvanceState(); michael@0: michael@0: // Give the runnable some help by invalidating any storages in the way. michael@0: StorageMatcher > matches; michael@0: matches.Find(mLiveStorages, pattern); michael@0: michael@0: for (uint32_t index = 0; index < matches.Length(); index++) { michael@0: // We need to grab references here to prevent the storage from dying while michael@0: // we invalidate it. michael@0: nsCOMPtr storage = matches[index]; michael@0: storage->Invalidate(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: PLDHashOperator michael@0: QuotaManager::GetOriginsExceedingGroupLimit(const nsACString& aKey, michael@0: GroupInfoPair* aValue, michael@0: void* aUserArg) michael@0: { michael@0: NS_ASSERTION(!aKey.IsEmpty(), "Empty key!"); michael@0: NS_ASSERTION(aValue, "Null pointer!"); michael@0: michael@0: nsRefPtr groupInfo = michael@0: aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY); michael@0: if (groupInfo) { michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "Shouldn't be null!"); michael@0: michael@0: if (groupInfo->mUsage > quotaManager->GetGroupLimit()) { michael@0: nsTArray* doomedOriginInfos = michael@0: static_cast*>(aUserArg); michael@0: michael@0: nsTArray >& originInfos = groupInfo->mOriginInfos; michael@0: originInfos.Sort(OriginInfoLRUComparator()); michael@0: michael@0: uint64_t usage = groupInfo->mUsage; michael@0: for (uint32_t i = 0; i < originInfos.Length(); i++) { michael@0: OriginInfo* originInfo = originInfos[i]; michael@0: michael@0: doomedOriginInfos->AppendElement(originInfo); michael@0: usage -= originInfo->mUsage; michael@0: michael@0: if (usage <= quotaManager->GetGroupLimit()) { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: // static michael@0: PLDHashOperator michael@0: QuotaManager::GetAllTemporaryStorageOrigins(const nsACString& aKey, michael@0: GroupInfoPair* aValue, michael@0: void* aUserArg) michael@0: { michael@0: NS_ASSERTION(!aKey.IsEmpty(), "Empty key!"); michael@0: NS_ASSERTION(aValue, "Null pointer!"); michael@0: michael@0: nsRefPtr groupInfo = michael@0: aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY); michael@0: if (groupInfo) { michael@0: nsTArray* originInfos = michael@0: static_cast*>(aUserArg); michael@0: michael@0: originInfos->AppendElements(groupInfo->mOriginInfos); michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: QuotaManager::CheckTemporaryStorageLimits() michael@0: { michael@0: AssertIsOnIOThread(); michael@0: michael@0: nsTArray doomedOriginInfos; michael@0: { michael@0: MutexAutoLock lock(mQuotaMutex); michael@0: michael@0: mGroupInfoPairs.EnumerateRead(GetOriginsExceedingGroupLimit, michael@0: &doomedOriginInfos); michael@0: michael@0: uint64_t usage = 0; michael@0: for (uint32_t index = 0; index < doomedOriginInfos.Length(); index++) { michael@0: usage += doomedOriginInfos[index]->mUsage; michael@0: } michael@0: michael@0: if (mTemporaryStorageUsage - usage > mTemporaryStorageLimit) { michael@0: nsTArray originInfos; michael@0: michael@0: mGroupInfoPairs.EnumerateRead(GetAllTemporaryStorageOrigins, michael@0: &originInfos); michael@0: michael@0: for (uint32_t index = originInfos.Length(); index > 0; index--) { michael@0: if (doomedOriginInfos.Contains(originInfos[index - 1])) { michael@0: originInfos.RemoveElementAt(index - 1); michael@0: } michael@0: } michael@0: michael@0: originInfos.Sort(OriginInfoLRUComparator()); michael@0: michael@0: for (uint32_t i = 0; i < originInfos.Length(); i++) { michael@0: if (mTemporaryStorageUsage - usage <= mTemporaryStorageLimit) { michael@0: originInfos.TruncateLength(i); michael@0: break; michael@0: } michael@0: michael@0: usage += originInfos[i]->mUsage; michael@0: } michael@0: michael@0: doomedOriginInfos.AppendElements(originInfos); michael@0: } michael@0: } michael@0: michael@0: for (uint32_t index = 0; index < doomedOriginInfos.Length(); index++) { michael@0: DeleteTemporaryFilesForOrigin(doomedOriginInfos[index]->mOrigin); michael@0: } michael@0: michael@0: nsTArray doomedOrigins; michael@0: { michael@0: MutexAutoLock lock(mQuotaMutex); michael@0: michael@0: for (uint32_t index = 0; index < doomedOriginInfos.Length(); index++) { michael@0: OriginInfo* doomedOriginInfo = doomedOriginInfos[index]; michael@0: michael@0: nsCString group = doomedOriginInfo->mGroupInfo->mGroup; michael@0: nsCString origin = doomedOriginInfo->mOrigin; michael@0: LockedRemoveQuotaForOrigin(PERSISTENCE_TYPE_TEMPORARY, group, origin); michael@0: michael@0: #ifdef DEBUG michael@0: doomedOriginInfos[index] = nullptr; michael@0: #endif michael@0: michael@0: doomedOrigins.AppendElement(origin); michael@0: } michael@0: } michael@0: michael@0: for (uint32_t index = 0; index < doomedOrigins.Length(); index++) { michael@0: OriginClearCompleted( michael@0: PERSISTENCE_TYPE_TEMPORARY, michael@0: OriginOrPatternString::FromOrigin(doomedOrigins[index])); michael@0: } michael@0: } michael@0: michael@0: // static michael@0: PLDHashOperator michael@0: QuotaManager::AddTemporaryStorageOrigins( michael@0: const nsACString& aKey, michael@0: ArrayCluster* aValue, michael@0: void* aUserArg) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(!aKey.IsEmpty(), "Empty key!"); michael@0: NS_ASSERTION(aValue, "Null pointer!"); michael@0: NS_ASSERTION(aUserArg, "Null pointer!"); michael@0: michael@0: OriginCollection& collection = *static_cast(aUserArg); michael@0: michael@0: if (collection.ContainsOrigin(aKey)) { michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < Client::TYPE_MAX; i++) { michael@0: nsTArray& array = (*aValue)[i]; michael@0: for (uint32_t j = 0; j < array.Length(); j++) { michael@0: nsIOfflineStorage*& storage = array[j]; michael@0: if (storage->Type() == PERSISTENCE_TYPE_TEMPORARY) { michael@0: collection.AddOrigin(aKey); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: // static michael@0: PLDHashOperator michael@0: QuotaManager::GetInactiveTemporaryStorageOrigins(const nsACString& aKey, michael@0: GroupInfoPair* aValue, michael@0: void* aUserArg) michael@0: { michael@0: NS_ASSERTION(!aKey.IsEmpty(), "Empty key!"); michael@0: NS_ASSERTION(aValue, "Null pointer!"); michael@0: NS_ASSERTION(aUserArg, "Null pointer!"); michael@0: michael@0: nsRefPtr groupInfo = michael@0: aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY); michael@0: if (groupInfo) { michael@0: InactiveOriginsInfo* info = static_cast(aUserArg); michael@0: michael@0: nsTArray >& originInfos = groupInfo->mOriginInfos; michael@0: michael@0: for (uint32_t i = 0; i < originInfos.Length(); i++) { michael@0: OriginInfo* originInfo = originInfos[i]; michael@0: michael@0: if (!info->collection.ContainsOrigin(originInfo->mOrigin)) { michael@0: NS_ASSERTION(!originInfo->mQuotaObjects.Count(), michael@0: "Inactive origin shouldn't have open files!"); michael@0: info->origins.AppendElement(originInfo); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: uint64_t michael@0: QuotaManager::CollectOriginsForEviction(uint64_t aMinSizeToBeFreed, michael@0: nsTArray& aOriginInfos) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: // Collect active origins first. michael@0: OriginCollection originCollection; michael@0: michael@0: // Add patterns and origins that have running or pending synchronized ops. michael@0: // (add patterns first to reduce redundancy in the origin collection). michael@0: uint32_t index; michael@0: for (index = 0; index < mSynchronizedOps.Length(); index++) { michael@0: nsAutoPtr& op = mSynchronizedOps[index]; michael@0: if (op->mPersistenceType.IsNull() || michael@0: op->mPersistenceType.Value() == PERSISTENCE_TYPE_TEMPORARY) { michael@0: if (op->mOriginOrPattern.IsPattern() && michael@0: !originCollection.ContainsPattern(op->mOriginOrPattern)) { michael@0: originCollection.AddPattern(op->mOriginOrPattern); michael@0: } michael@0: } michael@0: } michael@0: michael@0: for (index = 0; index < mSynchronizedOps.Length(); index++) { michael@0: nsAutoPtr& op = mSynchronizedOps[index]; michael@0: if (op->mPersistenceType.IsNull() || michael@0: op->mPersistenceType.Value() == PERSISTENCE_TYPE_TEMPORARY) { michael@0: if (op->mOriginOrPattern.IsOrigin() && michael@0: !originCollection.ContainsOrigin(op->mOriginOrPattern)) { michael@0: originCollection.AddOrigin(op->mOriginOrPattern); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Add origins that have live temporary storages. michael@0: mLiveStorages.EnumerateRead(AddTemporaryStorageOrigins, &originCollection); michael@0: michael@0: // Enumerate inactive origins. This must be protected by the mutex. michael@0: nsTArray inactiveOrigins; michael@0: { michael@0: InactiveOriginsInfo info(originCollection, inactiveOrigins); michael@0: MutexAutoLock lock(mQuotaMutex); michael@0: mGroupInfoPairs.EnumerateRead(GetInactiveTemporaryStorageOrigins, &info); michael@0: } michael@0: michael@0: // We now have a list of all inactive origins. So it's safe to sort the list michael@0: // and calculate available size without holding the lock. michael@0: michael@0: // Sort by the origin access time. michael@0: inactiveOrigins.Sort(OriginInfoLRUComparator()); michael@0: michael@0: // Create a list of inactive and the least recently used origins michael@0: // whose aggregate size is greater or equals the minimal size to be freed. michael@0: uint64_t sizeToBeFreed = 0; michael@0: for(index = 0; index < inactiveOrigins.Length(); index++) { michael@0: if (sizeToBeFreed >= aMinSizeToBeFreed) { michael@0: inactiveOrigins.TruncateLength(index); michael@0: break; michael@0: } michael@0: michael@0: sizeToBeFreed += inactiveOrigins[index]->mUsage; michael@0: } michael@0: michael@0: if (sizeToBeFreed >= aMinSizeToBeFreed) { michael@0: // Success, add synchronized ops for these origins, so any other michael@0: // operations for them will be delayed (until origin eviction is finalized). michael@0: michael@0: for(index = 0; index < inactiveOrigins.Length(); index++) { michael@0: OriginOrPatternString oops = michael@0: OriginOrPatternString::FromOrigin(inactiveOrigins[index]->mOrigin); michael@0: michael@0: AddSynchronizedOp(oops, michael@0: Nullable(PERSISTENCE_TYPE_TEMPORARY)); michael@0: } michael@0: michael@0: inactiveOrigins.SwapElements(aOriginInfos); michael@0: return sizeToBeFreed; michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: void michael@0: QuotaManager::DeleteTemporaryFilesForOrigin(const nsACString& aOrigin) michael@0: { michael@0: nsCOMPtr directory; michael@0: nsresult rv = GetDirectoryForOrigin(PERSISTENCE_TYPE_TEMPORARY, aOrigin, michael@0: getter_AddRefs(directory)); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: michael@0: rv = directory->Remove(true); michael@0: if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST && michael@0: rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) { michael@0: // This should never fail if we've closed all storage connections michael@0: // correctly... michael@0: NS_ERROR("Failed to remove directory!"); michael@0: } michael@0: } michael@0: michael@0: void michael@0: QuotaManager::FinalizeOriginEviction(nsTArray& aOrigins) michael@0: { michael@0: NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: nsRefPtr runnable = michael@0: new FinalizeOriginEvictionRunnable(aOrigins); michael@0: michael@0: nsresult rv = IsOnIOThread() ? runnable->RunImmediately() michael@0: : runnable->Dispatch(); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: } michael@0: michael@0: void michael@0: QuotaManager::SaveOriginAccessTime(const nsACString& aOrigin, michael@0: int64_t aTimestamp) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: if (QuotaManager::IsShuttingDown()) { michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr runnable = michael@0: new SaveOriginAccessTimeRunnable(aOrigin, aTimestamp); michael@0: michael@0: if (NS_FAILED(mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL))) { michael@0: NS_WARNING("Failed to dispatch runnable!"); michael@0: } michael@0: } michael@0: michael@0: void michael@0: QuotaManager::GetOriginPatternString(uint32_t aAppId, michael@0: MozBrowserPatternFlag aBrowserFlag, michael@0: const nsACString& aOrigin, michael@0: nsAutoCString& _retval) michael@0: { michael@0: NS_ASSERTION(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID, michael@0: "Bad appId!"); michael@0: NS_ASSERTION(aOrigin.IsEmpty() || aBrowserFlag != IgnoreMozBrowser, michael@0: "Bad args!"); michael@0: michael@0: if (aOrigin.IsEmpty()) { michael@0: _retval.Truncate(); michael@0: michael@0: _retval.AppendInt(aAppId); michael@0: _retval.Append('+'); michael@0: michael@0: if (aBrowserFlag != IgnoreMozBrowser) { michael@0: if (aBrowserFlag == MozBrowser) { michael@0: _retval.Append('t'); michael@0: } michael@0: else { michael@0: _retval.Append('f'); michael@0: } michael@0: _retval.Append('+'); michael@0: } michael@0: michael@0: return; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: if (aAppId != nsIScriptSecurityManager::NO_APP_ID || michael@0: aBrowserFlag == MozBrowser) { michael@0: nsAutoCString pattern; michael@0: GetOriginPatternString(aAppId, aBrowserFlag, EmptyCString(), pattern); michael@0: NS_ASSERTION(PatternMatchesOrigin(pattern, aOrigin), michael@0: "Origin doesn't match parameters!"); michael@0: } michael@0: #endif michael@0: michael@0: _retval = aOrigin; michael@0: } michael@0: michael@0: SynchronizedOp::SynchronizedOp(const OriginOrPatternString& aOriginOrPattern, michael@0: Nullable aPersistenceType, michael@0: const nsACString& aId) michael@0: : mOriginOrPattern(aOriginOrPattern), mPersistenceType(aPersistenceType), michael@0: mId(aId) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: MOZ_COUNT_CTOR(SynchronizedOp); michael@0: } michael@0: michael@0: SynchronizedOp::~SynchronizedOp() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: MOZ_COUNT_DTOR(SynchronizedOp); michael@0: } michael@0: michael@0: bool michael@0: SynchronizedOp::MustWaitFor(const SynchronizedOp& aExistingOp) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: if (aExistingOp.mOriginOrPattern.IsNull() || mOriginOrPattern.IsNull()) { michael@0: return true; michael@0: } michael@0: michael@0: bool match; michael@0: michael@0: if (aExistingOp.mOriginOrPattern.IsOrigin()) { michael@0: if (mOriginOrPattern.IsOrigin()) { michael@0: match = aExistingOp.mOriginOrPattern.Equals(mOriginOrPattern); michael@0: } michael@0: else { michael@0: match = PatternMatchesOrigin(mOriginOrPattern, aExistingOp.mOriginOrPattern); michael@0: } michael@0: } michael@0: else if (mOriginOrPattern.IsOrigin()) { michael@0: match = PatternMatchesOrigin(aExistingOp.mOriginOrPattern, mOriginOrPattern); michael@0: } michael@0: else { michael@0: match = PatternMatchesOrigin(mOriginOrPattern, aExistingOp.mOriginOrPattern) || michael@0: PatternMatchesOrigin(aExistingOp.mOriginOrPattern, mOriginOrPattern); michael@0: } michael@0: michael@0: // If the origins don't match, the second can proceed. michael@0: if (!match) { michael@0: return false; michael@0: } michael@0: michael@0: // If the origins match but the persistence types are different, the second michael@0: // can proceed. michael@0: if (!aExistingOp.mPersistenceType.IsNull() && !mPersistenceType.IsNull() && michael@0: aExistingOp.mPersistenceType.Value() != mPersistenceType.Value()) { michael@0: return false; michael@0: } michael@0: michael@0: // If the origins and the ids match, the second must wait. michael@0: if (aExistingOp.mId == mId) { michael@0: return true; michael@0: } michael@0: michael@0: // Waiting is required if either one corresponds to an origin clearing michael@0: // (an empty Id). michael@0: if (aExistingOp.mId.IsEmpty() || mId.IsEmpty()) { michael@0: return true; michael@0: } michael@0: michael@0: // Otherwise, things for the same origin but different storages can proceed michael@0: // independently. michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: SynchronizedOp::DelayRunnable(nsIRunnable* aRunnable) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(mDelayedRunnables.IsEmpty() || mId.IsEmpty(), michael@0: "Only ClearOrigin operations can delay multiple runnables!"); michael@0: michael@0: mDelayedRunnables.AppendElement(aRunnable); michael@0: } michael@0: michael@0: void michael@0: SynchronizedOp::DispatchDelayedRunnables() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(!mListener, "Any listener should be gone by now!"); michael@0: michael@0: uint32_t count = mDelayedRunnables.Length(); michael@0: for (uint32_t index = 0; index < count; index++) { michael@0: NS_DispatchToCurrentThread(mDelayedRunnables[index]); michael@0: } michael@0: michael@0: mDelayedRunnables.Clear(); michael@0: } michael@0: michael@0: CollectOriginsHelper::CollectOriginsHelper(mozilla::Mutex& aMutex, michael@0: uint64_t aMinSizeToBeFreed) michael@0: : mMinSizeToBeFreed(aMinSizeToBeFreed), michael@0: mMutex(aMutex), michael@0: mCondVar(aMutex, "CollectOriginsHelper::mCondVar"), michael@0: mSizeToBeFreed(0), michael@0: mWaiting(true) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!"); michael@0: mMutex.AssertCurrentThreadOwns(); michael@0: } michael@0: michael@0: int64_t michael@0: CollectOriginsHelper::BlockAndReturnOriginsForEviction( michael@0: nsTArray& aOriginInfos) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!"); michael@0: mMutex.AssertCurrentThreadOwns(); michael@0: michael@0: while (mWaiting) { michael@0: mCondVar.Wait(); michael@0: } michael@0: michael@0: mOriginInfos.SwapElements(aOriginInfos); michael@0: return mSizeToBeFreed; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: CollectOriginsHelper::Run() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "Shouldn't be null!"); michael@0: michael@0: // We use extra stack vars here to avoid race detector warnings (the same michael@0: // memory accessed with and without the lock held). michael@0: nsTArray originInfos; michael@0: uint64_t sizeToBeFreed = michael@0: quotaManager->CollectOriginsForEviction(mMinSizeToBeFreed, originInfos); michael@0: michael@0: MutexAutoLock lock(mMutex); michael@0: michael@0: NS_ASSERTION(mWaiting, "Huh?!"); michael@0: michael@0: mOriginInfos.SwapElements(originInfos); michael@0: mSizeToBeFreed = sizeToBeFreed; michael@0: mWaiting = false; michael@0: mCondVar.Notify(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: OriginClearRunnable::OnExclusiveAccessAcquired() michael@0: { michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "This should never fail!"); michael@0: michael@0: nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: OriginClearRunnable::InvalidateOpenedStorages( michael@0: nsTArray >& aStorages, michael@0: void* aClosure) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: nsTArray > storages; michael@0: storages.SwapElements(aStorages); michael@0: michael@0: for (uint32_t index = 0; index < storages.Length(); index++) { michael@0: storages[index]->Invalidate(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: OriginClearRunnable::DeleteFiles(QuotaManager* aQuotaManager, michael@0: PersistenceType aPersistenceType) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: NS_ASSERTION(aQuotaManager, "Don't pass me null!"); michael@0: michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr directory = michael@0: do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: michael@0: rv = directory->InitWithPath(aQuotaManager->GetStoragePath(aPersistenceType)); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: michael@0: nsCOMPtr entries; michael@0: if (NS_FAILED(directory->GetDirectoryEntries(getter_AddRefs(entries))) || michael@0: !entries) { michael@0: return; michael@0: } michael@0: michael@0: nsCString originSanitized(mOriginOrPattern); michael@0: SanitizeOriginString(originSanitized); michael@0: michael@0: bool hasMore; michael@0: while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { michael@0: nsCOMPtr entry; michael@0: rv = entries->GetNext(getter_AddRefs(entry)); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: michael@0: nsCOMPtr file = do_QueryInterface(entry); michael@0: NS_ASSERTION(file, "Don't know what this is!"); michael@0: michael@0: bool isDirectory; michael@0: rv = file->IsDirectory(&isDirectory); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: michael@0: if (!isDirectory) { michael@0: NS_WARNING("Something in the IndexedDB directory that doesn't belong!"); michael@0: continue; michael@0: } michael@0: michael@0: nsString leafName; michael@0: rv = file->GetLeafName(leafName); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: michael@0: // Skip storages for other apps. michael@0: if (!PatternMatchesOrigin(originSanitized, michael@0: NS_ConvertUTF16toUTF8(leafName))) { michael@0: continue; michael@0: } michael@0: michael@0: if (NS_FAILED(file->Remove(true))) { michael@0: // This should never fail if we've closed all storage connections michael@0: // correctly... michael@0: NS_ERROR("Failed to remove directory!"); michael@0: } michael@0: } michael@0: michael@0: aQuotaManager->RemoveQuotaForPattern(aPersistenceType, mOriginOrPattern); michael@0: michael@0: aQuotaManager->OriginClearCompleted(aPersistenceType, mOriginOrPattern); michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED0(OriginClearRunnable, nsRunnable) michael@0: michael@0: NS_IMETHODIMP michael@0: OriginClearRunnable::Run() michael@0: { michael@0: PROFILER_LABEL("Quota", "OriginClearRunnable::Run"); michael@0: michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "This should never fail!"); michael@0: michael@0: switch (mCallbackState) { michael@0: case Pending: { michael@0: NS_NOTREACHED("Should never get here without being dispatched!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: case OpenAllowed: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: AdvanceState(); michael@0: michael@0: // Now we have to wait until the thread pool is done with all of the michael@0: // storages we care about. michael@0: nsresult rv = michael@0: quotaManager->AcquireExclusiveAccess(mOriginOrPattern, mPersistenceType, michael@0: this, InvalidateOpenedStorages, michael@0: nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: case IO: { michael@0: AssertIsOnIOThread(); michael@0: michael@0: AdvanceState(); michael@0: michael@0: if (mPersistenceType.IsNull()) { michael@0: DeleteFiles(quotaManager, PERSISTENCE_TYPE_PERSISTENT); michael@0: DeleteFiles(quotaManager, PERSISTENCE_TYPE_TEMPORARY); michael@0: } else { michael@0: DeleteFiles(quotaManager, mPersistenceType.Value()); michael@0: } michael@0: michael@0: // Now dispatch back to the main thread. michael@0: if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) { michael@0: NS_WARNING("Failed to dispatch to main thread!"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: case Complete: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: // Tell the QuotaManager that we're done. michael@0: quotaManager->AllowNextSynchronizedOp(mOriginOrPattern, mPersistenceType, michael@0: EmptyCString()); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: default: michael@0: NS_ERROR("Unknown state value!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: NS_NOTREACHED("Should never get here!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: AsyncUsageRunnable::AsyncUsageRunnable(uint32_t aAppId, michael@0: bool aInMozBrowserOnly, michael@0: const nsACString& aGroup, michael@0: const OriginOrPatternString& aOrigin, michael@0: nsIURI* aURI, michael@0: nsIUsageCallback* aCallback) michael@0: : mURI(aURI), michael@0: mCallback(aCallback), michael@0: mAppId(aAppId), michael@0: mGroup(aGroup), michael@0: mOrigin(aOrigin), michael@0: mCallbackState(Pending), michael@0: mInMozBrowserOnly(aInMozBrowserOnly) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(aURI, "Null pointer!"); michael@0: NS_ASSERTION(!aGroup.IsEmpty(), "Empty group!"); michael@0: NS_ASSERTION(aOrigin.IsOrigin(), "Expect origin only here!"); michael@0: NS_ASSERTION(!aOrigin.IsEmpty(), "Empty origin!"); michael@0: NS_ASSERTION(aCallback, "Null pointer!"); michael@0: } michael@0: michael@0: nsresult michael@0: AsyncUsageRunnable::TakeShortcut() michael@0: { michael@0: NS_ASSERTION(mCallbackState == Pending, "Huh?"); michael@0: michael@0: nsresult rv = NS_DispatchToCurrentThread(this); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mCallbackState = Shortcut; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: AsyncUsageRunnable::RunInternal() michael@0: { michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "This should never fail!"); michael@0: michael@0: nsresult rv; michael@0: michael@0: switch (mCallbackState) { michael@0: case Pending: { michael@0: NS_NOTREACHED("Should never get here without being dispatched!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: case OpenAllowed: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: AdvanceState(); michael@0: michael@0: rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Failed to dispatch to the IO thread!"); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: case IO: { michael@0: AssertIsOnIOThread(); michael@0: michael@0: AdvanceState(); michael@0: michael@0: // Add all the persistent storage files we care about. michael@0: rv = AddToUsage(quotaManager, PERSISTENCE_TYPE_PERSISTENT); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Add all the temporary storage files we care about. michael@0: rv = AddToUsage(quotaManager, PERSISTENCE_TYPE_TEMPORARY); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Run dispatches us back to the main thread. michael@0: return NS_OK; michael@0: } michael@0: michael@0: case Complete: // Fall through michael@0: case Shortcut: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: // Call the callback unless we were canceled. michael@0: if (!mCanceled) { michael@0: mCallback->OnUsageResult(mURI, TotalUsage(), FileUsage(), mAppId, michael@0: mInMozBrowserOnly); michael@0: } michael@0: michael@0: // Clean up. michael@0: mURI = nullptr; michael@0: mCallback = nullptr; michael@0: michael@0: // And tell the QuotaManager that we're done. michael@0: if (mCallbackState == Complete) { michael@0: quotaManager->AllowNextSynchronizedOp(mOrigin, michael@0: Nullable(), michael@0: EmptyCString()); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: default: michael@0: NS_ERROR("Unknown state value!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: NS_NOTREACHED("Should never get here!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsresult michael@0: AsyncUsageRunnable::AddToUsage(QuotaManager* aQuotaManager, michael@0: PersistenceType aPersistenceType) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: michael@0: nsCOMPtr directory; michael@0: nsresult rv = aQuotaManager->GetDirectoryForOrigin(aPersistenceType, mOrigin, michael@0: getter_AddRefs(directory)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool exists; michael@0: rv = directory->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // If the directory exists then enumerate all the files inside, adding up michael@0: // the sizes to get the final usage statistic. michael@0: if (exists && !mCanceled) { michael@0: bool initialized; michael@0: michael@0: if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) { michael@0: initialized = aQuotaManager->mInitializedOrigins.Contains(mOrigin); michael@0: michael@0: if (!initialized) { michael@0: rv = MaybeUpgradeOriginDirectory(directory); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: else { michael@0: NS_ASSERTION(aPersistenceType == PERSISTENCE_TYPE_TEMPORARY, "Huh?"); michael@0: initialized = aQuotaManager->mTemporaryStorageInitialized; michael@0: } michael@0: michael@0: nsCOMPtr entries; michael@0: rv = directory->GetDirectoryEntries(getter_AddRefs(entries)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool hasMore; michael@0: while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && michael@0: hasMore && !mCanceled) { michael@0: nsCOMPtr entry; michael@0: rv = entries->GetNext(getter_AddRefs(entry)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr file = do_QueryInterface(entry); michael@0: NS_ENSURE_TRUE(file, NS_NOINTERFACE); michael@0: michael@0: nsString leafName; michael@0: rv = file->GetLeafName(leafName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (leafName.EqualsLiteral(METADATA_FILE_NAME) || michael@0: leafName.EqualsLiteral(DSSTORE_FILE_NAME)) { michael@0: continue; michael@0: } michael@0: michael@0: if (!initialized) { michael@0: bool isDirectory; michael@0: rv = file->IsDirectory(&isDirectory); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!isDirectory) { michael@0: NS_WARNING("Unknown file found!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: } michael@0: michael@0: Client::Type clientType; michael@0: rv = Client::TypeFromText(leafName, clientType); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Unknown directory found!"); michael@0: if (!initialized) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: continue; michael@0: } michael@0: michael@0: nsRefPtr& client = aQuotaManager->mClients[clientType]; michael@0: michael@0: if (initialized) { michael@0: rv = client->GetUsageForOrigin(aPersistenceType, mGroup, mOrigin, this); michael@0: } michael@0: else { michael@0: rv = client->InitOrigin(aPersistenceType, mGroup, mOrigin, this); michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED(AsyncUsageRunnable, nsRunnable, nsIQuotaRequest) michael@0: michael@0: NS_IMETHODIMP michael@0: AsyncUsageRunnable::Run() michael@0: { michael@0: PROFILER_LABEL("Quota", "AsyncUsageRunnable::Run"); michael@0: michael@0: nsresult rv = RunInternal(); michael@0: michael@0: if (!NS_IsMainThread()) { michael@0: if (NS_FAILED(rv)) { michael@0: ResetUsage(); michael@0: } michael@0: michael@0: if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) { michael@0: NS_WARNING("Failed to dispatch to main thread!"); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AsyncUsageRunnable::Cancel() michael@0: { michael@0: if (mCanceled.exchange(true)) { michael@0: NS_WARNING("Canceled more than once?!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ResetOrClearRunnable::OnExclusiveAccessAcquired() michael@0: { michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "This should never fail!"); michael@0: michael@0: nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: ResetOrClearRunnable::InvalidateOpenedStorages( michael@0: nsTArray >& aStorages, michael@0: void* aClosure) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: nsTArray > storages; michael@0: storages.SwapElements(aStorages); michael@0: michael@0: for (uint32_t index = 0; index < storages.Length(); index++) { michael@0: storages[index]->Invalidate(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ResetOrClearRunnable::DeleteFiles(QuotaManager* aQuotaManager, michael@0: PersistenceType aPersistenceType) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: NS_ASSERTION(aQuotaManager, "Don't pass me null!"); michael@0: michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr directory = michael@0: do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: michael@0: rv = directory->InitWithPath(aQuotaManager->GetStoragePath(aPersistenceType)); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: michael@0: rv = directory->Remove(true); michael@0: if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST && michael@0: rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) { michael@0: // This should never fail if we've closed all storage connections michael@0: // correctly... michael@0: NS_ERROR("Failed to remove directory!"); michael@0: } michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED0(ResetOrClearRunnable, nsRunnable) michael@0: michael@0: NS_IMETHODIMP michael@0: ResetOrClearRunnable::Run() michael@0: { michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "This should never fail!"); michael@0: michael@0: switch (mCallbackState) { michael@0: case Pending: { michael@0: NS_NOTREACHED("Should never get here without being dispatched!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: case OpenAllowed: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: AdvanceState(); michael@0: michael@0: // Now we have to wait until the thread pool is done with all of the michael@0: // storages we care about. michael@0: nsresult rv = michael@0: quotaManager->AcquireExclusiveAccess(NullCString(), michael@0: Nullable(), this, michael@0: InvalidateOpenedStorages, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: case IO: { michael@0: AssertIsOnIOThread(); michael@0: michael@0: AdvanceState(); michael@0: michael@0: if (mClear) { michael@0: DeleteFiles(quotaManager, PERSISTENCE_TYPE_PERSISTENT); michael@0: DeleteFiles(quotaManager, PERSISTENCE_TYPE_TEMPORARY); michael@0: } michael@0: michael@0: quotaManager->RemoveQuota(); michael@0: quotaManager->ResetOrClearCompleted(); michael@0: michael@0: // Now dispatch back to the main thread. michael@0: if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) { michael@0: NS_WARNING("Failed to dispatch to main thread!"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: case Complete: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: // Tell the QuotaManager that we're done. michael@0: quotaManager->AllowNextSynchronizedOp(OriginOrPatternString::FromNull(), michael@0: Nullable(), michael@0: EmptyCString()); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: default: michael@0: NS_ERROR("Unknown state value!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: NS_NOTREACHED("Should never get here!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: FinalizeOriginEvictionRunnable::Run() michael@0: { michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "This should never fail!"); michael@0: michael@0: nsresult rv; michael@0: michael@0: switch (mCallbackState) { michael@0: case Pending: { michael@0: NS_NOTREACHED("Should never get here without being dispatched!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: case OpenAllowed: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: AdvanceState(); michael@0: michael@0: rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Failed to dispatch to the IO thread!"); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: case IO: { michael@0: AssertIsOnIOThread(); michael@0: michael@0: AdvanceState(); michael@0: michael@0: for (uint32_t index = 0; index < mOrigins.Length(); index++) { michael@0: quotaManager->OriginClearCompleted( michael@0: PERSISTENCE_TYPE_TEMPORARY, michael@0: OriginOrPatternString::FromOrigin(mOrigins[index])); michael@0: } michael@0: michael@0: if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) { michael@0: NS_WARNING("Failed to dispatch to main thread!"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: case Complete: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: for (uint32_t index = 0; index < mOrigins.Length(); index++) { michael@0: quotaManager->AllowNextSynchronizedOp( michael@0: OriginOrPatternString::FromOrigin(mOrigins[index]), michael@0: Nullable(PERSISTENCE_TYPE_TEMPORARY), michael@0: EmptyCString()); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: default: michael@0: NS_ERROR("Unknown state value!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: NS_NOTREACHED("Should never get here!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsresult michael@0: FinalizeOriginEvictionRunnable::Dispatch() michael@0: { michael@0: NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(mCallbackState == Pending, "Huh?"); michael@0: michael@0: mCallbackState = OpenAllowed; michael@0: return NS_DispatchToMainThread(this); michael@0: } michael@0: michael@0: nsresult michael@0: FinalizeOriginEvictionRunnable::RunImmediately() michael@0: { michael@0: AssertIsOnIOThread(); michael@0: NS_ASSERTION(mCallbackState == Pending, "Huh?"); michael@0: michael@0: mCallbackState = IO; michael@0: return this->Run(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: WaitForTransactionsToFinishRunnable::Run() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(mOp, "Null op!"); michael@0: NS_ASSERTION(mOp->mListener, "Nothing to run!"); michael@0: NS_ASSERTION(mCountdown, "Wrong countdown!"); michael@0: michael@0: if (--mCountdown) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Don't hold the listener alive longer than necessary. michael@0: nsRefPtr listener; michael@0: listener.swap(mOp->mListener); michael@0: michael@0: mOp = nullptr; michael@0: michael@0: nsresult rv = listener->OnExclusiveAccessAcquired(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // The listener is responsible for calling michael@0: // QuotaManager::AllowNextSynchronizedOp. michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: WaitForLockedFilesToFinishRunnable::Run() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: mBusy = false; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: SaveOriginAccessTimeRunnable::Run() michael@0: { michael@0: AssertIsOnIOThread(); michael@0: michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "This should never fail!"); michael@0: michael@0: nsCOMPtr directory; michael@0: nsresult rv = michael@0: quotaManager->GetDirectoryForOrigin(PERSISTENCE_TYPE_TEMPORARY, mOrigin, michael@0: getter_AddRefs(directory)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr stream; michael@0: rv = GetDirectoryMetadataStream(directory, true, getter_AddRefs(stream)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // The origin directory may not exist anymore. michael@0: if (stream) { michael@0: rv = stream->Write64(mTimestamp); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: }