diff --git a/b2g/installer/package-manifest.in b/b2g/installer/package-manifest.in index 9c7d18e973c8..dd46c7a9cfd0 100644 --- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -202,6 +202,7 @@ @BINPATH@/components/dom_browserelement.xpt @BINPATH@/components/dom_messages.xpt @BINPATH@/components/dom_power.xpt +@BINPATH@/components/dom_quota.xpt @BINPATH@/components/dom_range.xpt @BINPATH@/components/dom_settings.xpt @BINPATH@/components/dom_permissionsettings.xpt diff --git a/browser/base/content/pageinfo/permissions.js b/browser/base/content/pageinfo/permissions.js index bfdae9dcc256..ddf201800d2e 100644 --- a/browser/base/content/pageinfo/permissions.js +++ b/browser/base/content/pageinfo/permissions.js @@ -7,11 +7,11 @@ const ALLOW = nsIPermissionManager.ALLOW_ACTION; // 1 const BLOCK = nsIPermissionManager.DENY_ACTION; // 2 const SESSION = nsICookiePermission.ACCESS_SESSION; // 8 -const nsIIndexedDatabaseManager = - Components.interfaces.nsIIndexedDatabaseManager; +const nsIQuotaManager = Components.interfaces.nsIQuotaManager; var gPermURI; var gPrefs; +var gUsageRequest; var gPermObj = { image: function getImageDefaultPermission() @@ -116,9 +116,10 @@ function onUnloadPermission() .getService(Components.interfaces.nsIObserverService); os.removeObserver(permissionObserver, "perm-changed"); - var dbManager = Components.classes["@mozilla.org/dom/indexeddb/manager;1"] - .getService(nsIIndexedDatabaseManager); - dbManager.cancelGetUsageForURI(gPermURI, onIndexedDBUsageCallback); + if (gUsageRequest) { + gUsageRequest.cancel(); + gUsageRequest = null; + } } function initRow(aPartId) @@ -205,9 +206,10 @@ function setRadioState(aPartId, aValue) function initIndexedDBRow() { - var dbManager = Components.classes["@mozilla.org/dom/indexeddb/manager;1"] - .getService(nsIIndexedDatabaseManager); - dbManager.getUsageForURI(gPermURI, onIndexedDBUsageCallback); + var quotaManager = Components.classes["@mozilla.org/dom/quota/manager;1"] + .getService(nsIQuotaManager); + gUsageRequest = + quotaManager.getUsageForURI(gPermURI, onIndexedDBUsageCallback); var status = document.getElementById("indexedDBStatus"); var button = document.getElementById("indexedDBClear"); @@ -219,9 +221,9 @@ function initIndexedDBRow() function onIndexedDBClear() { - Components.classes["@mozilla.org/dom/indexeddb/manager;1"] - .getService(nsIIndexedDatabaseManager) - .clearDatabasesForURI(gPermURI); + Components.classes["@mozilla.org/dom/quota/manager;1"] + .getService(nsIQuotaManager) + .clearStoragesForURI(gPermURI); var permissionManager = Components.classes[PERMISSION_CONTRACTID] .getService(nsIPermissionManager); diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 193bf24b97b7..22524369ede9 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -203,6 +203,7 @@ #endif @BINPATH@/components/dom_browserelement.xpt @BINPATH@/components/dom_power.xpt +@BINPATH@/components/dom_quota.xpt @BINPATH@/components/dom_range.xpt @BINPATH@/components/dom_settings.xpt @BINPATH@/components/dom_permissionsettings.xpt diff --git a/content/base/src/nsDocument.cpp b/content/base/src/nsDocument.cpp index 15c99627e177..5ae71c66d49e 100644 --- a/content/base/src/nsDocument.cpp +++ b/content/base/src/nsDocument.cpp @@ -156,7 +156,7 @@ #include "mozAutoDocUpdate.h" #include "nsGlobalWindow.h" #include "mozilla/dom/EncodingUtils.h" -#include "mozilla/dom/indexedDB/IndexedDatabaseManager.h" +#include "mozilla/dom/quota/QuotaManager.h" #include "nsDOMNavigationTiming.h" #include "nsEventStateManager.h" @@ -7638,11 +7638,11 @@ nsDocument::CanSavePresentation(nsIRequest *aNewRequest) } } - // Check if we have running IndexedDB transactions - indexedDB::IndexedDatabaseManager* idbManager = - win ? indexedDB::IndexedDatabaseManager::Get() : nullptr; - if (idbManager && idbManager->HasOpenTransactions(win)) { - return false; + // Check if we have running offline storage transactions + quota::QuotaManager* quotaManager = + win ? quota::QuotaManager::Get() : nullptr; + if (quotaManager && quotaManager->HasOpenTransactions(win)) { + return false; } #ifdef MOZ_WEBRTC diff --git a/content/media/webrtc/MediaEngineWebRTC.h b/content/media/webrtc/MediaEngineWebRTC.h index 72703e56c3df..213b20c26a3b 100644 --- a/content/media/webrtc/MediaEngineWebRTC.h +++ b/content/media/webrtc/MediaEngineWebRTC.h @@ -18,6 +18,7 @@ #include "DOMMediaStream.h" #include "nsDirectoryServiceDefs.h" #include "nsComponentManagerUtils.h" +#include "nsRefPtrHashtable.h" #include "VideoUtils.h" #include "MediaEngine.h" diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp index 6db7a7bf05d1..ed4ceddd64dc 100644 --- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -63,6 +63,7 @@ #include "mozilla/dom/Element.h" #include "mozilla/dom/indexedDB/FileInfo.h" #include "mozilla/dom/indexedDB/IndexedDatabaseManager.h" +#include "mozilla/dom/quota/QuotaManager.h" #include "GeckoProfiler.h" #include "nsDOMBlobBuilder.h" #include "nsIDOMFileHandle.h" @@ -2756,8 +2757,7 @@ nsDOMWindowUtils::GetFileReferences(const nsAString& aDatabaseName, NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); nsCString origin; - nsresult rv = indexedDB::IndexedDatabaseManager::GetASCIIOriginFromWindow( - window, origin); + nsresult rv = quota::QuotaManager::GetASCIIOriginFromWindow(window, origin); NS_ENSURE_SUCCESS(rv, rv); nsRefPtr mgr = diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index b481323162bf..faba920f3359 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -207,7 +207,7 @@ #include "prenv.h" #include "mozilla/dom/indexedDB/IDBFactory.h" -#include "mozilla/dom/indexedDB/IndexedDatabaseManager.h" +#include "mozilla/dom/quota/QuotaManager.h" #include "mozilla/dom/StructuredCloneTags.h" @@ -1390,11 +1390,10 @@ nsGlobalWindow::FreeInnerObjects() AutoPushJSContext cx(scx ? scx->GetNativeContext() : nullptr); mozilla::dom::workers::CancelWorkersForWindow(cx, this); - // Close all IndexedDB databases for this window. - indexedDB::IndexedDatabaseManager* idbManager = - indexedDB::IndexedDatabaseManager::Get(); - if (idbManager) { - idbManager->AbortCloseDatabasesForWindow(this); + // Close all offline storages for this window. + quota::QuotaManager* quotaManager = quota::QuotaManager::Get(); + if (quotaManager) { + quotaManager->AbortCloseStoragesForWindow(this); } ClearAllTimeouts(); diff --git a/dom/file/FileHandle.cpp b/dom/file/FileHandle.cpp index 9a9271c8374c..82d31ffad5d2 100644 --- a/dom/file/FileHandle.cpp +++ b/dom/file/FileHandle.cpp @@ -107,7 +107,7 @@ FileHandle::Open(FileMode aMode, ErrorResult& aError) { MOZ_ASSERT(NS_IsMainThread()); - if (FileService::IsShuttingDown() || mFileStorage->IsStorageShuttingDown()) { + if (FileService::IsShuttingDown() || mFileStorage->IsShuttingDown()) { aError.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR); return nullptr; } diff --git a/dom/file/FileService.cpp b/dom/file/FileService.cpp index 2cf1702fe7c1..b8288b074cb9 100644 --- a/dom/file/FileService.cpp +++ b/dom/file/FileService.cpp @@ -152,11 +152,11 @@ FileService::Enqueue(LockedFile* aLockedFile, FileHelper* aFileHelper) FileHandle* fileHandle = aLockedFile->mFileHandle; - if (fileHandle->mFileStorage->IsStorageInvalidated()) { + if (fileHandle->mFileStorage->IsInvalidated()) { return NS_ERROR_NOT_AVAILABLE; } - nsISupports* storageId = fileHandle->mFileStorage->StorageId(); + nsIAtom* storageId = fileHandle->mFileStorage->Id(); const nsAString& fileName = fileHandle->mFileName; bool modeIsWrite = aLockedFile->mMode == LockedFile::READ_WRITE; @@ -226,7 +226,7 @@ FileService::NotifyLockedFileCompleted(LockedFile* aLockedFile) NS_ASSERTION(aLockedFile, "Null pointer!"); FileHandle* fileHandle = aLockedFile->mFileHandle; - nsISupports* storageId = fileHandle->mFileStorage->StorageId(); + nsIAtom* storageId = fileHandle->mFileStorage->Id(); FileStorageInfo* fileStorageInfo; if (!mFileStorageInfos.Get(storageId, &fileStorageInfo)) { @@ -256,10 +256,10 @@ FileService::NotifyLockedFileCompleted(LockedFile* aLockedFile) } } -bool -FileService::WaitForAllStoragesToComplete( - nsTArray >& aStorages, - nsIRunnable* aCallback) +void +FileService::WaitForStoragesToComplete( + nsTArray >& aStorages, + nsIRunnable* aCallback) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(!aStorages.IsEmpty(), "No databases to wait on!"); @@ -272,8 +272,6 @@ FileService::WaitForAllStoragesToComplete( if (MaybeFireCallback(*callback)) { mCompleteCallbacks.RemoveElementAt(mCompleteCallbacks.Length() - 1); } - - return true; } void @@ -283,7 +281,7 @@ FileService::AbortLockedFilesForStorage(nsIFileStorage* aFileStorage) NS_ASSERTION(aFileStorage, "Null pointer!"); FileStorageInfo* fileStorageInfo; - if (!mFileStorageInfos.Get(aFileStorage->StorageId(), &fileStorageInfo)) { + if (!mFileStorageInfos.Get(aFileStorage->Id(), &fileStorageInfo)) { return; } @@ -303,7 +301,7 @@ FileService::HasLockedFilesForStorage(nsIFileStorage* aFileStorage) NS_ASSERTION(aFileStorage, "Null pointer!"); FileStorageInfo* fileStorageInfo; - if (!mFileStorageInfos.Get(aFileStorage->StorageId(), &fileStorageInfo)) { + if (!mFileStorageInfos.Get(aFileStorage->Id(), &fileStorageInfo)) { return false; } @@ -330,8 +328,7 @@ FileService::MaybeFireCallback(StoragesCompleteCallback& aCallback) NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); for (uint32_t index = 0; index < aCallback.mStorages.Length(); index++) { - if (mFileStorageInfos.Get(aCallback.mStorages[index]->StorageId(), - nullptr)) { + if (mFileStorageInfos.Get(aCallback.mStorages[index]->Id(), nullptr)) { return false; } } @@ -488,7 +485,7 @@ FileService::FileStorageInfo::RemoveLockedFileQueue(LockedFile* aLockedFile) bool FileService::FileStorageInfo::HasRunningLockedFiles( - nsIFileStorage* aFileStorage) + nsIFileStorage* aFileStorage) { for (uint32_t index = 0; index < mLockedFileQueues.Length(); index++) { LockedFile* lockedFile = mLockedFileQueues[index]->mLockedFile; diff --git a/dom/file/FileService.h b/dom/file/FileService.h index e3f94af6033c..97f5933dc170 100644 --- a/dom/file/FileService.h +++ b/dom/file/FileService.h @@ -46,9 +46,9 @@ public: void NotifyLockedFileCompleted(LockedFile* aLockedFile); - bool - WaitForAllStoragesToComplete(nsTArray >& aStorages, - nsIRunnable* aCallback); + void + WaitForStoragesToComplete(nsTArray >& aStorages, + nsIRunnable* aCallback); void AbortLockedFilesForStorage(nsIFileStorage* aFileStorage); @@ -129,8 +129,8 @@ private: inline void CollectRunningAndDelayedLockedFiles( - nsIFileStorage* aFileStorage, - nsTArray >& aLockedFiles); + nsIFileStorage* aFileStorage, + nsTArray >& aLockedFiles); void LockFileForReading(const nsAString& aFileName) diff --git a/dom/file/LockedFile.cpp b/dom/file/LockedFile.cpp index 83b20723becb..e09ef700cd11 100644 --- a/dom/file/LockedFile.cpp +++ b/dom/file/LockedFile.cpp @@ -384,7 +384,7 @@ LockedFile::CreateParallelStream(nsISupports** aStream) NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); nsIFileStorage* fileStorage = mFileHandle->mFileStorage; - if (fileStorage->IsStorageInvalidated()) { + if (fileStorage->IsInvalidated()) { return NS_ERROR_NOT_AVAILABLE; } @@ -404,7 +404,7 @@ LockedFile::GetOrCreateStream(nsISupports** aStream) NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); nsIFileStorage* fileStorage = mFileHandle->mFileStorage; - if (fileStorage->IsStorageInvalidated()) { + if (fileStorage->IsInvalidated()) { return NS_ERROR_NOT_AVAILABLE; } @@ -957,7 +957,7 @@ FinishHelper::Run() } nsIFileStorage* fileStorage = mLockedFile->mFileHandle->mFileStorage; - if (fileStorage->IsStorageInvalidated()) { + if (fileStorage->IsInvalidated()) { mAborted = true; } diff --git a/dom/file/nsIFileStorage.h b/dom/file/nsIFileStorage.h index e985f0a3ac3b..22a916b311d6 100644 --- a/dom/file/nsIFileStorage.h +++ b/dom/file/nsIFileStorage.h @@ -10,52 +10,50 @@ #include "nsISupports.h" #define NS_FILESTORAGE_IID \ - {0xa0801944, 0x2f1c, 0x4203, \ - { 0x9c, 0xaa, 0xaa, 0x47, 0xe0, 0x0c, 0x67, 0x92 } } + {0x6278f453, 0xd557, 0x4a55, \ + { 0x99, 0x3e, 0xf4, 0x69, 0xe2, 0xa5, 0xe1, 0xd0 } } + +class nsIAtom; class nsIFileStorage : public nsISupports { public: NS_DECLARE_STATIC_IID_ACCESSOR(NS_FILESTORAGE_IID) - virtual const nsACString& - StorageOrigin() = 0; + NS_IMETHOD_(nsIAtom*) + Id() = 0; - virtual nsISupports* - StorageId() = 0; + // Whether or not the storage has been invalidated. If it has then no further + // operations for this storage will be allowed to run. + NS_IMETHOD_(bool) + IsInvalidated() = 0; - virtual bool - IsStorageInvalidated() = 0; + NS_IMETHOD_(bool) + IsShuttingDown() = 0; - virtual bool - IsStorageShuttingDown() = 0; - - virtual void + NS_IMETHOD_(void) SetThreadLocals() = 0; - virtual void + NS_IMETHOD_(void) UnsetThreadLocals() = 0; }; NS_DEFINE_STATIC_IID_ACCESSOR(nsIFileStorage, NS_FILESTORAGE_IID) #define NS_DECL_NSIFILESTORAGE \ - virtual const nsACString& \ - StorageOrigin() MOZ_OVERRIDE; \ + NS_IMETHOD_(nsIAtom*) \ + Id() MOZ_OVERRIDE; \ \ - virtual nsISupports* \ - StorageId() MOZ_OVERRIDE; \ + NS_IMETHOD_(bool) \ + IsInvalidated() MOZ_OVERRIDE; \ \ - virtual bool \ - IsStorageInvalidated() MOZ_OVERRIDE; \ + NS_IMETHOD_(bool) \ + IsShuttingDown() MOZ_OVERRIDE; \ \ - virtual bool \ - IsStorageShuttingDown() MOZ_OVERRIDE; \ - \ - virtual void \ + NS_IMETHOD_(void) \ SetThreadLocals() MOZ_OVERRIDE; \ \ - virtual void \ + NS_IMETHOD_(void) \ UnsetThreadLocals() MOZ_OVERRIDE; #endif // nsIFileStorage_h__ diff --git a/dom/indexedDB/CheckPermissionsHelper.cpp b/dom/indexedDB/CheckPermissionsHelper.cpp index ada2c2e277f1..fd7f5d152a59 100644 --- a/dom/indexedDB/CheckPermissionsHelper.cpp +++ b/dom/indexedDB/CheckPermissionsHelper.cpp @@ -19,8 +19,9 @@ #include "nsDOMStorage.h" #include "nsNetUtil.h" #include "nsThreadUtils.h" -#include "mozilla/Services.h" +#include "mozilla/dom/quota/QuotaManager.h" #include "mozilla/Preferences.h" +#include "mozilla/Services.h" #include "IndexedDatabaseManager.h" @@ -140,10 +141,10 @@ CheckPermissionsHelper::Run() window.swap(mWindow); if (permission == PERMISSION_ALLOWED) { - IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); - NS_ASSERTION(mgr, "This should never be null!"); + quota::QuotaManager* quotaManager = quota::QuotaManager::Get(); + NS_ASSERTION(quotaManager, "This should never be null!"); - return helper->Dispatch(mgr->IOThread()); + return helper->Dispatch(quotaManager->IOThread()); } NS_ASSERTION(permission == PERMISSION_PROMPT || diff --git a/dom/indexedDB/Client.cpp b/dom/indexedDB/Client.cpp new file mode 100644 index 000000000000..9884996c43bd --- /dev/null +++ b/dom/indexedDB/Client.cpp @@ -0,0 +1,338 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Client.h" + +#include "mozilla/dom/quota/QuotaManager.h" +#include "mozilla/dom/quota/UsageRunnable.h" +#include "mozilla/dom/quota/Utilities.h" + +#include "IDBDatabase.h" +#include "IndexedDatabaseManager.h" +#include "TransactionThreadPool.h" + +USING_INDEXEDDB_NAMESPACE +using mozilla::dom::quota::QuotaManager; + +namespace { + +bool +GetDatabaseBaseFilename(const nsAString& aFilename, + nsAString& aDatabaseBaseFilename) +{ + NS_ASSERTION(!aFilename.IsEmpty(), "Bad argument!"); + + NS_NAMED_LITERAL_STRING(sqlite, ".sqlite"); + + if (!StringEndsWith(aFilename, sqlite)) { + return false; + } + + aDatabaseBaseFilename = + Substring(aFilename, 0, aFilename.Length() - sqlite.Length()); + + return true; +} + +} // anonymous namespace + +// This needs to be fully qualified to not confuse trace refcnt assertions. +NS_IMPL_ADDREF(mozilla::dom::indexedDB::Client) +NS_IMPL_RELEASE(mozilla::dom::indexedDB::Client) + +nsresult +Client::InitOrigin(const nsACString& aOrigin, UsageRunnable* aUsageRunnable) +{ + nsCOMPtr directory; + nsresult rv = GetDirectory(aOrigin, getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + // We need to see if there are any files in the directory already. If they + // are database files then we need to cleanup stored files (if it's needed) + // and also get the usage. + + nsAutoTArray subdirsToProcess; + nsAutoTArray , 20> unknownFiles; + nsTHashtable validSubdirs; + validSubdirs.Init(20); + + nsCOMPtr entries; + rv = directory->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore; + while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && + hasMore && (!aUsageRunnable || !aUsageRunnable->Canceled())) { + nsCOMPtr entry; + rv = entries->GetNext(getter_AddRefs(entry)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr file = do_QueryInterface(entry); + NS_ENSURE_TRUE(file, NS_NOINTERFACE); + + nsString leafName; + rv = file->GetLeafName(leafName); + NS_ENSURE_SUCCESS(rv, rv); + + if (StringEndsWith(leafName, NS_LITERAL_STRING(".sqlite-journal"))) { + continue; + } + + bool isDirectory; + rv = file->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + + if (isDirectory) { + if (!validSubdirs.GetEntry(leafName)) { + subdirsToProcess.AppendElement(leafName); + } + continue; + } + + nsString dbBaseFilename; + if (!GetDatabaseBaseFilename(leafName, dbBaseFilename)) { + unknownFiles.AppendElement(file); + continue; + } + + nsCOMPtr fmDirectory; + rv = directory->Clone(getter_AddRefs(fmDirectory)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = fmDirectory->Append(dbBaseFilename); + NS_ENSURE_SUCCESS(rv, rv); + + rv = FileManager::InitDirectory(fmDirectory, file, aOrigin); + NS_ENSURE_SUCCESS(rv, rv); + + if (aUsageRunnable) { + int64_t fileSize; + rv = file->GetFileSize(&fileSize); + NS_ENSURE_SUCCESS(rv, rv); + + aUsageRunnable->AppendToDatabaseUsage(uint64_t(fileSize)); + + uint64_t usage; + rv = FileManager::GetUsage(fmDirectory, &usage); + NS_ENSURE_SUCCESS(rv, rv); + + aUsageRunnable->AppendToFileUsage(usage); + } + + validSubdirs.PutEntry(dbBaseFilename); + } + NS_ENSURE_SUCCESS(rv, rv); + + for (uint32_t i = 0; i < subdirsToProcess.Length(); i++) { + const nsString& subdir = subdirsToProcess[i]; + if (!validSubdirs.GetEntry(subdir)) { + NS_WARNING("Unknown subdirectory found!"); + return NS_ERROR_UNEXPECTED; + } + } + + for (uint32_t i = 0; i < unknownFiles.Length(); i++) { + nsCOMPtr& unknownFile = unknownFiles[i]; + + // Some temporary SQLite files could disappear, so we have to check if the + // unknown file still exists. + bool exists; + rv = unknownFile->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + + if (exists) { + nsString leafName; + unknownFile->GetLeafName(leafName); + + // The journal file may exists even after db has been correctly opened. + if (!StringEndsWith(leafName, NS_LITERAL_STRING(".sqlite-journal"))) { + NS_WARNING("Unknown file found!"); + return NS_ERROR_UNEXPECTED; + } + } + } + + return NS_OK; +} + +nsresult +Client::GetUsageForOrigin(const nsACString& aOrigin, + UsageRunnable* aUsageRunnable) +{ + NS_ASSERTION(aUsageRunnable, "Null pointer!"); + + nsCOMPtr directory; + nsresult rv = GetDirectory(aOrigin, getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = GetUsageForDirectoryInternal(directory, aUsageRunnable, true); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +bool +Client::IsTransactionServiceActivated() +{ + return !!TransactionThreadPool::Get(); +} + +void +Client::WaitForStoragesToComplete(nsTArray& aStorages, + nsIRunnable* aCallback) +{ + NS_ASSERTION(!aStorages.IsEmpty(), "No storages to wait on!"); + NS_ASSERTION(aCallback, "Passed null callback!"); + + TransactionThreadPool* pool = TransactionThreadPool::Get(); + NS_ASSERTION(pool, "Should have checked if transaction service is active!"); + + nsTArray databases(aStorages.Length()); + for (uint32_t index = 0; index < aStorages.Length(); index++) { + IDBDatabase* database = IDBDatabase::FromStorage(aStorages[index]); + NS_ASSERTION(database, "This shouldn't be null!"); + + databases.AppendElement(database); + } + + pool->WaitForDatabasesToComplete(databases, aCallback); +} + +void +Client::AbortTransactionsForStorage(nsIOfflineStorage* aStorage) +{ + NS_ASSERTION(aStorage, "Passed null storage!"); + + TransactionThreadPool* pool = TransactionThreadPool::Get(); + NS_ASSERTION(pool, "Should have checked if transaction service is active!"); + + IDBDatabase* database = IDBDatabase::FromStorage(aStorage); + NS_ASSERTION(database, "This shouldn't be null!"); + + pool->AbortTransactionsForDatabase(database); +} + +bool +Client::HasTransactionsForStorage(nsIOfflineStorage* aStorage) +{ + TransactionThreadPool* pool = TransactionThreadPool::Get(); + NS_ASSERTION(pool, "Should have checked if transaction service is active!"); + + IDBDatabase* database = IDBDatabase::FromStorage(aStorage); + NS_ASSERTION(database, "This shouldn't be null!"); + + return pool->HasTransactionsForDatabase(database); +} + +void +Client::OnOriginClearCompleted(const nsACString& aPattern) +{ + IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); + if (mgr) { + mgr->InvalidateFileManagersForPattern(aPattern); + } +} + +void +Client::ShutdownTransactionService() +{ + TransactionThreadPool::Shutdown(); +} + +void +Client::OnShutdownCompleted() +{ + IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); + if (mgr) { + mgr->InvalidateAllFileManagers(); + } +} + +nsresult +Client::GetDirectory(const nsACString& aOrigin, nsIFile** aDirectory) +{ + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "This should never fail!"); + + nsCOMPtr directory; + nsresult rv = + quotaManager->GetDirectoryForOrigin(aOrigin, getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ASSERTION(directory, "What?"); + + rv = directory->Append(NS_LITERAL_STRING(IDB_DIRECTORY_NAME)); + NS_ENSURE_SUCCESS(rv, rv); + + directory.forget(aDirectory); + return NS_OK; +} + +nsresult +Client::GetUsageForDirectoryInternal(nsIFile* aDirectory, + UsageRunnable* aUsageRunnable, + bool aDatabaseFiles) +{ + NS_ASSERTION(aDirectory, "Null pointer!"); + NS_ASSERTION(aUsageRunnable, "Null pointer!"); + + nsCOMPtr entries; + nsresult rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!entries) { + return NS_OK; + } + + bool hasMore; + while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && + hasMore && !aUsageRunnable->Canceled()) { + nsCOMPtr entry; + rv = entries->GetNext(getter_AddRefs(entry)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr file(do_QueryInterface(entry)); + NS_ASSERTION(file, "Don't know what this is!"); + + bool isDirectory; + rv = file->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + + if (isDirectory) { + if (aDatabaseFiles) { + rv = GetUsageForDirectoryInternal(file, aUsageRunnable, false); + NS_ENSURE_SUCCESS(rv, rv); + } + else { + nsString leafName; + rv = file->GetLeafName(leafName); + NS_ENSURE_SUCCESS(rv, rv); + + if (!leafName.EqualsLiteral(JOURNAL_DIRECTORY_NAME)) { + NS_WARNING("Unknown directory found!"); + } + } + + continue; + } + + int64_t fileSize; + rv = file->GetFileSize(&fileSize); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ASSERTION(fileSize >= 0, "Negative size?!"); + + if (aDatabaseFiles) { + aUsageRunnable->AppendToDatabaseUsage(uint64_t(fileSize)); + } + else { + aUsageRunnable->AppendToFileUsage(uint64_t(fileSize)); + } + } + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} diff --git a/dom/indexedDB/Client.h b/dom/indexedDB/Client.h new file mode 100644 index 000000000000..39519bc21669 --- /dev/null +++ b/dom/indexedDB/Client.h @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_indexeddb_client_h__ +#define mozilla_dom_indexeddb_client_h__ + +#include "IndexedDatabase.h" + +#include "mozilla/dom/quota/Client.h" + +#define IDB_DIRECTORY_NAME "idb" +#define JOURNAL_DIRECTORY_NAME "journals" + +BEGIN_INDEXEDDB_NAMESPACE + +class Client : public mozilla::dom::quota::Client +{ + typedef mozilla::dom::quota::UsageRunnable UsageRunnable; + +public: + NS_IMETHOD_(nsrefcnt) + AddRef() MOZ_OVERRIDE; + + NS_IMETHOD_(nsrefcnt) + Release() MOZ_OVERRIDE; + + virtual Type + GetType() MOZ_OVERRIDE + { + return IDB; + } + + virtual nsresult + InitOrigin(const nsACString& aOrigin, + UsageRunnable* aUsageRunnable) MOZ_OVERRIDE; + + virtual nsresult + GetUsageForOrigin(const nsACString& aOrigin, + UsageRunnable* aUsageRunnable) MOZ_OVERRIDE; + + virtual bool + IsFileServiceUtilized() MOZ_OVERRIDE + { + return true; + } + + virtual bool + IsTransactionServiceActivated() MOZ_OVERRIDE; + + virtual void + WaitForStoragesToComplete(nsTArray& aStorages, + nsIRunnable* aCallback) MOZ_OVERRIDE; + + virtual void + AbortTransactionsForStorage(nsIOfflineStorage* aStorage) MOZ_OVERRIDE; + + virtual bool + HasTransactionsForStorage(nsIOfflineStorage* aStorage) MOZ_OVERRIDE; + + virtual void + OnOriginClearCompleted(const nsACString& aPattern) MOZ_OVERRIDE; + + virtual void + ShutdownTransactionService() MOZ_OVERRIDE; + + virtual void + OnShutdownCompleted() MOZ_OVERRIDE; + +private: + nsresult + GetDirectory(const nsACString& aOrigin, nsIFile** aDirectory); + + nsresult + GetUsageForDirectoryInternal(nsIFile* aDirectory, + UsageRunnable* aUsageRunnable, + bool aDatabaseFiles); + + nsAutoRefCnt mRefCnt; + NS_DECL_OWNINGTHREAD +}; + +END_INDEXEDDB_NAMESPACE + +#endif // mozilla_dom_indexeddb_client_h__ diff --git a/dom/indexedDB/FileInfo.cpp b/dom/indexedDB/FileInfo.cpp index 77d8830fd1a7..235099df8e6f 100644 --- a/dom/indexedDB/FileInfo.cpp +++ b/dom/indexedDB/FileInfo.cpp @@ -6,6 +6,8 @@ #include "FileInfo.h" +#include "mozilla/dom/quota/QuotaManager.h" + USING_INDEXEDDB_NAMESPACE // static @@ -91,7 +93,7 @@ FileInfo::UpdateReferences(nsAutoRefCnt& aRefCount, int32_t aDelta, void FileInfo::Cleanup() { - if (IndexedDatabaseManager::IsShuttingDown() || + if (quota::QuotaManager::IsShuttingDown() || mFileManager->Invalidated()) { return; } diff --git a/dom/indexedDB/FileManager.cpp b/dom/indexedDB/FileManager.cpp index e837b7b8c0b5..98a313b6b5ae 100644 --- a/dom/indexedDB/FileManager.cpp +++ b/dom/indexedDB/FileManager.cpp @@ -11,9 +11,11 @@ #include "nsIInputStream.h" #include "nsISimpleEnumerator.h" +#include "mozilla/dom/quota/Utilities.h" #include "mozStorageCID.h" #include "mozStorageHelper.h" +#include "Client.h" #include "FileInfo.h" #include "IndexedDatabaseManager.h" #include "OpenDatabaseHelper.h" @@ -21,8 +23,6 @@ #include "IndexedDatabaseInlines.h" #include -#define JOURNAL_DIRECTORY_NAME "journals" - USING_INDEXEDDB_NAMESPACE namespace { @@ -412,7 +412,7 @@ FileManager::GetUsage(nsIFile* aDirectory, uint64_t* aUsage) rv = file->GetFileSize(&fileSize); NS_ENSURE_SUCCESS(rv, rv); - IncrementUsage(&usage, uint64_t(fileSize)); + quota::IncrementUsage(&usage, uint64_t(fileSize)); } *aUsage = usage; diff --git a/dom/indexedDB/FileManager.h b/dom/indexedDB/FileManager.h index 370d4a8e70ca..8bea76f3c9f2 100644 --- a/dom/indexedDB/FileManager.h +++ b/dom/indexedDB/FileManager.h @@ -8,12 +8,14 @@ #define mozilla_dom_indexeddb_filemanager_h__ #include "IndexedDatabase.h" -#include "nsIFile.h" + #include "nsIDOMFile.h" +#include "nsIFile.h" + +#include "mozilla/dom/quota/StoragePrivilege.h" #include "nsDataHashtable.h" class mozIStorageConnection; -class mozIStorageServiceQuotaManagement; BEGIN_INDEXEDDB_NAMESPACE @@ -23,8 +25,10 @@ class FileManager { friend class FileInfo; + typedef mozilla::dom::quota::StoragePrivilege StoragePrivilege; + public: - FileManager(const nsACString& aOrigin, FactoryPrivilege aPrivilege, + FileManager(const nsACString& aOrigin, StoragePrivilege aPrivilege, const nsAString& aDatabaseName) : mOrigin(aOrigin), mPrivilege(aPrivilege), mDatabaseName(aDatabaseName), mLastFileId(0), mInvalidated(false) @@ -40,7 +44,7 @@ public: return mOrigin; } - const FactoryPrivilege& Privilege() const + const StoragePrivilege& Privilege() const { return mPrivilege; } @@ -81,7 +85,7 @@ public: private: nsCString mOrigin; - FactoryPrivilege mPrivilege; + StoragePrivilege mPrivilege; nsString mDatabaseName; nsString mDirectoryPath; diff --git a/dom/indexedDB/IDBDatabase.cpp b/dom/indexedDB/IDBDatabase.cpp index 4b994d4058aa..c20717620e0a 100644 --- a/dom/indexedDB/IDBDatabase.cpp +++ b/dom/indexedDB/IDBDatabase.cpp @@ -11,6 +11,7 @@ #include "mozilla/Mutex.h" #include "mozilla/storage.h" #include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/quota/Client.h" #include "mozilla/dom/quota/QuotaManager.h" #include "nsDOMClassInfo.h" #include "nsDOMLists.h" @@ -27,7 +28,6 @@ #include "IDBObjectStore.h" #include "IDBTransaction.h" #include "IDBFactory.h" -#include "IndexedDatabaseManager.h" #include "TransactionThreadPool.h" #include "DictionaryHelpers.h" #include "nsContentUtils.h" @@ -37,6 +37,7 @@ USING_INDEXEDDB_NAMESPACE using mozilla::dom::ContentParent; +using mozilla::dom::quota::Client; using mozilla::dom::quota::QuotaManager; namespace { @@ -199,17 +200,30 @@ IDBDatabase::Create(IDBWrapperCache* aOwnerCache, db->mFileManager = aFileManager; db->mContentParent = aContentParent; - IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); - NS_ASSERTION(mgr, "This should never be null!"); + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "This should never be null!"); - if (!mgr->RegisterDatabase(db)) { + db->mQuotaClient = quotaManager->GetClient(Client::IDB); + NS_ASSERTION(db->mQuotaClient, "This shouldn't fail!"); + + if (!quotaManager->RegisterStorage(db)) { // Either out of memory or shutting down. return nullptr; } + db->mRegistered = true; + return db.forget(); } +// static +IDBDatabase* +IDBDatabase::FromStorage(nsIOfflineStorage* aStorage) +{ + return aStorage->GetClient()->GetType() == Client::IDB ? + static_cast(aStorage) : nullptr; +} + IDBDatabase::IDBDatabase() : mDatabaseId(0), mActorChild(nullptr), @@ -237,14 +251,14 @@ IDBDatabase::~IDBDatabase() if (mRegistered) { CloseInternal(true); - IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); - if (mgr) { - mgr->UnregisterDatabase(this); + QuotaManager* quotaManager = QuotaManager::Get(); + if (quotaManager) { + quotaManager->UnregisterStorage(this); } } } -void +NS_IMETHODIMP_(void) IDBDatabase::Invalidate() { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); @@ -311,9 +325,9 @@ IDBDatabase::CloseInternal(bool aIsDead) } } - IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); - if (mgr) { - mgr->OnDatabaseClosed(this); + QuotaManager* quotaManager = QuotaManager::Get(); + if (quotaManager) { + quotaManager->OnStorageClosed(this); } // And let the parent process know as well. @@ -324,8 +338,8 @@ IDBDatabase::CloseInternal(bool aIsDead) } } -bool -IDBDatabase::IsClosed() const +NS_IMETHODIMP_(bool) +IDBDatabase::IsClosed() { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); return mClosed; @@ -367,10 +381,10 @@ IDBDatabase::OnUnlink() // transactions from starting and unblock any other SetVersion callers. CloseInternal(true); - // No reason for the IndexedDatabaseManager to track us any longer. - IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); - if (mgr) { - mgr->UnregisterDatabase(this); + // No reason for the QuotaManager to track us any longer. + QuotaManager* quotaManager = QuotaManager::Get(); + if (quotaManager) { + quotaManager->UnregisterStorage(this); // Don't try to unregister again in the destructor. mRegistered = false; @@ -433,6 +447,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(IDBDatabase) NS_INTERFACE_MAP_ENTRY(nsIIDBDatabase) NS_INTERFACE_MAP_ENTRY(nsIFileStorage) + NS_INTERFACE_MAP_ENTRY(nsIOfflineStorage) NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(IDBDatabase) NS_INTERFACE_MAP_END_INHERITING(IDBWrapperCache) @@ -602,7 +617,7 @@ IDBDatabase::Transaction(const jsval& aStoreNames, { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - if (IndexedDatabaseManager::IsShuttingDown()) { + if (QuotaManager::IsShuttingDown()) { return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } @@ -744,7 +759,7 @@ IDBDatabase::MozCreateFileHandle(const nsAString& aName, return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } - if (IndexedDatabaseManager::IsShuttingDown()) { + if (QuotaManager::IsShuttingDown()) { return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } @@ -757,10 +772,10 @@ IDBDatabase::MozCreateFileHandle(const nsAString& aName, nsRefPtr helper = new CreateFileHelper(this, request, aName, aType); - IndexedDatabaseManager* manager = IndexedDatabaseManager::Get(); - NS_ASSERTION(manager, "We should definitely have a manager here"); + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "We should definitely have a manager here"); - nsresult rv = helper->Dispatch(manager->IOThread()); + nsresult rv = helper->Dispatch(quotaManager->IOThread()); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); request.forget(_retval); @@ -778,43 +793,55 @@ IDBDatabase::Close() return NS_OK; } -const nsACString& -IDBDatabase::StorageOrigin() +NS_IMETHODIMP_(nsIAtom*) +IDBDatabase::Id() { - return Origin(); + return mDatabaseId; } -nsISupports* -IDBDatabase::StorageId() +NS_IMETHODIMP_(bool) +IDBDatabase::IsInvalidated() { - return Id(); + return mInvalidated; } -bool -IDBDatabase::IsStorageInvalidated() +NS_IMETHODIMP_(bool) +IDBDatabase::IsShuttingDown() { - return IsInvalidated(); + return QuotaManager::IsShuttingDown(); } -bool -IDBDatabase::IsStorageShuttingDown() -{ - return IndexedDatabaseManager::IsShuttingDown(); -} - -void +NS_IMETHODIMP_(void) IDBDatabase::SetThreadLocals() { NS_ASSERTION(GetOwner(), "Should have owner!"); QuotaManager::SetCurrentWindow(GetOwner()); } -void +NS_IMETHODIMP_(void) IDBDatabase::UnsetThreadLocals() { QuotaManager::SetCurrentWindow(nullptr); } +NS_IMETHODIMP_(mozilla::dom::quota::Client*) +IDBDatabase::GetClient() +{ + return mQuotaClient; +} + +NS_IMETHODIMP_(bool) +IDBDatabase::IsOwned(nsPIDOMWindow* aOwner) +{ + return GetOwner() == aOwner; +} + +NS_IMETHODIMP_(const nsACString&) +IDBDatabase::Origin() +{ + return mASCIIOrigin; +} + nsresult IDBDatabase::PostHandleEvent(nsEventChainPostVisitor& aVisitor) { diff --git a/dom/indexedDB/IDBDatabase.h b/dom/indexedDB/IDBDatabase.h index 5df1fb2a5647..016508fb7387 100644 --- a/dom/indexedDB/IDBDatabase.h +++ b/dom/indexedDB/IDBDatabase.h @@ -12,9 +12,12 @@ #include "nsIDocument.h" #include "nsIFileStorage.h" #include "nsIIDBDatabase.h" +#include "nsIOfflineStorage.h" + #include "nsDOMEventTargetHelper.h" -#include "mozilla/dom/indexedDB/IDBWrapperCache.h" + #include "mozilla/dom/indexedDB/FileManager.h" +#include "mozilla/dom/indexedDB/IDBWrapperCache.h" class nsIScriptContext; class nsPIDOMWindow; @@ -22,6 +25,9 @@ class nsPIDOMWindow; namespace mozilla { namespace dom { class ContentParent; +namespace quota { +class Client; +} } } @@ -40,7 +46,7 @@ struct ObjectStoreInfoGuts; class IDBDatabase : public IDBWrapperCache, public nsIIDBDatabase, - public nsIFileStorage + public nsIOfflineStorage { friend class AsyncConnectionHelper; friend class IndexedDatabaseManager; @@ -50,6 +56,7 @@ public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIIDBDATABASE NS_DECL_NSIFILESTORAGE + NS_DECL_NSIOFFLINESTORAGE_NOCLOSE NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(IDBDatabase, IDBWrapperCache) @@ -61,14 +68,19 @@ public: FileManager* aFileManager, mozilla::dom::ContentParent* aContentParent); + static IDBDatabase* + FromStorage(nsIOfflineStorage* aStorage); + + static IDBDatabase* + FromStorage(nsIFileStorage* aStorage) + { + nsCOMPtr storage = do_QueryInterface(aStorage); + return storage ? FromStorage(storage) : nullptr; + } + // nsIDOMEventTarget virtual nsresult PostHandleEvent(nsEventChainPostVisitor& aVisitor); - nsIAtom* Id() const - { - return mDatabaseId; - } - DatabaseInfo* Info() const { return mDatabaseInfo; @@ -95,28 +107,10 @@ public: return doc.forget(); } - const nsCString& Origin() const - { - return mASCIIOrigin; - } - - void Invalidate(); - - // Whether or not the database has been invalidated. If it has then no further - // transactions for this database will be allowed to run. This function may be - // called on any thread. - bool IsInvalidated() const - { - return mInvalidated; - } - void DisconnectFromActorParent(); void CloseInternal(bool aIsDead); - // Whether or not the database has had Close called on it. - bool IsClosed() const; - void EnterSetVersionTransaction(); void ExitSetVersionTransaction(); @@ -195,6 +189,8 @@ private: mozilla::dom::ContentParent* mContentParent; + nsRefPtr mQuotaClient; + bool mInvalidated; bool mRegistered; bool mClosed; diff --git a/dom/indexedDB/IDBFactory.cpp b/dom/indexedDB/IDBFactory.cpp index fefff8732e3a..5322eb5ffb91 100644 --- a/dom/indexedDB/IDBFactory.cpp +++ b/dom/indexedDB/IDBFactory.cpp @@ -18,6 +18,8 @@ #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/PBrowserChild.h" +#include "mozilla/dom/quota/OriginOrPatternString.h" +#include "mozilla/dom/quota/QuotaManager.h" #include "mozilla/dom/TabChild.h" #include "mozilla/storage.h" #include "nsComponentManagerUtils.h" @@ -45,6 +47,7 @@ #include USING_INDEXEDDB_NAMESPACE +USING_QUOTA_NAMESPACE using mozilla::dom::ContentChild; using mozilla::dom::ContentParent; @@ -103,7 +106,7 @@ IDBFactory::Create(nsPIDOMWindow* aWindow, // Make sure that the manager is up before we do anything here since lots of // decisions depend on which process we're running in. - nsRefPtr mgr = + indexedDB::IndexedDatabaseManager* mgr = indexedDB::IndexedDatabaseManager::GetOrCreate(); NS_ENSURE_TRUE(mgr, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); @@ -111,7 +114,7 @@ IDBFactory::Create(nsPIDOMWindow* aWindow, nsCString origin(aASCIIOrigin); if (origin.IsEmpty()) { - rv = IndexedDatabaseManager::GetASCIIOriginFromWindow(aWindow, origin); + rv = QuotaManager::GetASCIIOriginFromWindow(aWindow, origin); if (NS_FAILED(rv)) { // Not allowed. *aFactory = nullptr; @@ -161,8 +164,7 @@ IDBFactory::Create(JSContext* aCx, NS_ASSERTION(nsContentUtils::IsCallerChrome(), "Only for chrome!"); nsCString origin; - nsresult rv = - IndexedDatabaseManager::GetASCIIOriginFromWindow(nullptr, origin); + nsresult rv = QuotaManager::GetASCIIOriginFromWindow(nullptr, origin); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); nsRefPtr factory = new IDBFactory(); @@ -521,7 +523,7 @@ IDBFactory::OpenCommon(const nsAString& aName, nsCOMPtr window; JSObject* scriptOwner = nullptr; - FactoryPrivilege privilege; + StoragePrivilege privilege; if (mWindow) { window = mWindow; @@ -551,17 +553,17 @@ IDBFactory::OpenCommon(const nsAString& aName, nsRefPtr permissionHelper = new CheckPermissionsHelper(openHelper, window, aDeleting); - IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); - NS_ASSERTION(mgr, "This should never be null!"); + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "This should never be null!"); - rv = - mgr->WaitForOpenAllowed(OriginOrPatternString::FromOrigin(aASCIIOrigin), - openHelper->Id(), permissionHelper); + rv = quotaManager->WaitForOpenAllowed(OriginOrPatternString::FromOrigin( + aASCIIOrigin), openHelper->Id(), + permissionHelper); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } else if (aDeleting) { nsCOMPtr databaseId = - IndexedDatabaseManager::GetDatabaseId(aASCIIOrigin, aName); + QuotaManager::GetStorageId(aASCIIOrigin, aName); NS_ENSURE_TRUE(databaseId, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); IndexedDBDeleteDatabaseRequestChild* actor = diff --git a/dom/indexedDB/IDBFileHandle.cpp b/dom/indexedDB/IDBFileHandle.cpp index f71fd56a917a..3f866441cb9d 100644 --- a/dom/indexedDB/IDBFileHandle.cpp +++ b/dom/indexedDB/IDBFileHandle.cpp @@ -67,7 +67,10 @@ IDBFileHandle::Create(IDBDatabase* aDatabase, already_AddRefed IDBFileHandle::CreateStream(nsIFile* aFile, bool aReadOnly) { - const nsACString& origin = mFileStorage->StorageOrigin(); + nsCOMPtr storage = do_QueryInterface(mFileStorage); + NS_ASSERTION(storage, "This should always succeed!"); + + const nsACString& origin = storage->Origin(); nsCOMPtr result; diff --git a/dom/indexedDB/IDBTransaction.h b/dom/indexedDB/IDBTransaction.h index 147f597303d1..fbbc2c410fc1 100644 --- a/dom/indexedDB/IDBTransaction.h +++ b/dom/indexedDB/IDBTransaction.h @@ -20,6 +20,7 @@ #include "nsClassHashtable.h" #include "nsHashKeys.h" #include "nsInterfaceHashtable.h" +#include "nsRefPtrHashtable.h" #include "mozilla/dom/indexedDB/IDBDatabase.h" #include "mozilla/dom/indexedDB/IDBWrapperCache.h" diff --git a/dom/indexedDB/IndexedDatabase.h b/dom/indexedDB/IndexedDatabase.h index c9ff94e05334..b0ea8965f6c9 100644 --- a/dom/indexedDB/IndexedDatabase.h +++ b/dom/indexedDB/IndexedDatabase.h @@ -36,11 +36,6 @@ class FileInfo; class IDBDatabase; class IDBTransaction; -enum FactoryPrivilege { - Content, - Chrome -}; - template void SwapData(T& aData1, T& aData2) { @@ -175,44 +170,6 @@ struct SerializedStructuredCloneWriteInfo uint64_t offsetToKeyProp; }; -class OriginOrPatternString : public nsCString -{ -public: - static OriginOrPatternString - FromOrigin(const nsACString& aOrigin) - { - return OriginOrPatternString(aOrigin, true); - } - - static OriginOrPatternString - FromPattern(const nsACString& aPattern) - { - return OriginOrPatternString(aPattern, false); - } - - bool - IsOrigin() const - { - return mIsOrigin; - } - - bool - IsPattern() const - { - return !mIsOrigin; - } - -private: - OriginOrPatternString(const nsACString& aOriginOrPattern, bool aIsOrigin) - : nsCString(aOriginOrPattern), mIsOrigin(aIsOrigin) - { } - - bool - operator==(const OriginOrPatternString& aOther) MOZ_DELETE; - - bool mIsOrigin; -}; - END_INDEXEDDB_NAMESPACE #endif // mozilla_dom_indexeddb_indexeddatabase_h__ diff --git a/dom/indexedDB/IndexedDatabaseInlines.h b/dom/indexedDB/IndexedDatabaseInlines.h index f27d60c3479b..62e65d6c5a90 100644 --- a/dom/indexedDB/IndexedDatabaseInlines.h +++ b/dom/indexedDB/IndexedDatabaseInlines.h @@ -79,17 +79,4 @@ AppendConditionClause(const nsACString& aColumnName, aResult += NS_LITERAL_CSTRING(" :") + aArgName; } -inline void -IncrementUsage(uint64_t* aUsage, uint64_t aDelta) -{ - // Watch for overflow! - if ((UINT64_MAX - *aUsage) < aDelta) { - NS_WARNING("Usage exceeds the maximum!"); - *aUsage = UINT64_MAX; - } - else { - *aUsage += aDelta; - } -} - END_INDEXEDDB_NAMESPACE diff --git a/dom/indexedDB/IndexedDatabaseManager.cpp b/dom/indexedDB/IndexedDatabaseManager.cpp index 3f92a4b12530..7142480aa5ac 100644 --- a/dom/indexedDB/IndexedDatabaseManager.cpp +++ b/dom/indexedDB/IndexedDatabaseManager.cpp @@ -6,245 +6,52 @@ #include "IndexedDatabaseManager.h" -#include "mozIApplicationClearPrivateDataParams.h" -#include "nsIAtom.h" #include "nsIConsoleService.h" #include "nsIDOMScriptObjectFactory.h" #include "nsIFile.h" #include "nsIFileStorage.h" -#include "nsIObserverService.h" -#include "nsIPrincipal.h" #include "nsIScriptError.h" -#include "nsIScriptObjectPrincipal.h" -#include "nsIScriptSecurityManager.h" -#include "nsISHEntry.h" -#include "nsISimpleEnumerator.h" -#include "nsITimer.h" -#include "mozilla/dom/file/FileService.h" +#include "mozilla/ClearOnShutdown.h" #include "mozilla/dom/quota/QuotaManager.h" +#include "mozilla/dom/quota/Utilities.h" #include "mozilla/dom/TabContext.h" -#include "mozilla/LazyIdleThread.h" -#include "mozilla/Preferences.h" -#include "mozilla/Services.h" #include "mozilla/storage.h" -#include "nsAppDirectoryServiceDefs.h" #include "nsContentUtils.h" -#include "nsCRTGlue.h" -#include "nsDirectoryServiceUtils.h" #include "nsEventDispatcher.h" -#include "nsScriptSecurityManager.h" #include "nsThreadUtils.h" -#include "nsXPCOM.h" -#include "nsXPCOMPrivate.h" -#include "AsyncConnectionHelper.h" -#include "IDBDatabase.h" #include "IDBEvents.h" #include "IDBFactory.h" #include "IDBKeyRange.h" -#include "OpenDatabaseHelper.h" -#include "TransactionThreadPool.h" - -#include "IndexedDatabaseInlines.h" -#include - -// The amount of time, in milliseconds, that our IO thread will stay alive -// after the last event it processes. -#define DEFAULT_THREAD_TIMEOUT_MS 30000 - -// The amount of time, in milliseconds, that we will wait for active database -// transactions on shutdown before aborting them. -#define DEFAULT_SHUTDOWN_TIMER_MS 30000 - -// Amount of space that IndexedDB databases may use by default in megabytes. -#define DEFAULT_QUOTA_MB 50 - -// Preference that users can set to override DEFAULT_QUOTA_MB -#define PREF_INDEXEDDB_QUOTA "dom.indexedDB.warningQuota" - -// profile-before-change, when we need to shut down IDB -#define PROFILE_BEFORE_CHANGE_OBSERVER_ID "profile-before-change" +#include "IDBRequest.h" USING_INDEXEDDB_NAMESPACE -using namespace mozilla::services; using namespace mozilla::dom; -using mozilla::Preferences; -using mozilla::dom::file::FileService; -using mozilla::dom::quota::QuotaManager; +USING_QUOTA_NAMESPACE static NS_DEFINE_CID(kDOMSOF_CID, NS_DOM_SCRIPT_OBJECT_FACTORY_CID); namespace { -int32_t gShutdown = 0; +mozilla::StaticRefPtr gInstance; + +int32_t gInitialized = 0; int32_t gClosed = 0; -// Does not hold a reference. -IndexedDatabaseManager* gInstance = nullptr; - -int32_t gIndexedDBQuotaMB = DEFAULT_QUOTA_MB; - -bool -GetDatabaseBaseFilename(const nsAString& aFilename, - nsAString& aDatabaseBaseFilename) +class AsyncDeleteFileRunnable MOZ_FINAL : public nsIRunnable { - NS_ASSERTION(!aFilename.IsEmpty(), "Bad argument!"); - - NS_NAMED_LITERAL_STRING(sqlite, ".sqlite"); - nsAString::size_type filenameLen = aFilename.Length(); - nsAString::size_type sqliteLen = sqlite.Length(); - - if (sqliteLen > filenameLen || - Substring(aFilename, filenameLen - sqliteLen, sqliteLen) != sqlite) { - return false; - } - - aDatabaseBaseFilename = Substring(aFilename, 0, filenameLen - sqliteLen); - - return true; -} - -// Adds all databases in the hash to the given array. -template -PLDHashOperator -EnumerateToTArray(const nsACString& aKey, - nsTArray* aValue, - void* aUserArg) -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - NS_ASSERTION(!aKey.IsEmpty(), "Empty key!"); - NS_ASSERTION(aValue, "Null pointer!"); - NS_ASSERTION(aUserArg, "Null pointer!"); - - static_cast*>(aUserArg)->AppendElements(*aValue); - return PL_DHASH_NEXT; -} - -bool -PatternMatchesOrigin(const nsACString& aPatternString, const nsACString& aOrigin) -{ - // Aren't we smart! - return StringBeginsWith(aOrigin, aPatternString); -} - -enum MozBrowserPatternFlag -{ - MozBrowser = 0, - NotMozBrowser, - IgnoreMozBrowser -}; - -// Use one of the friendly overloads below. -void -GetOriginPatternString(uint32_t aAppId, MozBrowserPatternFlag aBrowserFlag, - const nsACString& aOrigin, nsAutoCString& _retval) -{ - NS_ASSERTION(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID, - "Bad appId!"); - NS_ASSERTION(aOrigin.IsEmpty() || aBrowserFlag != IgnoreMozBrowser, - "Bad args!"); - - if (aOrigin.IsEmpty()) { - _retval.Truncate(); - - _retval.AppendInt(aAppId); - _retval.Append('+'); - - if (aBrowserFlag != IgnoreMozBrowser) { - if (aBrowserFlag == MozBrowser) { - _retval.Append('t'); - } - else { - _retval.Append('f'); - } - _retval.Append('+'); - } - - return; - } - -#ifdef DEBUG - if (aAppId != nsIScriptSecurityManager::NO_APP_ID || - aBrowserFlag == MozBrowser) { - nsAutoCString pattern; - GetOriginPatternString(aAppId, aBrowserFlag, EmptyCString(), pattern); - NS_ASSERTION(PatternMatchesOrigin(pattern, aOrigin), - "Origin doesn't match parameters!"); - } -#endif - - _retval = aOrigin; -} - -void -GetOriginPatternString(uint32_t aAppId, bool aBrowserOnly, - const nsACString& aOrigin, nsAutoCString& _retval) -{ - return GetOriginPatternString(aAppId, - aBrowserOnly ? MozBrowser : NotMozBrowser, - aOrigin, _retval); -} - -void -GetOriginPatternStringMaybeIgnoreBrowser(uint32_t aAppId, bool aBrowserOnly, - nsAutoCString& _retval) -{ - return GetOriginPatternString(aAppId, - aBrowserOnly ? MozBrowser : IgnoreMozBrowser, - EmptyCString(), _retval); -} - -template -class PatternMatchArray : public nsAutoTArray -{ - typedef PatternMatchArray SelfType; - - struct Closure - { - Closure(SelfType& aSelf, const nsACString& aPattern) - : mSelf(aSelf), mPattern(aPattern) - { } - - SelfType& mSelf; - const nsACString& mPattern; - }; - public: - template - void - Find(const T& aHashtable, - const nsACString& aPattern) - { - SelfType::Clear(); + NS_DECL_ISUPPORTS + NS_DECL_NSIRUNNABLE - Closure closure(*this, aPattern); - aHashtable.EnumerateRead(SelfType::Enumerate, &closure); - } + AsyncDeleteFileRunnable(FileManager* aFileManager, int64_t aFileId); private: - static PLDHashOperator - Enumerate(const nsACString& aKey, - nsTArray* aValue, - void* aUserArg) - { - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - NS_ASSERTION(!aKey.IsEmpty(), "Empty key!"); - NS_ASSERTION(aValue, "Null pointer!"); - NS_ASSERTION(aUserArg, "Null pointer!"); - - Closure* closure = static_cast(aUserArg); - - if (PatternMatchesOrigin(closure->mPattern, aKey)) { - closure->mSelf.AppendElements(*aValue); - } - - return PL_DHASH_NEXT; - } + nsRefPtr mFileManager; + int64_t mFileId; }; -typedef PatternMatchArray DatabasePatternMatchArray; - PLDHashOperator InvalidateAndRemoveFileManagers( const nsACString& aKey, @@ -269,176 +76,52 @@ InvalidateAndRemoveFileManagers( return PL_DHASH_NEXT; } -void -SanitizeOriginString(nsCString& aOrigin) -{ - // We want profiles to be platform-independent so we always need to replace - // the same characters on every platform. Windows has the most extensive set - // of illegal characters so we use its FILE_ILLEGAL_CHARACTERS and - // FILE_PATH_SEPARATOR. - static const char kReplaceChars[] = CONTROL_CHARACTERS "/:*?\"<>|\\"; - -#ifdef XP_WIN - NS_ASSERTION(!strcmp(kReplaceChars, - FILE_ILLEGAL_CHARACTERS FILE_PATH_SEPARATOR), - "Illegal file characters have changed!"); -#endif - - aOrigin.ReplaceChar(kReplaceChars, '+'); -} - -nsresult -GetASCIIOriginFromURI(nsIURI* aURI, - uint32_t aAppId, - bool aInMozBrowser, - nsACString& aOrigin) -{ - NS_ASSERTION(aURI, "Null uri!"); - - nsCString origin; - mozilla::GetExtendedOrigin(aURI, aAppId, aInMozBrowser, origin); - - if (origin.IsEmpty()) { - NS_WARNING("GetExtendedOrigin returned empty string!"); - return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; - } - - aOrigin.Assign(origin); - return NS_OK; -} - -nsresult -GetASCIIOriginFromPrincipal(nsIPrincipal* aPrincipal, - nsACString& aOrigin) -{ - NS_ASSERTION(aPrincipal, "Don't hand me a null principal!"); - - static const char kChromeOrigin[] = "chrome"; - - nsCString origin; - if (nsContentUtils::IsSystemPrincipal(aPrincipal)) { - origin.AssignLiteral(kChromeOrigin); - } - else { - bool isNullPrincipal; - nsresult rv = aPrincipal->GetIsNullPrincipal(&isNullPrincipal); - NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - - if (isNullPrincipal) { - NS_WARNING("IndexedDB not supported from this principal!"); - return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; - } - - rv = aPrincipal->GetExtendedOrigin(origin); - NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - - if (origin.EqualsLiteral(kChromeOrigin)) { - NS_WARNING("Non-chrome principal can't use chrome origin!"); - return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; - } - } - - aOrigin.Assign(origin); - return NS_OK; -} - } // anonymous namespace IndexedDatabaseManager::IndexedDatabaseManager() : mFileMutex("IndexedDatabaseManager.mFileMutex") { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - NS_ASSERTION(!gInstance, "More than one instance!"); + + mFileManagers.Init(); } IndexedDatabaseManager::~IndexedDatabaseManager() { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - NS_ASSERTION(!gInstance || gInstance == this, "Different instances!"); - gInstance = nullptr; } bool IndexedDatabaseManager::sIsMainProcess = false; // static -already_AddRefed +IndexedDatabaseManager* IndexedDatabaseManager::GetOrCreate() { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - if (IsShuttingDown()) { - NS_ERROR("Calling GetOrCreateInstance() after shutdown!"); + if (IsClosed()) { + NS_ERROR("Calling GetOrCreate() after shutdown!"); return nullptr; } - nsRefPtr instance(gInstance); - - if (!instance) { + if (!gInstance) { sIsMainProcess = XRE_GetProcessType() == GeckoProcessType_Default; - instance = new IndexedDatabaseManager(); + nsRefPtr instance(new IndexedDatabaseManager()); - instance->mLiveDatabases.Init(); - instance->mFileManagers.Init(); - - nsresult rv; - - if (sIsMainProcess) { - nsCOMPtr dbBaseDirectory; - rv = NS_GetSpecialDirectory(NS_APP_INDEXEDDB_PARENT_DIR, - getter_AddRefs(dbBaseDirectory)); - if (NS_FAILED(rv)) { - rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, - getter_AddRefs(dbBaseDirectory)); - } - NS_ENSURE_SUCCESS(rv, nullptr); - - rv = dbBaseDirectory->Append(NS_LITERAL_STRING("indexedDB")); - NS_ENSURE_SUCCESS(rv, nullptr); - - rv = dbBaseDirectory->GetPath(instance->mDatabaseBasePath); - NS_ENSURE_SUCCESS(rv, nullptr); - - // Make a lazy thread for any IO we need (like clearing or enumerating the - // contents of indexedDB database directories). - instance->mIOThread = - new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, - NS_LITERAL_CSTRING("IndexedDB I/O"), - LazyIdleThread::ManualShutdown); - - // Make a timer here to avoid potential failures later. We don't actually - // initialize the timer until shutdown. - instance->mShutdownTimer = do_CreateInstance(NS_TIMER_CONTRACTID); - NS_ENSURE_TRUE(instance->mShutdownTimer, nullptr); - } - - // Make sure that the quota manager is up. - NS_ENSURE_TRUE(QuotaManager::GetOrCreate(), nullptr); - - nsCOMPtr obs = GetObserverService(); - NS_ENSURE_TRUE(obs, nullptr); - - // Must initialize the storage service on the main thread. - nsCOMPtr ss = - do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID); - NS_ENSURE_TRUE(ss, nullptr); - - // We need this callback to know when to shut down all our threads. - rv = obs->AddObserver(instance, PROFILE_BEFORE_CHANGE_OBSERVER_ID, false); + nsresult rv = instance->Init(); NS_ENSURE_SUCCESS(rv, nullptr); - if (NS_FAILED(Preferences::AddIntVarCache(&gIndexedDBQuotaMB, - PREF_INDEXEDDB_QUOTA, - DEFAULT_QUOTA_MB))) { - NS_WARNING("Unable to respond to quota pref changes!"); - gIndexedDBQuotaMB = DEFAULT_QUOTA_MB; + if (PR_ATOMIC_SET(&gInitialized, 1)) { + NS_ERROR("Initialized more than once?!"); } - // The observer service will hold our last reference, don't AddRef here. gInstance = instance; + + ClearOnShutdown(&gInstance); } - return instance.forget(); + return gInstance; } // static @@ -455,44 +138,35 @@ IndexedDatabaseManager::FactoryCreate() { // Returns a raw pointer that carries an owning reference! Lame, but the // singleton factory macros force this. - return GetOrCreate().get(); + IndexedDatabaseManager* mgr = GetOrCreate(); + NS_IF_ADDREF(mgr); + return mgr; } nsresult -IndexedDatabaseManager::GetDirectoryForOrigin(const nsACString& aASCIIOrigin, - nsIFile** aDirectory) const +IndexedDatabaseManager::Init() { - nsresult rv; - nsCOMPtr directory = - do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); + // Make sure that the quota manager is up. + NS_ENSURE_TRUE(QuotaManager::GetOrCreate(), NS_ERROR_FAILURE); - rv = directory->InitWithPath(GetBaseDirectory()); - NS_ENSURE_SUCCESS(rv, rv); + // Must initialize the storage service on the main thread. + nsCOMPtr ss = + do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID); + NS_ENSURE_TRUE(ss, NS_ERROR_FAILURE); - nsAutoCString originSanitized(aASCIIOrigin); - SanitizeOriginString(originSanitized); - - rv = directory->Append(NS_ConvertASCIItoUTF16(originSanitized)); - NS_ENSURE_SUCCESS(rv, rv); - - directory.forget(aDirectory); return NS_OK; } -// static -already_AddRefed -IndexedDatabaseManager::GetDatabaseId(const nsACString& aOrigin, - const nsAString& aName) +void +IndexedDatabaseManager::Destroy() { - nsCString str(aOrigin); - str.Append("*"); - str.Append(NS_ConvertUTF16toUTF8(aName)); + // Setting the closed flag prevents the service from being recreated. + // Don't set it though if there's no real instance created. + if (!!gInitialized && PR_ATOMIC_SET(&gClosed, 1)) { + NS_ERROR("Shutdown more than once?!"); + } - nsCOMPtr atom = do_GetAtom(str); - NS_ENSURE_TRUE(atom, nullptr); - - return atom.forget(); + delete this; } // static @@ -590,223 +264,14 @@ IndexedDatabaseManager::TabContextMayAccessOrigin(const TabContext& aContext, // browser elements. But if aContext is not for a browser element, it may // access both browser and non-browser elements. nsAutoCString pattern; - GetOriginPatternStringMaybeIgnoreBrowser(aContext.OwnOrContainingAppId(), - aContext.IsBrowserElement(), - pattern); + QuotaManager::GetOriginPatternStringMaybeIgnoreBrowser( + aContext.OwnOrContainingAppId(), + aContext.IsBrowserElement(), + pattern); return PatternMatchesOrigin(pattern, aOrigin); } -bool -IndexedDatabaseManager::RegisterDatabase(IDBDatabase* aDatabase) -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - NS_ASSERTION(aDatabase, "Null pointer!"); - - // Don't allow any new databases to be created after shutdown. - if (IsShuttingDown()) { - return false; - } - - // Add this database to its origin array if it exists, create it otherwise. - nsTArray* array; - if (!mLiveDatabases.Get(aDatabase->Origin(), &array)) { - nsAutoPtr > newArray(new nsTArray()); - mLiveDatabases.Put(aDatabase->Origin(), newArray); - array = newArray.forget(); - } - if (!array->AppendElement(aDatabase)) { - NS_WARNING("Out of memory?"); - return false; - } - - aDatabase->mRegistered = true; - return true; -} - -void -IndexedDatabaseManager::UnregisterDatabase(IDBDatabase* aDatabase) -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - NS_ASSERTION(aDatabase, "Null pointer!"); - - // Remove this database from its origin array, maybe remove the array if it - // is then empty. - nsTArray* array; - if (mLiveDatabases.Get(aDatabase->Origin(), &array) && - array->RemoveElement(aDatabase)) { - if (array->IsEmpty()) { - mLiveDatabases.Remove(aDatabase->Origin()); - } - return; - } - NS_ERROR("Didn't know anything about this database!"); -} - -void -IndexedDatabaseManager::OnUsageCheckComplete(AsyncUsageRunnable* aRunnable) -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - NS_ASSERTION(aRunnable, "Null pointer!"); - NS_ASSERTION(!aRunnable->mURI, "Should have been cleared!"); - NS_ASSERTION(!aRunnable->mCallback, "Should have been cleared!"); - - if (!mUsageRunnables.RemoveElement(aRunnable)) { - NS_ERROR("Don't know anything about this runnable!"); - } -} - -nsresult -IndexedDatabaseManager::WaitForOpenAllowed( - const OriginOrPatternString& aOriginOrPattern, - nsIAtom* aId, - nsIRunnable* aRunnable) -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - NS_ASSERTION(!aOriginOrPattern.IsEmpty(), "Empty pattern!"); - NS_ASSERTION(aRunnable, "Null pointer!"); - - nsAutoPtr op(new SynchronizedOp(aOriginOrPattern, aId)); - - // See if this runnable needs to wait. - bool delayed = false; - for (uint32_t index = mSynchronizedOps.Length(); index > 0; index--) { - nsAutoPtr& existingOp = mSynchronizedOps[index - 1]; - if (op->MustWaitFor(*existingOp)) { - existingOp->DelayRunnable(aRunnable); - delayed = true; - break; - } - } - - // Otherwise, dispatch it immediately. - if (!delayed) { - nsresult rv = NS_DispatchToCurrentThread(aRunnable); - NS_ENSURE_SUCCESS(rv, rv); - } - - // Adding this to the synchronized ops list will block any additional - // ops from proceeding until this one is done. - mSynchronizedOps.AppendElement(op.forget()); - - return NS_OK; -} - -void -IndexedDatabaseManager::AllowNextSynchronizedOp( - const OriginOrPatternString& aOriginOrPattern, - nsIAtom* aId) -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - NS_ASSERTION(!aOriginOrPattern.IsEmpty(), "Empty origin/pattern!"); - - uint32_t count = mSynchronizedOps.Length(); - for (uint32_t index = 0; index < count; index++) { - nsAutoPtr& op = mSynchronizedOps[index]; - if (op->mOriginOrPattern.IsOrigin() == aOriginOrPattern.IsOrigin() && - op->mOriginOrPattern == aOriginOrPattern) { - if (op->mId == aId) { - NS_ASSERTION(op->mDatabases.IsEmpty(), "How did this happen?"); - - op->DispatchDelayedRunnables(); - - mSynchronizedOps.RemoveElementAt(index); - return; - } - - // If one or the other is for an origin clear, we should have matched - // solely on origin. - NS_ASSERTION(op->mId && aId, "Why didn't we match earlier?"); - } - } - - NS_NOTREACHED("Why didn't we find a SynchronizedOp?"); -} - -nsresult -IndexedDatabaseManager::AcquireExclusiveAccess( - const nsACString& aPattern, - IDBDatabase* aDatabase, - AsyncConnectionHelper* aHelper, - nsIRunnable* aRunnable, - WaitingOnDatabasesCallback aCallback, - void* aClosure) -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - NS_ASSERTION(!aDatabase || aHelper, "Need a helper with a database!"); - NS_ASSERTION(aDatabase || aRunnable, "Need a runnable without a database!"); - - // Find the right SynchronizedOp. - SynchronizedOp* op = - FindSynchronizedOp(aPattern, aDatabase ? aDatabase->Id() : nullptr); - - NS_ASSERTION(op, "We didn't find a SynchronizedOp?"); - NS_ASSERTION(!op->mHelper, "SynchronizedOp already has a helper?!?"); - NS_ASSERTION(!op->mRunnable, "SynchronizedOp already has a runnable?!?"); - - DatabasePatternMatchArray matches; - matches.Find(mLiveDatabases, aPattern); - - // We need to wait for the databases to go away. - // Hold on to all database objects that represent the same database file - // (except the one that is requesting this version change). - nsTArray > liveDatabases; - - if (!matches.IsEmpty()) { - if (aDatabase) { - // Grab all databases that are not yet closed but whose database id match - // the one we're looking for. - for (uint32_t index = 0; index < matches.Length(); index++) { - IDBDatabase*& database = matches[index]; - if (!database->IsClosed() && - database != aDatabase && - database->Id() == aDatabase->Id()) { - liveDatabases.AppendElement(database); - } - } - } - else { - // We want *all* databases, even those that are closed, if we're going to - // clear the origin. - liveDatabases.AppendElements(matches); - } - } - - op->mHelper = aHelper; - op->mRunnable = aRunnable; - - if (!liveDatabases.IsEmpty()) { - NS_ASSERTION(op->mDatabases.IsEmpty(), - "How do we already have databases here?"); - op->mDatabases.AppendElements(liveDatabases); - - // Give our callback the databases so it can decide what to do with them. - aCallback(liveDatabases, aClosure); - - NS_ASSERTION(liveDatabases.IsEmpty(), - "Should have done something with the array!"); - - if (aDatabase) { - // Wait for those databases to close. - return NS_OK; - } - } - - // If we're trying to open a database and nothing blocks it, or if we're - // clearing an origin, then go ahead and schedule the op. - nsresult rv = RunSynchronizedOp(aDatabase, op); - NS_ENSURE_SUCCESS(rv, rv); - - return NS_OK; -} - -// static -bool -IndexedDatabaseManager::IsShuttingDown() -{ - return !!gShutdown; -} - // static bool IndexedDatabaseManager::IsClosed() @@ -814,307 +279,6 @@ IndexedDatabaseManager::IsClosed() return !!gClosed; } -void -IndexedDatabaseManager::AbortCloseDatabasesForWindow(nsPIDOMWindow* aWindow) -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - NS_ASSERTION(aWindow, "Null pointer!"); - - nsAutoTArray liveDatabases; - mLiveDatabases.EnumerateRead(EnumerateToTArray, - &liveDatabases); - - FileService* service = FileService::Get(); - TransactionThreadPool* pool = TransactionThreadPool::Get(); - - for (uint32_t index = 0; index < liveDatabases.Length(); index++) { - IDBDatabase*& database = liveDatabases[index]; - if (database->GetOwner() == aWindow) { - if (NS_FAILED(database->Close())) { - NS_WARNING("Failed to close database for dying window!"); - } - - if (service) { - service->AbortLockedFilesForStorage(database); - } - - if (pool) { - pool->AbortTransactionsForDatabase(database); - } - } - } -} - -bool -IndexedDatabaseManager::HasOpenTransactions(nsPIDOMWindow* aWindow) -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - NS_ASSERTION(aWindow, "Null pointer!"); - - nsAutoTArray liveDatabases; - mLiveDatabases.EnumerateRead(EnumerateToTArray, - &liveDatabases); - - FileService* service = FileService::Get(); - TransactionThreadPool* pool = TransactionThreadPool::Get(); - if (!service && !pool) { - return false; - } - - for (uint32_t index = 0; index < liveDatabases.Length(); index++) { - IDBDatabase*& database = liveDatabases[index]; - if (database->GetOwner() == aWindow && - ((service && service->HasLockedFilesForStorage(database)) || - (pool && pool->HasTransactionsForDatabase(database)))) { - return true; - } - } - - return false; -} - -void -IndexedDatabaseManager::OnDatabaseClosed(IDBDatabase* aDatabase) -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - NS_ASSERTION(aDatabase, "Null pointer!"); - - // Check through the list of SynchronizedOps to see if any are waiting for - // this database to close before proceeding. - SynchronizedOp* op = FindSynchronizedOp(aDatabase->Origin(), aDatabase->Id()); - if (op) { - // This database is in the scope of this SynchronizedOp. Remove it - // from the list if necessary. - if (op->mDatabases.RemoveElement(aDatabase)) { - // Now set up the helper if there are no more live databases. - NS_ASSERTION(op->mHelper || op->mRunnable, - "How did we get rid of the helper/runnable before " - "removing the last database?"); - if (op->mDatabases.IsEmpty()) { - // At this point, all databases are closed, so no new transactions - // can be started. There may, however, still be outstanding - // transactions that have not completed. We need to wait for those - // before we dispatch the helper. - if (NS_FAILED(RunSynchronizedOp(aDatabase, op))) { - NS_WARNING("Failed to run synchronized op!"); - } - } - } - } -} - -// static -uint32_t -IndexedDatabaseManager::GetIndexedDBQuotaMB() -{ - return uint32_t(std::max(gIndexedDBQuotaMB, 0)); -} - -nsresult -IndexedDatabaseManager::EnsureOriginIsInitialized(const nsACString& aOrigin, - FactoryPrivilege aPrivilege, - nsIFile** aDirectory) -{ -#ifdef DEBUG - { - bool correctThread; - NS_ASSERTION(NS_SUCCEEDED(mIOThread->IsOnCurrentThread(&correctThread)) && - correctThread, - "Running on the wrong thread!"); - } -#endif - - nsCOMPtr directory; - nsresult rv = GetDirectoryForOrigin(aOrigin, getter_AddRefs(directory)); - NS_ENSURE_SUCCESS(rv, rv); - - bool exists; - rv = directory->Exists(&exists); - NS_ENSURE_SUCCESS(rv, rv); - - if (exists) { - bool isDirectory; - rv = directory->IsDirectory(&isDirectory); - NS_ENSURE_SUCCESS(rv, rv); - NS_ENSURE_TRUE(isDirectory, NS_ERROR_UNEXPECTED); - } - else { - rv = directory->Create(nsIFile::DIRECTORY_TYPE, 0755); - NS_ENSURE_SUCCESS(rv, rv); - } - - if (mInitializedOrigins.Contains(aOrigin)) { - NS_ADDREF(*aDirectory = directory); - return NS_OK; - } - - // We need to see if there are any files in the directory already. If they - // are database files then we need to cleanup stored files (if it's needed) - // and also initialize the quota. - - nsAutoTArray subdirsToProcess; - nsAutoTArray , 20> unknownFiles; - - uint64_t usage = 0; - - nsTHashtable validSubdirs; - validSubdirs.Init(20); - - nsCOMPtr entries; - rv = directory->GetDirectoryEntries(getter_AddRefs(entries)); - NS_ENSURE_SUCCESS(rv, rv); - - bool hasMore; - while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { - nsCOMPtr entry; - rv = entries->GetNext(getter_AddRefs(entry)); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr file = do_QueryInterface(entry); - NS_ENSURE_TRUE(file, NS_NOINTERFACE); - - nsString leafName; - rv = file->GetLeafName(leafName); - NS_ENSURE_SUCCESS(rv, rv); - - if (StringEndsWith(leafName, NS_LITERAL_STRING(".sqlite-journal"))) { - continue; - } - - bool isDirectory; - rv = file->IsDirectory(&isDirectory); - NS_ENSURE_SUCCESS(rv, rv); - - if (isDirectory) { - if (!validSubdirs.GetEntry(leafName)) { - subdirsToProcess.AppendElement(leafName); - } - continue; - } - - nsString dbBaseFilename; - if (!GetDatabaseBaseFilename(leafName, dbBaseFilename)) { - unknownFiles.AppendElement(file); - continue; - } - - nsCOMPtr fmDirectory; - rv = directory->Clone(getter_AddRefs(fmDirectory)); - NS_ENSURE_SUCCESS(rv, rv); - - rv = fmDirectory->Append(dbBaseFilename); - NS_ENSURE_SUCCESS(rv, rv); - - rv = FileManager::InitDirectory(fmDirectory, file, aOrigin); - NS_ENSURE_SUCCESS(rv, rv); - - if (aPrivilege != Chrome) { - uint64_t fileUsage; - rv = FileManager::GetUsage(fmDirectory, &fileUsage); - NS_ENSURE_SUCCESS(rv, rv); - - IncrementUsage(&usage, fileUsage); - - int64_t fileSize; - rv = file->GetFileSize(&fileSize); - NS_ENSURE_SUCCESS(rv, rv); - - IncrementUsage(&usage, uint64_t(fileSize)); - } - - validSubdirs.PutEntry(dbBaseFilename); - } - NS_ENSURE_SUCCESS(rv, rv); - - for (uint32_t i = 0; i < subdirsToProcess.Length(); i++) { - const nsString& subdir = subdirsToProcess[i]; - if (!validSubdirs.GetEntry(subdir)) { - NS_WARNING("Unknown subdirectory found!"); - return NS_ERROR_UNEXPECTED; - } - } - - for (uint32_t i = 0; i < unknownFiles.Length(); i++) { - nsCOMPtr& unknownFile = unknownFiles[i]; - - // Some temporary SQLite files could disappear, so we have to check if the - // unknown file still exists. - bool exists; - rv = unknownFile->Exists(&exists); - NS_ENSURE_SUCCESS(rv, rv); - - if (exists) { - nsString leafName; - unknownFile->GetLeafName(leafName); - - // The journal file may exists even after db has been correctly opened. - if (!StringEndsWith(leafName, NS_LITERAL_STRING(".sqlite-journal"))) { - NS_WARNING("Unknown file found!"); - return NS_ERROR_UNEXPECTED; - } - } - } - - if (aPrivilege != Chrome) { - QuotaManager* quotaManager = QuotaManager::Get(); - NS_ASSERTION(quotaManager, "Shouldn't be null!"); - - quotaManager->InitQuotaForOrigin(aOrigin, GetIndexedDBQuotaMB(), usage); - } - - mInitializedOrigins.AppendElement(aOrigin); - - NS_ADDREF(*aDirectory = directory); - return NS_OK; -} - -void -IndexedDatabaseManager::UninitializeOriginsByPattern( - const nsACString& aPattern) -{ -#ifdef DEBUG - { - bool correctThread; - NS_ASSERTION(NS_SUCCEEDED(mIOThread->IsOnCurrentThread(&correctThread)) && - correctThread, - "Running on the wrong thread!"); - } -#endif - - for (int32_t i = mInitializedOrigins.Length() - 1; i >= 0; i--) { - if (PatternMatchesOrigin(aPattern, mInitializedOrigins[i])) { - mInitializedOrigins.RemoveElementAt(i); - } - } -} - -// static -nsresult -IndexedDatabaseManager::GetASCIIOriginFromWindow(nsPIDOMWindow* aWindow, - nsCString& aASCIIOrigin) -{ - NS_ASSERTION(NS_IsMainThread(), - "We're about to touch a window off the main thread!"); - - if (!aWindow) { - aASCIIOrigin.AssignLiteral("chrome"); - NS_ASSERTION(nsContentUtils::IsCallerChrome(), - "Null window but not chrome!"); - return NS_OK; - } - - nsCOMPtr sop = do_QueryInterface(aWindow); - NS_ENSURE_TRUE(sop, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - - nsCOMPtr principal = sop->GetPrincipal(); - NS_ENSURE_TRUE(principal, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - - nsresult rv = GetASCIIOriginFromPrincipal(principal, aASCIIOrigin); - NS_ENSURE_SUCCESS(rv, rv); - - return NS_OK; -} - #ifdef DEBUG //static bool @@ -1145,7 +309,7 @@ IndexedDatabaseManager::GetFileManager(const nsACString& aOrigin, return result.forget(); } } - + return nullptr; } @@ -1163,6 +327,12 @@ IndexedDatabaseManager::AddFileManager(FileManager* aFileManager) array->AppendElement(aFileManager); } +void +IndexedDatabaseManager::InvalidateAllFileManagers() +{ + mFileManagers.Enumerate(InvalidateAndRemoveFileManagers, nullptr); +} + void IndexedDatabaseManager::InvalidateFileManagersForPattern( const nsACString& aPattern) @@ -1204,906 +374,28 @@ IndexedDatabaseManager::AsyncDeleteFile(FileManager* aFileManager, NS_ENSURE_ARG_POINTER(aFileManager); - // See if we're currently clearing the databases for this origin. If so then + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "Shouldn't be null!"); + + // See if we're currently clearing the storages for this origin. If so then // we pretend that we've already deleted everything. - if (IsClearOriginPending(aFileManager->Origin())) { + if (quotaManager->IsClearOriginPending(aFileManager->Origin())) { return NS_OK; } nsRefPtr runnable = new AsyncDeleteFileRunnable(aFileManager, aFileId); - nsresult rv = mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL); + nsresult rv = + quotaManager->IOThread()->Dispatch(runnable, NS_DISPATCH_NORMAL); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } -// static -nsresult -IndexedDatabaseManager::RunSynchronizedOp(IDBDatabase* aDatabase, - SynchronizedOp* aOp) -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - NS_ASSERTION(aOp, "Null pointer!"); - NS_ASSERTION(!aDatabase || aOp->mHelper, "No helper on this op!"); - NS_ASSERTION(aDatabase || aOp->mRunnable, "No runnable on this op!"); - NS_ASSERTION(!aDatabase || aOp->mDatabases.IsEmpty(), - "This op isn't ready to run!"); - - FileService* service = FileService::Get(); - TransactionThreadPool* pool = TransactionThreadPool::Get(); - - nsTArray databases; - if (aDatabase) { - if (service || pool) { - databases.AppendElement(aDatabase); - } - } - else { - aOp->mDatabases.SwapElements(databases); - } - - uint32_t waitCount = service && pool && !databases.IsEmpty() ? 2 : 1; - - nsRefPtr runnable = - new WaitForTransactionsToFinishRunnable(aOp, waitCount); - - // There's no point in delaying if we don't yet have a transaction thread pool - // or a file service. Also, if we're not waiting on any databases then we can - // also run immediately. - if (!(service || pool) || databases.IsEmpty()) { - nsresult rv = runnable->Run(); - NS_ENSURE_SUCCESS(rv, rv); - - return NS_OK; - } - - // Ask each service to call us back when they're done with this database. - if (service) { - // Have to copy here in case the pool needs a list too. - nsTArray > array; - array.AppendElements(databases); - - if (!service->WaitForAllStoragesToComplete(array, runnable)) { - NS_WARNING("Failed to wait for storages to complete!"); - return NS_ERROR_FAILURE; - } - } - - if (pool && !pool->WaitForAllDatabasesToComplete(databases, runnable)) { - NS_WARNING("Failed to wait for databases to complete!"); - return NS_ERROR_FAILURE; - } - - return NS_OK; -} - -IndexedDatabaseManager::SynchronizedOp* -IndexedDatabaseManager::FindSynchronizedOp(const nsACString& aPattern, - nsIAtom* aId) -{ - for (uint32_t index = 0; index < mSynchronizedOps.Length(); index++) { - const nsAutoPtr& currentOp = mSynchronizedOps[index]; - if (PatternMatchesOrigin(aPattern, currentOp->mOriginOrPattern) && - (!currentOp->mId || currentOp->mId == aId)) { - return currentOp; - } - } - - return nullptr; -} - -nsresult -IndexedDatabaseManager::ClearDatabasesForApp(uint32_t aAppId, bool aBrowserOnly) -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - NS_ASSERTION(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID, - "Bad appId!"); - - // This only works from the main process. - NS_ENSURE_TRUE(IsMainProcess(), NS_ERROR_NOT_AVAILABLE); - - nsAutoCString pattern; - GetOriginPatternStringMaybeIgnoreBrowser(aAppId, aBrowserOnly, pattern); - - // If there is a pending or running clear operation for this app, return - // immediately. - if (IsClearOriginPending(pattern)) { - return NS_OK; - } - - OriginOrPatternString oops = OriginOrPatternString::FromPattern(pattern); - - // Queue up the origin clear runnable. - nsRefPtr runnable = new OriginClearRunnable(oops); - - nsresult rv = WaitForOpenAllowed(oops, nullptr, runnable); - NS_ENSURE_SUCCESS(rv, rv); - - runnable->AdvanceState(); - - // Give the runnable some help by invalidating any databases in the way. - DatabasePatternMatchArray matches; - matches.Find(mLiveDatabases, pattern); - - for (uint32_t index = 0; index < matches.Length(); index++) { - // We need to grab references here to prevent the database from dying while - // we invalidate it. - nsRefPtr database = matches[index]; - database->Invalidate(); - } - - return NS_OK; -} - -NS_IMPL_ISUPPORTS2(IndexedDatabaseManager, nsIIndexedDatabaseManager, - nsIObserver) - -NS_IMETHODIMP -IndexedDatabaseManager::GetUsageForURI( - nsIURI* aURI, - nsIIndexedDatabaseUsageCallback* aCallback, - uint32_t aAppId, - bool aInMozBrowserOnly, - uint8_t aOptionalArgCount) -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - - NS_ENSURE_ARG_POINTER(aURI); - NS_ENSURE_ARG_POINTER(aCallback); - - // This only works from the main process. - NS_ENSURE_TRUE(IsMainProcess(), NS_ERROR_NOT_AVAILABLE); - - if (!aOptionalArgCount) { - aAppId = nsIScriptSecurityManager::NO_APP_ID; - } - - // Figure out which origin we're dealing with. - nsCString origin; - nsresult rv = GetASCIIOriginFromURI(aURI, aAppId, aInMozBrowserOnly, origin); - NS_ENSURE_SUCCESS(rv, rv); - - OriginOrPatternString oops = OriginOrPatternString::FromOrigin(origin); - - nsRefPtr runnable = - new AsyncUsageRunnable(aAppId, aInMozBrowserOnly, oops, aURI, aCallback); - - nsRefPtr* newRunnable = - mUsageRunnables.AppendElement(runnable); - NS_ENSURE_TRUE(newRunnable, NS_ERROR_OUT_OF_MEMORY); - - // Otherwise put the computation runnable in the queue. - rv = WaitForOpenAllowed(oops, nullptr, runnable); - NS_ENSURE_SUCCESS(rv, rv); - - runnable->AdvanceState(); - - return NS_OK; -} - -NS_IMETHODIMP -IndexedDatabaseManager::CancelGetUsageForURI( - nsIURI* aURI, - nsIIndexedDatabaseUsageCallback* aCallback, - uint32_t aAppId, - bool aInMozBrowserOnly, - uint8_t aOptionalArgCount) -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - - NS_ENSURE_ARG_POINTER(aURI); - NS_ENSURE_ARG_POINTER(aCallback); - - // This only works from the main process. - NS_ENSURE_TRUE(IsMainProcess(), NS_ERROR_NOT_AVAILABLE); - - if (!aOptionalArgCount) { - aAppId = nsIScriptSecurityManager::NO_APP_ID; - } - - // See if one of our pending callbacks matches both the URI and the callback - // given. Cancel an remove it if so. - for (uint32_t index = 0; index < mUsageRunnables.Length(); index++) { - nsRefPtr& runnable = mUsageRunnables[index]; - - if (runnable->mAppId == aAppId && - runnable->mInMozBrowserOnly == aInMozBrowserOnly) { - bool equals; - nsresult rv = runnable->mURI->Equals(aURI, &equals); - NS_ENSURE_SUCCESS(rv, rv); - - if (equals && SameCOMIdentity(aCallback, runnable->mCallback)) { - runnable->Cancel(); - break; - } - } - } - return NS_OK; -} - -NS_IMETHODIMP -IndexedDatabaseManager::ClearDatabasesForURI(nsIURI* aURI, - uint32_t aAppId, - bool aInMozBrowserOnly, - uint8_t aOptionalArgCount) -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - - NS_ENSURE_ARG_POINTER(aURI); - - // This only works from the main process. - NS_ENSURE_TRUE(IsMainProcess(), NS_ERROR_NOT_AVAILABLE); - - if (!aOptionalArgCount) { - aAppId = nsIScriptSecurityManager::NO_APP_ID; - } - - // Figure out which origin we're dealing with. - nsCString origin; - nsresult rv = GetASCIIOriginFromURI(aURI, aAppId, aInMozBrowserOnly, origin); - NS_ENSURE_SUCCESS(rv, rv); - - nsAutoCString pattern; - GetOriginPatternString(aAppId, aInMozBrowserOnly, origin, pattern); - - // If there is a pending or running clear operation for this origin, return - // immediately. - if (IsClearOriginPending(pattern)) { - return NS_OK; - } - - OriginOrPatternString oops = OriginOrPatternString::FromPattern(pattern); - - // Queue up the origin clear runnable. - nsRefPtr runnable = new OriginClearRunnable(oops); - - rv = WaitForOpenAllowed(oops, nullptr, runnable); - NS_ENSURE_SUCCESS(rv, rv); - - runnable->AdvanceState(); - - // Give the runnable some help by invalidating any databases in the way. - DatabasePatternMatchArray matches; - matches.Find(mLiveDatabases, pattern); - - for (uint32_t index = 0; index < matches.Length(); index++) { - // We need to grab references to any live databases here to prevent them - // from dying while we invalidate them. - nsRefPtr database = matches[index]; - database->Invalidate(); - } - - // After everything has been invalidated the helper should be dispatched to - // the end of the event queue. - return NS_OK; -} - -NS_IMETHODIMP -IndexedDatabaseManager::Observe(nsISupports* aSubject, - const char* aTopic, - const PRUnichar* aData) -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - - if (!strcmp(aTopic, PROFILE_BEFORE_CHANGE_OBSERVER_ID)) { - // Setting this flag prevents the service from being recreated and prevents - // further databases from being created. - if (PR_ATOMIC_SET(&gShutdown, 1)) { - NS_ERROR("Shutdown more than once?!"); - } - - if (sIsMainProcess) { - FileService* service = FileService::Get(); - if (service) { - // This should only wait for IDB databases (file storages) to complete. - // Other file storages may still have running locked files. - // If the necko service (thread pool) gets the shutdown notification - // first then the sync loop won't be processed at all, otherwise it will - // lock the main thread until all IDB file storages are finished. - - nsTArray > - liveDatabases(mLiveDatabases.Count()); - mLiveDatabases.EnumerateRead( - EnumerateToTArray >, - &liveDatabases); - - if (!liveDatabases.IsEmpty()) { - nsRefPtr runnable = - new WaitForLockedFilesToFinishRunnable(); - - if (!service->WaitForAllStoragesToComplete(liveDatabases, - runnable)) { - NS_WARNING("Failed to wait for databases to complete!"); - } - - nsIThread* thread = NS_GetCurrentThread(); - while (runnable->IsBusy()) { - if (!NS_ProcessNextEvent(thread)) { - NS_ERROR("Failed to process next event!"); - break; - } - } - } - } - - // Make sure to join with our IO thread. - if (NS_FAILED(mIOThread->Shutdown())) { - NS_WARNING("Failed to shutdown IO thread!"); - } - - // Kick off the shutdown timer. - if (NS_FAILED(mShutdownTimer->Init(this, DEFAULT_SHUTDOWN_TIMER_MS, - nsITimer::TYPE_ONE_SHOT))) { - NS_WARNING("Failed to initialize shutdown timer!"); - } - - // This will spin the event loop while we wait on all the database threads - // to close. Our timer may fire during that loop. - TransactionThreadPool::Shutdown(); - - // Cancel the timer regardless of whether it actually fired. - if (NS_FAILED(mShutdownTimer->Cancel())) { - NS_WARNING("Failed to cancel shutdown timer!"); - } - } - - mFileManagers.Enumerate(InvalidateAndRemoveFileManagers, nullptr); - - if (PR_ATOMIC_SET(&gClosed, 1)) { - NS_ERROR("Close more than once?!"); - } - - return NS_OK; - } - - if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC)) { - NS_ASSERTION(sIsMainProcess, "Should only happen in the main process!"); - - NS_WARNING("Some database operations are taking longer than expected " - "during shutdown and will be aborted!"); - - // Grab all live databases, for all origins. - nsAutoTArray liveDatabases; - mLiveDatabases.EnumerateRead(EnumerateToTArray, - &liveDatabases); - - // Invalidate them all. - if (!liveDatabases.IsEmpty()) { - uint32_t count = liveDatabases.Length(); - for (uint32_t index = 0; index < count; index++) { - liveDatabases[index]->Invalidate(); - } - } - - return NS_OK; - } - - if (!strcmp(aTopic, TOPIC_WEB_APP_CLEAR_DATA)) { - nsCOMPtr params = - do_QueryInterface(aSubject); - NS_ENSURE_TRUE(params, NS_ERROR_UNEXPECTED); - - uint32_t appId; - nsresult rv = params->GetAppId(&appId); - NS_ENSURE_SUCCESS(rv, rv); - - bool browserOnly; - rv = params->GetBrowserOnly(&browserOnly); - NS_ENSURE_SUCCESS(rv, rv); - - rv = ClearDatabasesForApp(appId, browserOnly); - NS_ENSURE_SUCCESS(rv, rv); - - return NS_OK; - } - - NS_NOTREACHED("Unknown topic!"); - return NS_ERROR_UNEXPECTED; -} - -NS_IMPL_THREADSAFE_ISUPPORTS1(IndexedDatabaseManager::OriginClearRunnable, - nsIRunnable) - -// static -void -IndexedDatabaseManager:: -OriginClearRunnable::InvalidateOpenedDatabases( - nsTArray >& aDatabases, - void* aClosure) -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - - nsTArray > databases; - databases.SwapElements(aDatabases); - - for (uint32_t index = 0; index < databases.Length(); index++) { - databases[index]->Invalidate(); - } -} - -void -IndexedDatabaseManager:: -OriginClearRunnable::DeleteFiles(IndexedDatabaseManager* aManager) -{ - NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); - NS_ASSERTION(aManager, "Don't pass me null!"); - - nsresult rv; - - nsCOMPtr directory = - do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); - NS_ENSURE_SUCCESS_VOID(rv); - - rv = directory->InitWithPath(aManager->GetBaseDirectory()); - NS_ENSURE_SUCCESS_VOID(rv); - - nsCOMPtr entries; - if (NS_FAILED(directory->GetDirectoryEntries(getter_AddRefs(entries))) || - !entries) { - return; - } - - nsCString originSanitized(mOriginOrPattern); - SanitizeOriginString(originSanitized); - - bool hasMore; - while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { - nsCOMPtr entry; - rv = entries->GetNext(getter_AddRefs(entry)); - NS_ENSURE_SUCCESS_VOID(rv); - - nsCOMPtr file = do_QueryInterface(entry); - NS_ASSERTION(file, "Don't know what this is!"); - - bool isDirectory; - rv = file->IsDirectory(&isDirectory); - NS_ENSURE_SUCCESS_VOID(rv); - - if (!isDirectory) { - NS_WARNING("Something in the IndexedDB directory that doesn't belong!"); - continue; - } - - nsString leafName; - rv = file->GetLeafName(leafName); - NS_ENSURE_SUCCESS_VOID(rv); - - // Skip databases for other apps. - if (!PatternMatchesOrigin(originSanitized, - NS_ConvertUTF16toUTF8(leafName))) { - continue; - } - - if (NS_FAILED(file->Remove(true))) { - // This should never fail if we've closed all database connections - // correctly... - NS_ERROR("Failed to remove directory!"); - } - - QuotaManager* quotaManager = QuotaManager::Get(); - NS_ASSERTION(quotaManager, "Shouldn't be null!"); - - quotaManager->RemoveQuotaForPattern(mOriginOrPattern); - - aManager->UninitializeOriginsByPattern(mOriginOrPattern); - } -} - -NS_IMETHODIMP -IndexedDatabaseManager::OriginClearRunnable::Run() -{ - IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); - NS_ASSERTION(mgr, "This should never fail!"); - - switch (mCallbackState) { - case Pending: { - NS_NOTREACHED("Should never get here without being dispatched!"); - return NS_ERROR_UNEXPECTED; - } - - case OpenAllowed: { - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - - AdvanceState(); - - // Now we have to wait until the thread pool is done with all of the - // databases we care about. - nsresult rv = mgr->AcquireExclusiveAccess(mOriginOrPattern, this, - InvalidateOpenedDatabases, - nullptr); - NS_ENSURE_SUCCESS(rv, rv); - - return NS_OK; - } - - case IO: { - NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); - - AdvanceState(); - - DeleteFiles(mgr); - - // Now dispatch back to the main thread. - if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) { - NS_WARNING("Failed to dispatch to main thread!"); - return NS_ERROR_FAILURE; - } - - return NS_OK; - } - - case Complete: { - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - - mgr->InvalidateFileManagersForPattern(mOriginOrPattern); - - // Tell the IndexedDatabaseManager that we're done. - mgr->AllowNextSynchronizedOp(mOriginOrPattern, nullptr); - - return NS_OK; - } - - default: - NS_ERROR("Unknown state value!"); - return NS_ERROR_UNEXPECTED; - } - - NS_NOTREACHED("Should never get here!"); - return NS_ERROR_UNEXPECTED; -} - -IndexedDatabaseManager::AsyncUsageRunnable::AsyncUsageRunnable( - uint32_t aAppId, - bool aInMozBrowserOnly, - const OriginOrPatternString& aOrigin, - nsIURI* aURI, - nsIIndexedDatabaseUsageCallback* aCallback) -: mURI(aURI), - mCallback(aCallback), - mUsage(0), - mFileUsage(0), - mAppId(aAppId), - mCanceled(0), - mOrigin(aOrigin), - mCallbackState(Pending), - mInMozBrowserOnly(aInMozBrowserOnly) -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - NS_ASSERTION(aURI, "Null pointer!"); - NS_ASSERTION(aOrigin.IsOrigin(), "Expect origin only here!"); - NS_ASSERTION(!aOrigin.IsEmpty(), "Empty origin!"); - NS_ASSERTION(aCallback, "Null pointer!"); -} - -void -IndexedDatabaseManager::AsyncUsageRunnable::Cancel() -{ - if (PR_ATOMIC_SET(&mCanceled, 1)) { - NS_ERROR("Canceled more than once?!"); - } -} - -nsresult -IndexedDatabaseManager::AsyncUsageRunnable::TakeShortcut() -{ - NS_ASSERTION(mCallbackState == Pending, "Huh?"); - - nsresult rv = NS_DispatchToCurrentThread(this); - NS_ENSURE_SUCCESS(rv, rv); - - mCallbackState = Shortcut; - return NS_OK; -} - -nsresult -IndexedDatabaseManager::AsyncUsageRunnable::RunInternal() -{ - IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); - NS_ASSERTION(mgr, "This should never fail!"); - - switch (mCallbackState) { - case Pending: { - NS_NOTREACHED("Should never get here without being dispatched!"); - return NS_ERROR_UNEXPECTED; - } - - case OpenAllowed: { - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - - AdvanceState(); - - if (NS_FAILED(mgr->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL))) { - NS_WARNING("Failed to dispatch to the IO thread!"); - } - - return NS_OK; - } - - case IO: { - NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); - - AdvanceState(); - - // Get the directory that contains all the database files we care about. - nsCOMPtr directory; - nsresult rv = mgr->GetDirectoryForOrigin(mOrigin, getter_AddRefs(directory)); - NS_ENSURE_SUCCESS(rv, rv); - - bool exists; - rv = directory->Exists(&exists); - NS_ENSURE_SUCCESS(rv, rv); - - // If the directory exists then enumerate all the files inside, adding up the - // sizes to get the final usage statistic. - if (exists && !mCanceled) { - rv = GetUsageForDirectory(directory, &mUsage); - NS_ENSURE_SUCCESS(rv, rv); - } - - // Run dispatches us back to the main thread. - return NS_OK; - } - - case Complete: // Fall through - case Shortcut: { - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - - // Call the callback unless we were canceled. - if (!mCanceled) { - uint64_t usage = mUsage; - IncrementUsage(&usage, mFileUsage); - mCallback->OnUsageResult(mURI, usage, mFileUsage, mAppId, - mInMozBrowserOnly); - } - - // Clean up. - mURI = nullptr; - mCallback = nullptr; - - // And tell the IndexedDatabaseManager that we're done. - mgr->OnUsageCheckComplete(this); - if (mCallbackState == Complete) { - mgr->AllowNextSynchronizedOp(mOrigin, nullptr); - } - - return NS_OK; - } - - default: - NS_ERROR("Unknown state value!"); - return NS_ERROR_UNEXPECTED; - } - - NS_NOTREACHED("Should never get here!"); - return NS_ERROR_UNEXPECTED; -} - -nsresult -IndexedDatabaseManager::AsyncUsageRunnable::GetUsageForDirectory( - nsIFile* aDirectory, - uint64_t* aUsage) -{ - NS_ASSERTION(aDirectory, "Null pointer!"); - NS_ASSERTION(aUsage, "Null pointer!"); - - nsCOMPtr entries; - nsresult rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries)); - NS_ENSURE_SUCCESS(rv, rv); - - if (!entries) { - return NS_OK; - } - - bool hasMore; - while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && - hasMore && !mCanceled) { - nsCOMPtr entry; - rv = entries->GetNext(getter_AddRefs(entry)); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr file(do_QueryInterface(entry)); - NS_ASSERTION(file, "Don't know what this is!"); - - bool isDirectory; - rv = file->IsDirectory(&isDirectory); - NS_ENSURE_SUCCESS(rv, rv); - - if (isDirectory) { - if (aUsage == &mFileUsage) { - NS_WARNING("Unknown directory found!"); - } - else { - rv = GetUsageForDirectory(file, &mFileUsage); - NS_ENSURE_SUCCESS(rv, rv); - } - - continue; - } - - int64_t fileSize; - rv = file->GetFileSize(&fileSize); - NS_ENSURE_SUCCESS(rv, rv); - - NS_ASSERTION(fileSize >= 0, "Negative size?!"); - - IncrementUsage(aUsage, uint64_t(fileSize)); - } - NS_ENSURE_SUCCESS(rv, rv); - - return NS_OK; -} - -NS_IMPL_THREADSAFE_ISUPPORTS1(IndexedDatabaseManager::AsyncUsageRunnable, - nsIRunnable) - -NS_IMETHODIMP -IndexedDatabaseManager::AsyncUsageRunnable::Run() -{ - nsresult rv = RunInternal(); - - if (!NS_IsMainThread()) { - if (NS_FAILED(rv)) { - mUsage = 0; - } - - if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) { - NS_WARNING("Failed to dispatch to main thread!"); - } - } - - return NS_OK; -} - -NS_IMPL_THREADSAFE_ISUPPORTS1( - IndexedDatabaseManager::WaitForTransactionsToFinishRunnable, - nsIRunnable) - -NS_IMETHODIMP -IndexedDatabaseManager::WaitForTransactionsToFinishRunnable::Run() -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - NS_ASSERTION(mOp, "Null op!"); - NS_ASSERTION(mOp->mHelper || mOp->mRunnable, "Nothing to run!"); - NS_ASSERTION(mCountdown, "Wrong countdown!"); - - if (--mCountdown) { - return NS_OK; - } - - // Don't hold the callback alive longer than necessary. - nsRefPtr helper; - helper.swap(mOp->mHelper); - - nsCOMPtr runnable; - runnable.swap(mOp->mRunnable); - - mOp = nullptr; - - nsresult rv; - - if (helper && helper->HasTransaction()) { - // If the helper has a transaction, dispatch it to the transaction - // threadpool. - rv = helper->DispatchToTransactionPool(); - NS_ENSURE_SUCCESS(rv, rv); - } - else { - // Otherwise, dispatch it to the IO thread. - IndexedDatabaseManager* manager = IndexedDatabaseManager::Get(); - NS_ASSERTION(manager, "We should definitely have a manager here"); - - nsIEventTarget* target = manager->IOThread(); - - rv = helper ? - helper->Dispatch(target) : - target->Dispatch(runnable, NS_DISPATCH_NORMAL); - NS_ENSURE_SUCCESS(rv, rv); - } - - // The helper or runnable is responsible for calling - // IndexedDatabaseManager::AllowNextSynchronizedOp. - return NS_OK; -} - -NS_IMPL_THREADSAFE_ISUPPORTS1( - IndexedDatabaseManager::WaitForLockedFilesToFinishRunnable, - nsIRunnable) - -NS_IMETHODIMP -IndexedDatabaseManager::WaitForLockedFilesToFinishRunnable::Run() -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - - mBusy = false; - - return NS_OK; -} - -IndexedDatabaseManager:: -SynchronizedOp::SynchronizedOp(const OriginOrPatternString& aOriginOrPattern, - nsIAtom* aId) -: mOriginOrPattern(aOriginOrPattern), mId(aId) -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - MOZ_COUNT_CTOR(IndexedDatabaseManager::SynchronizedOp); -} - -IndexedDatabaseManager::SynchronizedOp::~SynchronizedOp() -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - MOZ_COUNT_DTOR(IndexedDatabaseManager::SynchronizedOp); -} - -bool -IndexedDatabaseManager:: -SynchronizedOp::MustWaitFor(const SynchronizedOp& aExistingOp) -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - - bool match; - - if (aExistingOp.mOriginOrPattern.IsOrigin()) { - if (mOriginOrPattern.IsOrigin()) { - match = aExistingOp.mOriginOrPattern.Equals(mOriginOrPattern); - } - else { - match = PatternMatchesOrigin(mOriginOrPattern, aExistingOp.mOriginOrPattern); - } - } - else if (mOriginOrPattern.IsOrigin()) { - match = PatternMatchesOrigin(aExistingOp.mOriginOrPattern, mOriginOrPattern); - } - else { - match = PatternMatchesOrigin(mOriginOrPattern, aExistingOp.mOriginOrPattern) || - PatternMatchesOrigin(aExistingOp.mOriginOrPattern, mOriginOrPattern); - } - - // If the origins don't match, the second can proceed. - if (!match) { - return false; - } - - // If the origins and the ids match, the second must wait. - if (aExistingOp.mId == mId) { - return true; - } - - // Waiting is required if either one corresponds to an origin clearing - // (a null Id). - if (!aExistingOp.mId || !mId) { - return true; - } - - // Otherwise, things for the same origin but different databases can proceed - // independently. - return false; -} - -void -IndexedDatabaseManager::SynchronizedOp::DelayRunnable(nsIRunnable* aRunnable) -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - NS_ASSERTION(mDelayedRunnables.IsEmpty() || !mId, - "Only ClearOrigin operations can delay multiple runnables!"); - - mDelayedRunnables.AppendElement(aRunnable); -} - -void -IndexedDatabaseManager::SynchronizedOp::DispatchDelayedRunnables() -{ - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - NS_ASSERTION(!mHelper, "Any helper should be gone by now!"); - - uint32_t count = mDelayedRunnables.Length(); - for (uint32_t index = 0; index < count; index++) { - NS_DispatchToCurrentThread(mDelayedRunnables[index]); - } - - mDelayedRunnables.Clear(); -} +NS_IMPL_ADDREF(IndexedDatabaseManager) +NS_IMPL_RELEASE_WITH_DESTROY(IndexedDatabaseManager, Destroy()) +NS_IMPL_QUERY_INTERFACE1(IndexedDatabaseManager, nsIIndexedDatabaseManager) NS_IMETHODIMP IndexedDatabaseManager::InitWindowless(const jsval& aObj, JSContext* aCx) @@ -2162,18 +454,17 @@ IndexedDatabaseManager::InitWindowless(const jsval& aObj, JSContext* aCx) return NS_OK; } -IndexedDatabaseManager:: AsyncDeleteFileRunnable::AsyncDeleteFileRunnable(FileManager* aFileManager, int64_t aFileId) : mFileManager(aFileManager), mFileId(aFileId) { } -NS_IMPL_THREADSAFE_ISUPPORTS1(IndexedDatabaseManager::AsyncDeleteFileRunnable, +NS_IMPL_THREADSAFE_ISUPPORTS1(AsyncDeleteFileRunnable, nsIRunnable) NS_IMETHODIMP -IndexedDatabaseManager::AsyncDeleteFileRunnable::Run() +AsyncDeleteFileRunnable::Run() { NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); diff --git a/dom/indexedDB/IndexedDatabaseManager.h b/dom/indexedDB/IndexedDatabaseManager.h index 57311e52f6f8..966cd44e3721 100644 --- a/dom/indexedDB/IndexedDatabaseManager.h +++ b/dom/indexedDB/IndexedDatabaseManager.h @@ -9,23 +9,15 @@ #include "mozilla/dom/indexedDB/IndexedDatabase.h" -#include "mozilla/Mutex.h" - #include "nsIIndexedDatabaseManager.h" -#include "nsIObserver.h" -#include "nsIRunnable.h" -#include "nsIThread.h" -#include "nsIURI.h" +#include "mozilla/Mutex.h" #include "nsClassHashtable.h" -#include "nsRefPtrHashtable.h" #include "nsHashKeys.h" #define INDEXEDDB_MANAGER_CONTRACTID "@mozilla.org/dom/indexeddb/manager;1" class nsIAtom; -class nsIFile; -class nsITimer; class nsPIDOMWindow; class nsEventChainPostVisitor; @@ -35,96 +27,30 @@ class TabContext; } } - BEGIN_INDEXEDDB_NAMESPACE -class AsyncConnectionHelper; class FileManager; -class IDBDatabase; -class IndexedDatabaseManager MOZ_FINAL : public nsIIndexedDatabaseManager, - public nsIObserver +class IndexedDatabaseManager MOZ_FINAL : public nsIIndexedDatabaseManager { - friend class IDBDatabase; - public: - static already_AddRefed GetOrCreate(); - - // Returns a non-owning reference. - static IndexedDatabaseManager* Get(); - - // Returns an owning reference! No one should call this but the factory. - static IndexedDatabaseManager* FactoryCreate(); - NS_DECL_ISUPPORTS NS_DECL_NSIINDEXEDDATABASEMANAGER - NS_DECL_NSIOBSERVER - // Waits for databases to be cleared and for version change transactions to - // complete before dispatching the given runnable. - nsresult WaitForOpenAllowed(const OriginOrPatternString& aOriginOrPattern, - nsIAtom* aId, - nsIRunnable* aRunnable); + // Returns a non-owning reference. + static IndexedDatabaseManager* + GetOrCreate(); - void AllowNextSynchronizedOp(const OriginOrPatternString& aOriginOrPattern, - nsIAtom* aId); + // Returns a non-owning reference. + static IndexedDatabaseManager* + Get(); - nsIThread* IOThread() - { - NS_ASSERTION(mIOThread, "This should never be null!"); - return mIOThread; - } + // Returns an owning reference! No one should call this but the factory. + static IndexedDatabaseManager* + FactoryCreate(); - // Returns true if we've begun the shutdown process. - static bool IsShuttingDown(); - - static bool IsClosed(); - - typedef void - (*WaitingOnDatabasesCallback)(nsTArray >&, void*); - - // Acquire exclusive access to the database given (waits for all others to - // close). If databases need to close first, the callback will be invoked - // with an array of said databases. - nsresult AcquireExclusiveAccess(IDBDatabase* aDatabase, - const nsACString& aOrigin, - AsyncConnectionHelper* aHelper, - WaitingOnDatabasesCallback aCallback, - void* aClosure) - { - NS_ASSERTION(aDatabase, "Need a DB here!"); - return AcquireExclusiveAccess(aOrigin, aDatabase, aHelper, nullptr, - aCallback, aClosure); - } - - nsresult AcquireExclusiveAccess(const nsACString& aOrigin, - nsIRunnable* aRunnable, - WaitingOnDatabasesCallback aCallback, - void* aClosure) - { - return AcquireExclusiveAccess(aOrigin, nullptr, nullptr, aRunnable, aCallback, - aClosure); - } - - // Called when a window is being purged from the bfcache or the user leaves - // a page which isn't going into the bfcache. Forces any live database - // objects to close themselves and aborts any running transactions. - void AbortCloseDatabasesForWindow(nsPIDOMWindow* aWindow); - - // Used to check if there are running transactions in a given window. - bool HasOpenTransactions(nsPIDOMWindow* aWindow); - - static uint32_t - GetIndexedDBQuotaMB(); - - nsresult EnsureOriginIsInitialized(const nsACString& aOrigin, - FactoryPrivilege aPrivilege, - nsIFile** aDirectory); - - void UninitializeOriginsByPattern(const nsACString& aPattern); - - static nsresult - GetASCIIOriginFromWindow(nsPIDOMWindow* aWindow, nsCString& aASCIIOrigin); + static bool + IsClosed(); static bool IsMainProcess() @@ -143,25 +69,22 @@ public: void AddFileManager(FileManager* aFileManager); - void InvalidateFileManagersForPattern(const nsACString& aPattern); + void + InvalidateAllFileManagers(); - void InvalidateFileManager(const nsACString& aOrigin, - const nsAString& aDatabaseName); + void + InvalidateFileManagersForPattern(const nsACString& aPattern); - nsresult AsyncDeleteFile(FileManager* aFileManager, - int64_t aFileId); - - const nsString& - GetBaseDirectory() const - { - return mDatabaseBasePath; - } + void + InvalidateFileManager(const nsACString& aOrigin, + const nsAString& aDatabaseName); nsresult - GetDirectoryForOrigin(const nsACString& aASCIIOrigin, - nsIFile** aDirectory) const; + AsyncDeleteFile(FileManager* aFileManager, + int64_t aFileId); - static mozilla::Mutex& FileMutex() + static mozilla::Mutex& + FileMutex() { IndexedDatabaseManager* mgr = Get(); NS_ASSERTION(mgr, "Must have a manager here!"); @@ -169,10 +92,6 @@ public: return mgr->mFileMutex; } - static already_AddRefed - GetDatabaseId(const nsACString& aOrigin, - const nsAString& aName); - static nsresult FireWindowOnError(nsPIDOMWindow* aOwner, nsEventChainPostVisitor& aVisitor); @@ -185,288 +104,22 @@ private: IndexedDatabaseManager(); ~IndexedDatabaseManager(); - nsresult AcquireExclusiveAccess(const nsACString& aOrigin, - IDBDatabase* aDatabase, - AsyncConnectionHelper* aHelper, - nsIRunnable* aRunnable, - WaitingOnDatabasesCallback aCallback, - void* aClosure); + nsresult + Init(); - // Called when a database is created. - bool RegisterDatabase(IDBDatabase* aDatabase); - - // Called when a database is being unlinked or destroyed. - void UnregisterDatabase(IDBDatabase* aDatabase); - - // Called when a database has been closed. - void OnDatabaseClosed(IDBDatabase* aDatabase); - - nsresult ClearDatabasesForApp(uint32_t aAppId, bool aBrowserOnly); - - // Responsible for clearing the database files for a particular origin on the - // IO thread. Created when nsIIDBIndexedDatabaseManager::ClearDatabasesForURI - // is called. Runs three times, first on the main thread, next on the IO - // thread, and then finally again on the main thread. While on the IO thread - // the runnable will actually remove the origin's database files and the - // directory that contains them before dispatching itself back to the main - // thread. When back on the main thread the runnable will notify the - // IndexedDatabaseManager that the job has been completed. - class OriginClearRunnable MOZ_FINAL : public nsIRunnable - { - enum CallbackState { - // Not yet run. - Pending = 0, - - // Running on the main thread in the callback for OpenAllowed. - OpenAllowed, - - // Running on the IO thread. - IO, - - // Running on the main thread after all work is done. - Complete - }; - - public: - NS_DECL_ISUPPORTS - NS_DECL_NSIRUNNABLE - - OriginClearRunnable(const OriginOrPatternString& aOriginOrPattern) - : mOriginOrPattern(aOriginOrPattern), - mCallbackState(Pending) - { } - - void AdvanceState() - { - switch (mCallbackState) { - case Pending: - mCallbackState = OpenAllowed; - return; - case OpenAllowed: - mCallbackState = IO; - return; - case IO: - mCallbackState = Complete; - return; - default: - NS_NOTREACHED("Can't advance past Complete!"); - } - } - - static void InvalidateOpenedDatabases( - nsTArray >& aDatabases, - void* aClosure); - - void DeleteFiles(IndexedDatabaseManager* aManager); - - private: - OriginOrPatternString mOriginOrPattern; - CallbackState mCallbackState; - }; - - // Responsible for calculating the amount of space taken up by databases of a - // certain origin. Created when nsIIDBIndexedDatabaseManager::GetUsageForURI - // is called. May be canceled with - // nsIIDBIndexedDatabaseManager::CancelGetUsageForURI. Runs twice, first on - // the IO thread, then again on the main thread. While on the IO thread the - // runnable will calculate the size of all files in the origin's directory - // before dispatching itself back to the main thread. When on the main thread - // the runnable will call the callback and then notify the - // IndexedDatabaseManager that the job has been completed. - class AsyncUsageRunnable MOZ_FINAL : public nsIRunnable - { - enum CallbackState { - // Not yet run. - Pending = 0, - - // Running on the main thread in the callback for OpenAllowed. - OpenAllowed, - - // Running on the IO thread. - IO, - - // Running on the main thread after all work is done. - Complete, - - // Running on the main thread after skipping the work - Shortcut - }; - - public: - NS_DECL_ISUPPORTS - NS_DECL_NSIRUNNABLE - - AsyncUsageRunnable(uint32_t aAppId, - bool aInMozBrowserOnly, - const OriginOrPatternString& aOrigin, - nsIURI* aURI, - nsIIndexedDatabaseUsageCallback* aCallback); - - // Sets the canceled flag so that the callback is never called. - void Cancel(); - - void AdvanceState() - { - switch (mCallbackState) { - case Pending: - mCallbackState = OpenAllowed; - return; - case OpenAllowed: - mCallbackState = IO; - return; - case IO: - mCallbackState = Complete; - return; - default: - NS_NOTREACHED("Can't advance past Complete!"); - } - } - - nsresult TakeShortcut(); - - // Run calls the RunInternal method and makes sure that we always dispatch - // to the main thread in case of an error. - inline nsresult RunInternal(); - - nsresult GetUsageForDirectory(nsIFile* aDirectory, - uint64_t* aUsage); - - nsCOMPtr mURI; - nsCOMPtr mCallback; - uint64_t mUsage; - uint64_t mFileUsage; - uint32_t mAppId; - int32_t mCanceled; - OriginOrPatternString mOrigin; - CallbackState mCallbackState; - bool mInMozBrowserOnly; - }; - - // Called when AsyncUsageRunnable has finished its Run() method. - inline void OnUsageCheckComplete(AsyncUsageRunnable* aRunnable); - - // A struct that contains the information corresponding to a pending or - // running operation that requires synchronization (e.g. opening a db, - // clearing dbs for an origin, etc). - struct SynchronizedOp - { - SynchronizedOp(const OriginOrPatternString& aOriginOrPattern, - nsIAtom* aId); - ~SynchronizedOp(); - - // Test whether this SynchronizedOp needs to wait for the given op. - bool MustWaitFor(const SynchronizedOp& aOp); - - void DelayRunnable(nsIRunnable* aRunnable); - void DispatchDelayedRunnables(); - - const OriginOrPatternString mOriginOrPattern; - nsCOMPtr mId; - nsRefPtr mHelper; - nsCOMPtr mRunnable; - nsTArray > mDelayedRunnables; - nsTArray mDatabases; - }; - - // A callback runnable used by the TransactionPool when it's safe to proceed - // with a SetVersion/DeleteDatabase/etc. - class WaitForTransactionsToFinishRunnable MOZ_FINAL : public nsIRunnable - { - public: - WaitForTransactionsToFinishRunnable(SynchronizedOp* aOp, - uint32_t aCountdown) - : mOp(aOp), mCountdown(aCountdown) - { - NS_ASSERTION(mOp, "Why don't we have a runnable?"); - NS_ASSERTION(mOp->mDatabases.IsEmpty(), "We're here too early!"); - NS_ASSERTION(mOp->mHelper || mOp->mRunnable, - "What are we supposed to do when we're done?"); - NS_ASSERTION(mCountdown, "Wrong countdown!"); - } - - NS_DECL_ISUPPORTS - NS_DECL_NSIRUNNABLE - - private: - // The IndexedDatabaseManager holds this alive. - SynchronizedOp* mOp; - uint32_t mCountdown; - }; - - class WaitForLockedFilesToFinishRunnable MOZ_FINAL : public nsIRunnable - { - public: - WaitForLockedFilesToFinishRunnable() - : mBusy(true) - { } - - NS_DECL_ISUPPORTS - NS_DECL_NSIRUNNABLE - - bool IsBusy() const - { - return mBusy; - } - - private: - bool mBusy; - }; - - class AsyncDeleteFileRunnable MOZ_FINAL : public nsIRunnable - { - public: - NS_DECL_ISUPPORTS - NS_DECL_NSIRUNNABLE - AsyncDeleteFileRunnable(FileManager* aFileManager, int64_t aFileId); - - private: - nsRefPtr mFileManager; - int64_t mFileId; - }; - - static nsresult RunSynchronizedOp(IDBDatabase* aDatabase, - SynchronizedOp* aOp); - - SynchronizedOp* FindSynchronizedOp(const nsACString& aPattern, - nsIAtom* aId); - - bool IsClearOriginPending(const nsACString& aPattern) - { - return !!FindSynchronizedOp(aPattern, nullptr); - } - - // Maintains a list of live databases per origin. - nsClassHashtable > mLiveDatabases; + void + Destroy(); // Maintains a list of all file managers per origin. This list isn't // protected by any mutex but it is only ever touched on the IO thread. nsClassHashtable > > mFileManagers; - // Maintains a list of origins that we're currently enumerating to gather - // usage statistics. - nsAutoTArray, 1> mUsageRunnables; - - // Maintains a list of synchronized operatons that are in progress or queued. - nsAutoTArray, 5> mSynchronizedOps; - - // Thread on which IO is performed. - nsCOMPtr mIOThread; - - // A timer that gets activated at shutdown to ensure we close all databases. - nsCOMPtr mShutdownTimer; - - // A list of all successfully initialized origins. This list isn't protected - // by any mutex but it is only ever touched on the IO thread. - nsTArray mInitializedOrigins; - // Lock protecting FileManager.mFileInfos and nsDOMFileBase.mFileInfos // It's s also used to atomically update FileInfo.mRefCnt, FileInfo.mDBRefCnt // and FileInfo.mSliceRefCnt mozilla::Mutex mFileMutex; - nsString mDatabaseBasePath; - static bool sIsMainProcess; }; diff --git a/dom/indexedDB/Makefile.in b/dom/indexedDB/Makefile.in index 2b8d8cb8d04d..e0ee95d4c358 100644 --- a/dom/indexedDB/Makefile.in +++ b/dom/indexedDB/Makefile.in @@ -21,6 +21,7 @@ EXPORTS_NAMESPACES = mozilla/dom/indexedDB CPPSRCS = \ AsyncConnectionHelper.cpp \ CheckPermissionsHelper.cpp \ + Client.cpp \ DatabaseInfo.cpp \ FileInfo.cpp \ FileManager.cpp \ @@ -43,7 +44,10 @@ CPPSRCS = \ $(NULL) EXPORTS_mozilla/dom/indexedDB = \ + Client.h \ DatabaseInfo.h \ + FileManager.h \ + FileInfo.h \ IDBCursor.h \ IDBDatabase.h \ IDBEvents.h \ @@ -59,8 +63,6 @@ EXPORTS_mozilla/dom/indexedDB = \ IndexedDatabaseManager.h \ Key.h \ KeyPath.h \ - FileManager.h \ - FileInfo.h \ $(NULL) LOCAL_INCLUDES = \ diff --git a/dom/indexedDB/OpenDatabaseHelper.cpp b/dom/indexedDB/OpenDatabaseHelper.cpp index 5462f16c3aea..3684f1c55083 100644 --- a/dom/indexedDB/OpenDatabaseHelper.cpp +++ b/dom/indexedDB/OpenDatabaseHelper.cpp @@ -8,6 +8,8 @@ #include "nsIFile.h" +#include "mozilla/dom/quota/AcquireListener.h" +#include "mozilla/dom/quota/OriginOrPatternString.h" #include "mozilla/dom/quota/QuotaManager.h" #include "mozilla/storage.h" #include "nsEscape.h" @@ -15,6 +17,7 @@ #include "nsThreadUtils.h" #include "snappy/snappy.h" +#include "Client.h" #include "nsIBFCacheEntry.h" #include "IDBEvents.h" #include "IDBFactory.h" @@ -1323,7 +1326,8 @@ UpgradeSchemaFrom11_0To12_0(mozIStorageConnection* aConnection) class VersionChangeEventsRunnable; class SetVersionHelper : public AsyncConnectionHelper, - public IDBTransactionListener + public IDBTransactionListener, + public AcquireListener { friend class VersionChangeEventsRunnable; @@ -1346,6 +1350,9 @@ public: virtual nsresult GetSuccessResult(JSContext* aCx, jsval* aVal) MOZ_OVERRIDE; + virtual nsresult + OnExclusiveAccessAcquired() MOZ_OVERRIDE; + protected: virtual nsresult Init() MOZ_OVERRIDE; @@ -1393,7 +1400,8 @@ private: uint64_t mCurrentVersion; }; -class DeleteDatabaseHelper : public AsyncConnectionHelper +class DeleteDatabaseHelper : public AsyncConnectionHelper, + public AcquireListener { friend class VersionChangeEventsRunnable; public: @@ -1408,6 +1416,8 @@ public: mASCIIOrigin(aASCIIOrigin) { } + NS_DECL_ISUPPORTS_INHERITED + nsresult GetSuccessResult(JSContext* aCx, jsval* aVal); @@ -1419,6 +1429,9 @@ public: AsyncConnectionHelper::ReleaseMainThreadObjects(); } + virtual nsresult + OnExclusiveAccessAcquired() MOZ_OVERRIDE; + protected: nsresult DoDatabaseWork(mozIStorageConnection* aConnection); nsresult Init(); @@ -1470,11 +1483,11 @@ class VersionChangeEventsRunnable : public nsRunnable { public: VersionChangeEventsRunnable( - IDBDatabase* aRequestingDatabase, - IDBOpenDBRequest* aRequest, - nsTArray >& aWaitingDatabases, - int64_t aOldVersion, - int64_t aNewVersion) + IDBDatabase* aRequestingDatabase, + IDBOpenDBRequest* aRequest, + nsTArray >& aWaitingDatabases, + int64_t aOldVersion, + int64_t aNewVersion) : mRequestingDatabase(aRequestingDatabase), mRequest(aRequest), mOldVersion(aOldVersion), @@ -1497,7 +1510,9 @@ public: // closed. Also kick bfcached documents out of bfcache. uint32_t count = mWaitingDatabases.Length(); for (uint32_t index = 0; index < count; index++) { - nsRefPtr& database = mWaitingDatabases[index]; + IDBDatabase* database = + IDBDatabase::FromStorage(mWaitingDatabases[index]); + NS_ASSERTION(database, "This shouldn't be null!"); if (database->IsClosed()) { continue; @@ -1519,12 +1534,12 @@ public: // We can't kick the document out of the bfcache because it's not yet // fully in the bfcache. Instead we'll abort everything for the window // and mark it as not-bfcacheable. - IndexedDatabaseManager* manager = IndexedDatabaseManager::Get(); - NS_ASSERTION(manager, "Huh?"); - manager->AbortCloseDatabasesForWindow(owner); + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "Huh?"); + quotaManager->AbortCloseStoragesForWindow(owner); NS_ASSERTION(database->IsClosed(), - "AbortCloseDatabasesForWindow should have closed database"); + "AbortCloseStoragesForWindow should have closed database"); ownerDoc->DisallowBFCaching(); continue; } @@ -1559,12 +1574,12 @@ public: template static - void QueueVersionChange(nsTArray >& aDatabases, + void QueueVersionChange(nsTArray >& aDatabases, void* aClosure); private: nsRefPtr mRequestingDatabase; nsRefPtr mRequest; - nsTArray > mWaitingDatabases; + nsTArray > mWaitingDatabases; int64_t mOldVersion; int64_t mNewVersion; }; @@ -1576,7 +1591,7 @@ NS_IMPL_THREADSAFE_ISUPPORTS1(OpenDatabaseHelper, nsIRunnable) nsresult OpenDatabaseHelper::Init() { - mDatabaseId = IndexedDatabaseManager::GetDatabaseId(mASCIIOrigin, mName); + mDatabaseId = QuotaManager::GetStorageId(mASCIIOrigin, mName); NS_ENSURE_TRUE(mDatabaseId, NS_ERROR_FAILURE); return NS_OK; @@ -1609,7 +1624,7 @@ OpenDatabaseHelper::DoDatabaseWork() #ifdef DEBUG { bool correctThread; - NS_ASSERTION(NS_SUCCEEDED(IndexedDatabaseManager::Get()->IOThread()-> + NS_ASSERTION(NS_SUCCEEDED(QuotaManager::Get()->IOThread()-> IsOnCurrentThread(&correctThread)) && correctThread, "Running on the wrong thread!"); @@ -1618,7 +1633,7 @@ OpenDatabaseHelper::DoDatabaseWork() mState = eFiringEvents; // In case we fail somewhere along the line. - if (IndexedDatabaseManager::IsShuttingDown()) { + if (QuotaManager::IsShuttingDown()) { return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } @@ -1631,14 +1646,34 @@ OpenDatabaseHelper::DoDatabaseWork() nsCOMPtr dbDirectory; - IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); - NS_ASSERTION(mgr, "This should never be null!"); + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "This should never be null!"); - nsresult rv = mgr->EnsureOriginIsInitialized(mASCIIOrigin, - mPrivilege, - getter_AddRefs(dbDirectory)); + nsresult rv = + quotaManager->EnsureOriginIsInitialized(mASCIIOrigin, + mPrivilege, + getter_AddRefs(dbDirectory)); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + rv = dbDirectory->Append(NS_LITERAL_STRING(IDB_DIRECTORY_NAME)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + bool exists; + rv = dbDirectory->Exists(&exists); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + if (!exists) { + rv = dbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + } +#ifdef DEBUG + else { + bool isDirectory; + NS_ASSERTION(NS_SUCCEEDED(dbDirectory->IsDirectory(&isDirectory)) && + isDirectory, "Should have caught this earlier!"); + } +#endif + nsAutoString filename; rv = GetDatabaseFilename(mName, filename); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); @@ -1710,6 +1745,9 @@ OpenDatabaseHelper::DoDatabaseWork() mState = eSetVersionPending; } + IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); + NS_ASSERTION(mgr, "This should never be null!"); + nsRefPtr fileManager = mgr->GetFileManager(mASCIIOrigin, mName); if (!fileManager) { fileManager = new FileManager(mASCIIOrigin, mPrivilege, mName); @@ -1917,12 +1955,13 @@ OpenDatabaseHelper::StartSetVersion() new SetVersionHelper(transaction, mOpenDBRequest, this, mRequestedVersion, mCurrentVersion); - IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); - NS_ASSERTION(mgr, "This should never be null!"); + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "This should never be null!"); - rv = mgr->AcquireExclusiveAccess(mDatabase, mDatabase->Origin(), helper, + rv = quotaManager->AcquireExclusiveAccess( + mDatabase, mDatabase->Origin(), helper, &VersionChangeEventsRunnable::QueueVersionChange, - helper); + helper); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); // The SetVersionHelper is responsible for dispatching us back to the @@ -1946,12 +1985,13 @@ OpenDatabaseHelper::StartDelete() new DeleteDatabaseHelper(mOpenDBRequest, this, mCurrentVersion, mName, mASCIIOrigin); - IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); - NS_ASSERTION(mgr, "This should never be null!"); + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "This should never be null!"); - rv = mgr->AcquireExclusiveAccess(mDatabase, mDatabase->Origin(), helper, + rv = quotaManager->AcquireExclusiveAccess( + mDatabase, mDatabase->Origin(), helper, &VersionChangeEventsRunnable::QueueVersionChange, - helper); + helper); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); // The DeleteDatabaseHelper is responsible for dispatching us back to the @@ -2040,10 +2080,10 @@ OpenDatabaseHelper::Run() DispatchSuccessEvent(); } - IndexedDatabaseManager* manager = IndexedDatabaseManager::Get(); - NS_ASSERTION(manager, "This should never be null!"); + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "This should never be null!"); - manager->AllowNextSynchronizedOp( + quotaManager->AllowNextSynchronizedOp( OriginOrPatternString::FromOrigin(mASCIIOrigin), mDatabaseId); @@ -2298,12 +2338,21 @@ SetVersionHelper::GetSuccessResult(JSContext* aCx, aVal); } +nsresult +SetVersionHelper::OnExclusiveAccessAcquired() +{ + nsresult rv = DispatchToTransactionPool(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + // static template void VersionChangeEventsRunnable::QueueVersionChange( - nsTArray >& aDatabases, - void* aClosure) + nsTArray >& aDatabases, + void* aClosure) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(!aDatabases.IsEmpty(), "Why are we here?"); @@ -2366,23 +2415,28 @@ SetVersionHelper::NotifyTransactionPostComplete(IDBTransaction* aTransaction) return rv; } +NS_IMPL_ISUPPORTS_INHERITED0(DeleteDatabaseHelper, AsyncConnectionHelper); + nsresult DeleteDatabaseHelper::DoDatabaseWork(mozIStorageConnection* aConnection) { NS_ASSERTION(!aConnection, "How did we get a connection here?"); - const FactoryPrivilege& privilege = mOpenHelper->Privilege(); + const StoragePrivilege& privilege = mOpenHelper->Privilege(); - IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); - NS_ASSERTION(mgr, "This should never fail!"); + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "This should never fail!"); nsCOMPtr directory; - nsresult rv = mgr->GetDirectoryForOrigin(mASCIIOrigin, - getter_AddRefs(directory)); + nsresult rv = quotaManager->GetDirectoryForOrigin(mASCIIOrigin, + getter_AddRefs(directory)); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); NS_ASSERTION(directory, "What?"); + rv = directory->Append(NS_LITERAL_STRING(IDB_DIRECTORY_NAME)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + nsAutoString filename; rv = GetDatabaseFilename(mName, filename); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); @@ -2460,6 +2514,18 @@ DeleteDatabaseHelper::GetSuccessResult(JSContext* aCx, jsval* aVal) return NS_OK; } +nsresult +DeleteDatabaseHelper::OnExclusiveAccessAcquired() +{ + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "We should definitely have a manager here"); + + nsresult rv = Dispatch(quotaManager->IOThread()); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + nsresult DeleteDatabaseHelper::Init() { diff --git a/dom/indexedDB/OpenDatabaseHelper.h b/dom/indexedDB/OpenDatabaseHelper.h index 5a3d987c3d05..bea3624364a0 100644 --- a/dom/indexedDB/OpenDatabaseHelper.h +++ b/dom/indexedDB/OpenDatabaseHelper.h @@ -24,6 +24,8 @@ BEGIN_INDEXEDDB_NAMESPACE class OpenDatabaseHelper : public HelperBase { + typedef mozilla::dom::quota::StoragePrivilege StoragePrivilege; + public: OpenDatabaseHelper(IDBOpenDBRequest* aRequest, const nsAString& aName, @@ -31,7 +33,7 @@ public: uint64_t aRequestedVersion, bool aForDeletion, mozilla::dom::ContentParent* aContentParent, - FactoryPrivilege aPrivilege) + StoragePrivilege aPrivilege) : HelperBase(aRequest), mOpenDBRequest(aRequest), mName(aName), mASCIIOrigin(aASCIIOrigin), mRequestedVersion(aRequestedVersion), mForDeletion(aForDeletion), mPrivilege(aPrivilege), mDatabaseId(nullptr), @@ -77,7 +79,7 @@ public: return mDatabase; } - const FactoryPrivilege& Privilege() const + const StoragePrivilege& Privilege() const { return mPrivilege; } @@ -109,7 +111,7 @@ protected: nsCString mASCIIOrigin; uint64_t mRequestedVersion; bool mForDeletion; - FactoryPrivilege mPrivilege; + StoragePrivilege mPrivilege; nsCOMPtr mDatabaseId; mozilla::dom::ContentParent* mContentParent; diff --git a/dom/indexedDB/TransactionThreadPool.cpp b/dom/indexedDB/TransactionThreadPool.cpp index 450b3d2e29d3..6420623a6fcc 100644 --- a/dom/indexedDB/TransactionThreadPool.cpp +++ b/dom/indexedDB/TransactionThreadPool.cpp @@ -279,8 +279,7 @@ TransactionThreadPool::GetQueueForTransaction(IDBTransaction* aTransaction) return *info->queue; } - TransactionInfo* transactionInfo = new TransactionInfo(aTransaction, - objectStoreNames); + TransactionInfo* transactionInfo = new TransactionInfo(aTransaction); dbTransactionInfo->transactions.Put(aTransaction, transactionInfo);; @@ -352,31 +351,23 @@ TransactionThreadPool::Dispatch(IDBTransaction* aTransaction, return NS_OK; } -bool -TransactionThreadPool::WaitForAllDatabasesToComplete( - nsTArray& aDatabases, - nsIRunnable* aCallback) +void +TransactionThreadPool::WaitForDatabasesToComplete( + nsTArray& aDatabases, + nsIRunnable* aCallback) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(!aDatabases.IsEmpty(), "No databases to wait on!"); NS_ASSERTION(aCallback, "Null pointer!"); DatabasesCompleteCallback* callback = mCompleteCallbacks.AppendElement(); - if (!callback) { - NS_WARNING("Out of memory!"); - return false; - } callback->mCallback = aCallback; - if (!callback->mDatabases.SwapElements(aDatabases)) { - NS_ERROR("This should never fail!"); - } + callback->mDatabases.SwapElements(aDatabases); if (MaybeFireCallback(*callback)) { mCompleteCallbacks.RemoveElementAt(mCompleteCallbacks.Length() - 1); } - - return true; } // static @@ -430,12 +421,12 @@ TransactionThreadPool::AbortTransactionsForDatabase(IDBDatabase* aDatabase) struct NS_STACK_CLASS TransactionSearchInfo { - TransactionSearchInfo(IDBDatabase* aDatabase) + TransactionSearchInfo(nsIOfflineStorage* aDatabase) : db(aDatabase), found(false) { } - IDBDatabase* db; + nsIOfflineStorage* db; bool found; }; diff --git a/dom/indexedDB/TransactionThreadPool.h b/dom/indexedDB/TransactionThreadPool.h index 62768016b390..82d1ad3ca90b 100644 --- a/dom/indexedDB/TransactionThreadPool.h +++ b/dom/indexedDB/TransactionThreadPool.h @@ -16,7 +16,6 @@ #include "mozilla/Monitor.h" #include "nsClassHashtable.h" #include "nsHashKeys.h" -#include "nsRefPtrHashtable.h" #include "IDBTransaction.h" @@ -46,8 +45,8 @@ public: bool aFinish, nsIRunnable* aFinishRunnable); - bool WaitForAllDatabasesToComplete(nsTArray& aDatabases, - nsIRunnable* aCallback); + void WaitForDatabasesToComplete(nsTArray& aDatabases, + nsIRunnable* aCallback); // Abort all transactions, unless they are already in the process of being // committed, for aDatabase. @@ -83,8 +82,7 @@ protected: struct TransactionInfo { - TransactionInfo(IDBTransaction* aTransaction, - const nsTArray& aObjectStoreNames) + TransactionInfo(IDBTransaction* aTransaction) { MOZ_COUNT_CTOR(TransactionInfo); @@ -93,7 +91,6 @@ protected: transaction = aTransaction; queue = new TransactionQueue(aTransaction); - objectStoreNames.AppendElements(aObjectStoreNames); } ~TransactionInfo() @@ -103,7 +100,6 @@ protected: nsRefPtr transaction; nsRefPtr queue; - nsTArray objectStoreNames; nsTHashtable > blockedOn; nsTHashtable > blocking; }; diff --git a/dom/indexedDB/ipc/IndexedDBChild.cpp b/dom/indexedDB/ipc/IndexedDBChild.cpp index 0bd14d8817a3..8d48b987d0f4 100644 --- a/dom/indexedDB/ipc/IndexedDBChild.cpp +++ b/dom/indexedDB/ipc/IndexedDBChild.cpp @@ -11,6 +11,7 @@ #include "mozilla/Assertions.h" #include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/quota/QuotaManager.h" #include "AsyncConnectionHelper.h" #include "DatabaseInfo.h" @@ -19,11 +20,11 @@ #include "IDBIndex.h" #include "IDBObjectStore.h" #include "IDBTransaction.h" -#include "IndexedDatabaseManager.h" USING_INDEXEDDB_NAMESPACE using namespace mozilla::dom; +using mozilla::dom::quota::QuotaManager; namespace { @@ -285,8 +286,7 @@ IndexedDBDatabaseChild::EnsureDatabase( databaseId = mDatabase->Id(); } else { - databaseId = - IndexedDatabaseManager::GetDatabaseId(aDBInfo.origin, aDBInfo.name); + databaseId = QuotaManager::GetStorageId(aDBInfo.origin, aDBInfo.name); } NS_ENSURE_TRUE(databaseId, false); diff --git a/dom/indexedDB/nsIIndexedDatabaseManager.idl b/dom/indexedDB/nsIIndexedDatabaseManager.idl index fef40c7eb992..da1be243d994 100644 --- a/dom/indexedDB/nsIIndexedDatabaseManager.idl +++ b/dom/indexedDB/nsIIndexedDatabaseManager.idl @@ -6,63 +6,9 @@ #include "nsISupports.idl" -interface nsIURI; - -[scriptable, function, uuid(38f15cc7-2df0-4a90-8b7f-1606b2243522)] -interface nsIIndexedDatabaseUsageCallback : nsISupports -{ - void onUsageResult(in nsIURI aURI, - in unsigned long long aUsage, - in unsigned long long aFileUsage, - in unsigned long aAppId, - in boolean aInMozBrowserOnly); -}; - -[scriptable, builtinclass, uuid(e5168115-baff-4559-887e-7c0405cc9e63)] +[scriptable, builtinclass, uuid(538d1085-517e-405a-a0f0-eb575cb0b8e5)] interface nsIIndexedDatabaseManager : nsISupports { - /** - * Schedules an asynchronous callback that will return the total amount of - * disk space being used by databases for the given origin. - * - * @param aURI - * The URI whose usage is being queried. - * @param aCallback - * The callback that will be called when the usage is available. - */ - [optional_argc] - void getUsageForURI(in nsIURI aURI, - in nsIIndexedDatabaseUsageCallback aCallback, - [optional] in unsigned long aAppId, - [optional] in boolean aInMozBrowserOnly); - - /** - * Cancels an asynchronous usage check initiated by a previous call to - * getUsageForURI(). - * - * @param aURI - * The URI whose usage is being queried. - * @param aCallback - * The callback that will be called when the usage is available. - */ - [optional_argc] - void cancelGetUsageForURI(in nsIURI aURI, - in nsIIndexedDatabaseUsageCallback aCallback, - [optional] in unsigned long aAppId, - [optional] in boolean aInMozBrowserOnly); - - /** - * Removes all databases stored for the given URI. The files may not be - * deleted immediately depending on prohibitive concurrent operations. - * - * @param aURI - * The URI whose databases are to be cleared. - */ - [optional_argc] - void clearDatabasesForURI(in nsIURI aURI, - [optional] in unsigned long aAppId, - [optional] in boolean aInMozBrowserOnly); - /** * Defines indexedDB and IDBKeyrange with its static functions on * aObject and initializes DOM exception providers if needed. diff --git a/dom/indexedDB/test/bug839193.js b/dom/indexedDB/test/bug839193.js index 343607483f76..293696802e7e 100644 --- a/dom/indexedDB/test/bug839193.js +++ b/dom/indexedDB/test/bug839193.js @@ -2,19 +2,18 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const nsIIndexedDatabaseManager = - Components.interfaces.nsIIndexedDatabaseManager; +const nsIQuotaManager = Components.interfaces.nsIQuotaManager; let gURI = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService).newURI("http://localhost", null, null); -function onIndexedDBUsageCallback(uri, usage, fileUsage) {} +function onUsageCallback(uri, usage, fileUsage) {} function onLoad() { - var dbManager = Components.classes["@mozilla.org/dom/indexeddb/manager;1"] - .getService(nsIIndexedDatabaseManager); - dbManager.getUsageForURI(gURI, onIndexedDBUsageCallback); - dbManager.cancelGetUsageForURI(gURI, onIndexedDBUsageCallback); + var quotaManager = Components.classes["@mozilla.org/dom/quota/manager;1"] + .getService(nsIQuotaManager); + var quotaRequest = quotaManager.getUsageForURI(gURI, onUsageCallback); + quotaRequest.cancel(); Components.classes["@mozilla.org/observer-service;1"] .getService(Components.interfaces.nsIObserverService) .notifyObservers(window, "bug839193-loaded", null); diff --git a/dom/indexedDB/test/file.js b/dom/indexedDB/test/file.js index e254a3cbd035..e268c847ea41 100644 --- a/dom/indexedDB/test/file.js +++ b/dom/indexedDB/test/file.js @@ -173,8 +173,8 @@ function grabFileUsageAndContinueHandler(usage, fileUsage) function getUsage(usageHandler) { let comp = SpecialPowers.wrap(Components); - let idbManager = comp.classes["@mozilla.org/dom/indexeddb/manager;1"] - .getService(comp.interfaces.nsIIndexedDatabaseManager); + let quotaManager = comp.classes["@mozilla.org/dom/quota/manager;1"] + .getService(comp.interfaces.nsIQuotaManager); let uri = SpecialPowers.getDocumentURIObject(window.document); let callback = { @@ -183,7 +183,7 @@ function getUsage(usageHandler) } }; - idbManager.getUsageForURI(uri, callback); + quotaManager.getUsageForURI(uri, callback); } function scheduleGC() diff --git a/dom/indexedDB/test/helpers.js b/dom/indexedDB/test/helpers.js index fa514b3d9051..3e8b73b78c89 100644 --- a/dom/indexedDB/test/helpers.js +++ b/dom/indexedDB/test/helpers.js @@ -40,18 +40,18 @@ function clearAllDatabases(callback) { let comp = SpecialPowers.wrap(Components); - let idbManager = - comp.classes["@mozilla.org/dom/indexeddb/manager;1"] - .getService(comp.interfaces.nsIIndexedDatabaseManager); + let quotaManager = + comp.classes["@mozilla.org/dom/quota/manager;1"] + .getService(comp.interfaces.nsIQuotaManager); let uri = SpecialPowers.getDocumentURIObject(document); - idbManager.clearDatabasesForURI(uri); - idbManager.getUsageForURI(uri, function(uri, usage, fileUsage) { + quotaManager.clearStoragesForURI(uri); + quotaManager.getUsageForURI(uri, function(uri, usage, fileUsage) { if (usage) { ok(false, "getUsageForURI returned non-zero usage after clearing all " + - "databases!"); + "storages!"); } runCallback(); }); diff --git a/dom/quota/AcquireListener.h b/dom/quota/AcquireListener.h new file mode 100644 index 000000000000..cb02f3e4f9f0 --- /dev/null +++ b/dom/quota/AcquireListener.h @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_quota_acquirelistener_h__ +#define mozilla_dom_quota_acquirelistener_h__ + +#include "mozilla/dom/quota/QuotaCommon.h" + +BEGIN_QUOTA_NAMESPACE + +class AcquireListener +{ +public: + NS_IMETHOD_(nsrefcnt) + AddRef() = 0; + + NS_IMETHOD_(nsrefcnt) + Release() = 0; + + virtual nsresult + OnExclusiveAccessAcquired() = 0; +}; + +END_QUOTA_NAMESPACE + +#endif // mozilla_dom_quota_acquirelistener_h__ diff --git a/dom/quota/ArrayCluster.h b/dom/quota/ArrayCluster.h new file mode 100644 index 000000000000..7c72fb5dc1db --- /dev/null +++ b/dom/quota/ArrayCluster.h @@ -0,0 +1,106 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_quota_arraycluster_h__ +#define mozilla_dom_quota_arraycluster_h__ + +#include "mozilla/dom/quota/QuotaCommon.h" + +#include "Client.h" + +BEGIN_QUOTA_NAMESPACE + +template +class ArrayCluster +{ +public: + ArrayCluster() + { + mArrays.AppendElements(Length); + } + + nsTArray& + ArrayAt(uint32_t aIndex) + { + MOZ_ASSERT(aIndex < Length, "Bad index!"); + return mArrays[aIndex]; + } + + nsTArray& + operator[](uint32_t aIndex) + { + return ArrayAt(aIndex); + } + + bool + IsEmpty() + { + for (uint32_t index = 0; index < Length; index++) { + if (!mArrays[index].IsEmpty()) { + return false; + } + } + return true; + } + + template + void + AppendElementsTo(uint32_t aIndex, nsTArray& aArray) + { + NS_ASSERTION(aIndex < Length, "Bad index!"); + aArray.AppendElements(mArrays[aIndex]); + } + + template + void + AppendElementsTo(uint32_t aIndex, ArrayCluster& aArrayCluster) + { + NS_ASSERTION(aIndex < Length, "Bad index!"); + aArrayCluster[aIndex].AppendElements(mArrays[aIndex]); + } + + template + void + AppendElementsTo(nsTArray& aArray) + { + for (uint32_t index = 0; index < Length; index++) { + aArray.AppendElements(mArrays[index]); + } + } + + template + void + AppendElementsTo(ArrayCluster& aArrayCluster) + { + for (uint32_t index = 0; index < Length; index++) { + aArrayCluster[index].AppendElements(mArrays[index]); + } + } + + template + void + SwapElements(ArrayCluster& aArrayCluster) + { + for (uint32_t index = 0; index < Length; index++) { + mArrays[index].SwapElements(aArrayCluster.mArrays[index]); + } + } + + void + Clear() + { + for (uint32_t index = 0; index < Length; index++) { + mArrays[index].Clear(); + } + } + +private: + nsAutoTArray, Length> mArrays; +}; + +END_QUOTA_NAMESPACE + +#endif // mozilla_dom_quota_arraycluster_h__ diff --git a/dom/quota/CheckQuotaHelper.cpp b/dom/quota/CheckQuotaHelper.cpp index 266d63e68556..af5168855901 100644 --- a/dom/quota/CheckQuotaHelper.cpp +++ b/dom/quota/CheckQuotaHelper.cpp @@ -14,7 +14,7 @@ #include "nsIURI.h" #include "nsPIDOMWindow.h" -#include "mozilla/dom/indexedDB/IndexedDatabaseManager.h" +#include "mozilla/dom/quota/QuotaManager.h" #include "mozilla/Services.h" #include "nsContentUtils.h" #include "nsNetUtil.h" @@ -29,7 +29,6 @@ USING_QUOTA_NAMESPACE using namespace mozilla::services; -using mozilla::dom::indexedDB::IndexedDatabaseManager; using mozilla::MutexAutoLock; namespace { @@ -167,7 +166,7 @@ CheckQuotaHelper::Run() } } else if (mPromptResult == nsIPermissionManager::UNKNOWN_ACTION) { - uint32_t quota = IndexedDatabaseManager::GetIndexedDBQuotaMB(); + uint32_t quota = QuotaManager::GetStorageQuotaMB(); nsString quotaString; quotaString.AppendInt(quota); diff --git a/dom/quota/CheckQuotaHelper.h b/dom/quota/CheckQuotaHelper.h index 948b8e02832b..16d914105e7f 100644 --- a/dom/quota/CheckQuotaHelper.h +++ b/dom/quota/CheckQuotaHelper.h @@ -49,4 +49,4 @@ private: END_QUOTA_NAMESPACE -#endif // mozilla_dom_indexeddb_checkquotahelper_h__ +#endif // mozilla_dom_quota_checkquotahelper_h__ diff --git a/dom/quota/Client.h b/dom/quota/Client.h new file mode 100644 index 000000000000..e04f7a691bd2 --- /dev/null +++ b/dom/quota/Client.h @@ -0,0 +1,110 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_quota_client_h__ +#define mozilla_dom_quota_client_h__ + +#include "mozilla/dom/quota/QuotaCommon.h" + +class nsIOfflineStorage; +class nsIRunnable; + +BEGIN_QUOTA_NAMESPACE + +class UsageRunnable; + +// An abstract interface for quota manager clients. +// Each storage API must provide an implementation of this interface in order +// to participate in centralized quota and storage handling. +class Client +{ +public: + NS_IMETHOD_(nsrefcnt) + AddRef() = 0; + + NS_IMETHOD_(nsrefcnt) + Release() = 0; + + enum Type { + IDB = 0, + //LS, + //APPCACHE, + TYPE_MAX + }; + + virtual Type + GetType() = 0; + + static nsresult + TypeToText(Type aType, nsAString& aText) + { + switch (aType) { + case IDB: + aText.AssignLiteral("idb"); + break; + + case TYPE_MAX: + default: + NS_NOTREACHED("Bad id value!"); + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; + } + + static nsresult + TypeFromText(const nsAString& aText, Type& aType) + { + if (aText.EqualsLiteral("idb")) { + aType = IDB; + } + else { + return NS_ERROR_FAILURE; + } + + return NS_OK; + } + + virtual nsresult + InitOrigin(const nsACString& aOrigin, UsageRunnable* aUsageRunnable) = 0; + + virtual nsresult + GetUsageForOrigin(const nsACString& aOrigin, + UsageRunnable* aUsageRunnable) = 0; + + virtual bool + IsFileServiceUtilized() = 0; + + virtual bool + IsTransactionServiceActivated() = 0; + + virtual void + WaitForStoragesToComplete(nsTArray& aStorages, + nsIRunnable* aCallback) = 0; + + virtual void + AbortTransactionsForStorage(nsIOfflineStorage* aStorage) = 0; + + virtual bool + HasTransactionsForStorage(nsIOfflineStorage* aStorage) = 0; + + virtual void + OnOriginClearCompleted(const nsACString& aPattern) = 0; + + virtual void + ShutdownTransactionService() = 0; + + virtual void + OnShutdownCompleted() = 0; + +protected: + virtual ~Client() + { } +}; + +END_QUOTA_NAMESPACE + +#endif // mozilla_dom_quota_client_h__ diff --git a/dom/quota/FileStreams.cpp b/dom/quota/FileStreams.cpp index 9de244f7792c..cf4d9731e17e 100644 --- a/dom/quota/FileStreams.cpp +++ b/dom/quota/FileStreams.cpp @@ -6,6 +6,8 @@ #include "FileStreams.h" +#include "QuotaManager.h" + USING_QUOTA_NAMESPACE template diff --git a/dom/quota/FileStreams.h b/dom/quota/FileStreams.h index 77bfad47121e..ba56cdbbc76d 100644 --- a/dom/quota/FileStreams.h +++ b/dom/quota/FileStreams.h @@ -11,7 +11,7 @@ #include "nsFileStreams.h" -#include "QuotaManager.h" +#include "QuotaObject.h" BEGIN_QUOTA_NAMESPACE diff --git a/dom/quota/Makefile.in b/dom/quota/Makefile.in index fcba5264eb76..41cf0e4e5de3 100644 --- a/dom/quota/Makefile.in +++ b/dom/quota/Makefile.in @@ -12,6 +12,7 @@ include $(DEPTH)/config/autoconf.mk LIBRARY_NAME = domquota_s LIBXUL_LIBRARY = 1 FORCE_STATIC_LIB = 1 +FAIL_ON_WARNINGS := 1 include $(topsrcdir)/dom/dom-config.mk @@ -21,12 +22,31 @@ CPPSRCS = \ CheckQuotaHelper.cpp \ FileStreams.cpp \ QuotaManager.cpp \ + QuotaObject.cpp \ + $(NULL) + +EXPORTS = \ + nsIOfflineStorage.h \ $(NULL) EXPORTS_mozilla/dom/quota = \ + AcquireListener.h \ + ArrayCluster.h \ + Client.h \ FileStreams.h \ + OriginOrPatternString.h \ QuotaCommon.h \ QuotaManager.h \ + QuotaObject.h \ + StoragePrivilege.h \ + UsageRunnable.h \ + Utilities.h \ $(NULL) +LOCAL_INCLUDES = \ + -I$(topsrcdir)/caps/include \ + $(NULL) + +include $(topsrcdir)/config/config.mk +include $(topsrcdir)/ipc/chromium/chromium-config.mk include $(topsrcdir)/config/rules.mk diff --git a/dom/quota/OriginOrPatternString.h b/dom/quota/OriginOrPatternString.h new file mode 100644 index 000000000000..a26e30d140e9 --- /dev/null +++ b/dom/quota/OriginOrPatternString.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_quota_originorpatternstring_h__ +#define mozilla_dom_quota_originorpatternstring_h__ + +#include "mozilla/dom/quota/QuotaCommon.h" + +BEGIN_QUOTA_NAMESPACE + +class OriginOrPatternString : public nsCString +{ +public: + static OriginOrPatternString + FromOrigin(const nsACString& aOrigin) + { + return OriginOrPatternString(aOrigin, true); + } + + static OriginOrPatternString + FromPattern(const nsACString& aPattern) + { + return OriginOrPatternString(aPattern, false); + } + + bool + IsOrigin() const + { + return mIsOrigin; + } + + bool + IsPattern() const + { + return !mIsOrigin; + } + +private: + OriginOrPatternString(const nsACString& aOriginOrPattern, bool aIsOrigin) + : nsCString(aOriginOrPattern), mIsOrigin(aIsOrigin) + { } + + bool + operator==(const OriginOrPatternString& aOther) MOZ_DELETE; + + bool mIsOrigin; +}; + +END_QUOTA_NAMESPACE + +#endif // mozilla_dom_quota_originorpatternstring_h__ diff --git a/dom/quota/QuotaManager.cpp b/dom/quota/QuotaManager.cpp index 42da545f5b87..13a4db72623f 100644 --- a/dom/quota/QuotaManager.cpp +++ b/dom/quota/QuotaManager.cpp @@ -6,19 +6,321 @@ #include "QuotaManager.h" +#include "mozIApplicationClearPrivateDataParams.h" +#include "nsIAtom.h" #include "nsIFile.h" +#include "nsIObserverService.h" +#include "nsIOfflineStorage.h" +#include "nsIPrincipal.h" +#include "nsIQuotaRequest.h" +#include "nsIRunnable.h" +#include "nsISimpleEnumerator.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIScriptSecurityManager.h" +#include "nsITimer.h" +#include "nsIURI.h" +#include "nsIUsageCallback.h" -#include "mozilla/ClearOnShutdown.h" +#include +#include "mozilla/dom/file/FileService.h" +#include "mozilla/dom/indexedDB/Client.h" +#include "mozilla/LazyIdleThread.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "nsAppDirectoryServiceDefs.h" #include "nsComponentManagerUtils.h" +#include "nsContentUtils.h" +#include "nsCRTGlue.h" +#include "nsDirectoryServiceUtils.h" +#include "nsScriptSecurityManager.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" #include "xpcpublic.h" +#include "AcquireListener.h" #include "CheckQuotaHelper.h" +#include "OriginOrPatternString.h" +#include "QuotaObject.h" +#include "StorageMatcher.h" +#include "UsageRunnable.h" +#include "Utilities.h" + +// The amount of time, in milliseconds, that our IO thread will stay alive +// after the last event it processes. +#define DEFAULT_THREAD_TIMEOUT_MS 30000 + +// The amount of time, in milliseconds, that we will wait for active storage +// transactions on shutdown before aborting them. +#define DEFAULT_SHUTDOWN_TIMER_MS 30000 + +// Amount of space that storages may use by default in megabytes. +#define DEFAULT_QUOTA_MB 50 + +// Preference that users can set to override DEFAULT_QUOTA_MB +#define PREF_STORAGE_QUOTA "dom.indexedDB.warningQuota" + +// profile-before-change, when we need to shut down quota manager +#define PROFILE_BEFORE_CHANGE_OBSERVER_ID "profile-before-change" + +#define METADATA_FILE_NAME ".metadata" USING_QUOTA_NAMESPACE +using namespace mozilla::dom; +using mozilla::dom::file::FileService; + +BEGIN_QUOTA_NAMESPACE + +// A struct that contains the information corresponding to a pending or +// running operation that requires synchronization (e.g. opening a db, +// clearing dbs for an origin, etc). +struct SynchronizedOp +{ + SynchronizedOp(const OriginOrPatternString& aOriginOrPattern, + nsISupports* aId); + + ~SynchronizedOp(); + + // Test whether this SynchronizedOp needs to wait for the given op. + bool + MustWaitFor(const SynchronizedOp& aOp); + + void + DelayRunnable(nsIRunnable* aRunnable); + + void + DispatchDelayedRunnables(); + + const OriginOrPatternString mOriginOrPattern; + nsCOMPtr mId; + nsRefPtr mListener; + nsTArray > mDelayedRunnables; + ArrayCluster mStorages; +}; + +// Responsible for clearing the storage files for a particular origin on the +// IO thread. Created when nsIQuotaManager::ClearStoragesForURI is called. +// Runs three times, first on the main thread, next on the IO thread, and then +// finally again on the main thread. While on the IO thread the runnable will +// actually remove the origin's storage files and the directory that contains +// them before dispatching itself back to the main thread. When back on the main +// thread the runnable will notify the QuotaManager that the job has been +// completed. +class OriginClearRunnable MOZ_FINAL : public nsIRunnable, + public AcquireListener +{ + enum CallbackState { + // Not yet run. + Pending = 0, + + // Running on the main thread in the callback for OpenAllowed. + OpenAllowed, + + // Running on the IO thread. + IO, + + // Running on the main thread after all work is done. + Complete + }; + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIRUNNABLE + + // AcquireListener override + virtual nsresult + OnExclusiveAccessAcquired() MOZ_OVERRIDE; + + OriginClearRunnable(const OriginOrPatternString& aOriginOrPattern) + : mOriginOrPattern(aOriginOrPattern), + mCallbackState(Pending) + { } + + void + AdvanceState() + { + switch (mCallbackState) { + case Pending: + mCallbackState = OpenAllowed; + return; + case OpenAllowed: + mCallbackState = IO; + return; + case IO: + mCallbackState = Complete; + return; + default: + NS_NOTREACHED("Can't advance past Complete!"); + } + } + + static void + InvalidateOpenedStorages(nsTArray >& aStorages, + void* aClosure); + + void + DeleteFiles(QuotaManager* aQuotaManager); + +private: + OriginOrPatternString mOriginOrPattern; + CallbackState mCallbackState; +}; + +// Responsible for calculating the amount of space taken up by storages of a +// certain origin. Created when nsIQuotaManager::GetUsageForURI is called. +// May be canceled with nsIQuotaRequest::Cancel. Runs three times, first +// on the main thread, next on the IO thread, and then finally again on the main +// thread. While on the IO thread the runnable will calculate the size of all +// files in the origin's directory before dispatching itself back to the main +// thread. When on the main thread the runnable will call the callback and then +// notify the QuotaManager that the job has been completed. +class AsyncUsageRunnable MOZ_FINAL : public UsageRunnable, + public nsIRunnable, + public nsIQuotaRequest +{ + enum CallbackState { + // Not yet run. + Pending = 0, + + // Running on the main thread in the callback for OpenAllowed. + OpenAllowed, + + // Running on the IO thread. + IO, + + // Running on the main thread after all work is done. + Complete, + + // Running on the main thread after skipping the work + Shortcut + }; + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIRUNNABLE + NS_DECL_NSIQUOTAREQUEST + + AsyncUsageRunnable(uint32_t aAppId, + bool aInMozBrowserOnly, + const OriginOrPatternString& aOrigin, + nsIURI* aURI, + nsIUsageCallback* aCallback); + + void + AdvanceState() + { + switch (mCallbackState) { + case Pending: + mCallbackState = OpenAllowed; + return; + case OpenAllowed: + mCallbackState = IO; + return; + case IO: + mCallbackState = Complete; + return; + default: + NS_NOTREACHED("Can't advance past Complete!"); + } + } + + nsresult + TakeShortcut(); + + // Run calls the RunInternal method and makes sure that we always dispatch + // to the main thread in case of an error. + inline nsresult + RunInternal(); + + nsCOMPtr mURI; + nsCOMPtr mCallback; + uint32_t mAppId; + OriginOrPatternString mOrigin; + CallbackState mCallbackState; + bool mInMozBrowserOnly; +}; + +END_QUOTA_NAMESPACE namespace { -nsAutoPtr gInstance; +QuotaManager* gInstance = nullptr; +int32_t gShutdown = 0; + +int32_t gStorageQuotaMB = DEFAULT_QUOTA_MB; + +// A callback runnable used by the TransactionPool when it's safe to proceed +// with a SetVersion/DeleteDatabase/etc. +class WaitForTransactionsToFinishRunnable MOZ_FINAL : public nsIRunnable +{ +public: + WaitForTransactionsToFinishRunnable(SynchronizedOp* aOp) + : mOp(aOp), mCountdown(1) + { + NS_ASSERTION(mOp, "Why don't we have a runnable?"); + NS_ASSERTION(mOp->mStorages.IsEmpty(), "We're here too early!"); + NS_ASSERTION(mOp->mListener, + "What are we supposed to do when we're done?"); + NS_ASSERTION(mCountdown, "Wrong countdown!"); + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIRUNNABLE + + void + AddRun() + { + mCountdown++; + } + +private: + // The QuotaManager holds this alive. + SynchronizedOp* mOp; + uint32_t mCountdown; +}; + +class WaitForLockedFilesToFinishRunnable MOZ_FINAL : public nsIRunnable +{ +public: + WaitForLockedFilesToFinishRunnable() + : mBusy(true) + { } + + NS_DECL_ISUPPORTS + NS_DECL_NSIRUNNABLE + + bool + IsBusy() const + { + return mBusy; + } + +private: + bool mBusy; +}; + +bool +IsMainProcess() +{ + return XRE_GetProcessType() == GeckoProcessType_Default; +} + +void +SanitizeOriginString(nsCString& aOrigin) +{ + // We want profiles to be platform-independent so we always need to replace + // the same characters on every platform. Windows has the most extensive set + // of illegal characters so we use its FILE_ILLEGAL_CHARACTERS and + // FILE_PATH_SEPARATOR. + static const char kReplaceChars[] = CONTROL_CHARACTERS "/:*?\"<>|\\"; + +#ifdef XP_WIN + NS_ASSERTION(!strcmp(kReplaceChars, + FILE_ILLEGAL_CHARACTERS FILE_PATH_SEPARATOR), + "Illegal file characters have changed!"); +#endif + + aOrigin.ReplaceChar(kReplaceChars, '+'); +} PLDHashOperator RemoveQuotaForPatternCallback(const nsACString& aKey, @@ -32,7 +334,7 @@ RemoveQuotaForPatternCallback(const nsACString& aKey, const nsACString* pattern = static_cast(aUserArg); - if (StringBeginsWith(aKey, *pattern)) { + if (PatternMatchesOrigin(*pattern, aKey)) { return PL_DHASH_REMOVE; } @@ -41,180 +343,47 @@ RemoveQuotaForPatternCallback(const nsACString& aKey, } // anonymous namespace -void -QuotaObject::AddRef() -{ - QuotaManager* quotaManager = QuotaManager::Get(); - if (!quotaManager) { - NS_ERROR("Null quota manager, this shouldn't happen, possible leak!"); - - NS_AtomicIncrementRefcnt(mRefCnt); - - return; - } - - MutexAutoLock lock(quotaManager->mQuotaMutex); - - ++mRefCnt; -} - -void -QuotaObject::Release() -{ - QuotaManager* quotaManager = QuotaManager::Get(); - if (!quotaManager) { - NS_ERROR("Null quota manager, this shouldn't happen, possible leak!"); - - nsrefcnt count = NS_AtomicDecrementRefcnt(mRefCnt); - if (count == 0) { - mRefCnt = 1; - delete this; - } - - return; - } - - { - MutexAutoLock lock(quotaManager->mQuotaMutex); - - --mRefCnt; - - if (mRefCnt > 0) { - return; - } - - if (mOriginInfo) { - mOriginInfo->mQuotaObjects.Remove(mPath); - } - } - - delete this; -} - -void -QuotaObject::UpdateSize(int64_t aSize) -{ - QuotaManager* quotaManager = QuotaManager::Get(); - NS_ASSERTION(quotaManager, "Shouldn't be null!"); - - MutexAutoLock lock(quotaManager->mQuotaMutex); - - if (mOriginInfo) { - mOriginInfo->mUsage -= mSize; - mSize = aSize; - mOriginInfo->mUsage += mSize; - } -} - -bool -QuotaObject::MaybeAllocateMoreSpace(int64_t aOffset, int32_t aCount) -{ - int64_t end = aOffset + aCount; - - QuotaManager* quotaManager = QuotaManager::Get(); - NS_ASSERTION(quotaManager, "Shouldn't be null!"); - - MutexAutoLock lock(quotaManager->mQuotaMutex); - - if (mSize >= end || !mOriginInfo) { - return true; - } - - int64_t newUsage = mOriginInfo->mUsage - mSize + end; - if (newUsage > mOriginInfo->mLimit) { - // This will block the thread, but it will also drop the mutex while - // waiting. The mutex will be reacquired again when the waiting is finished. - if (!quotaManager->LockedQuotaIsLifted()) { - return false; - } - - // Threads raced, the origin info removal has been done by some other - // thread. - if (!mOriginInfo) { - // The other thread could allocate more space. - if (end > mSize) { - mSize = end; - } - - return true; - } - - nsCString origin = mOriginInfo->mOrigin; - - mOriginInfo->LockedClearOriginInfos(); - NS_ASSERTION(!mOriginInfo, - "Should have cleared in LockedClearOriginInfos!"); - - quotaManager->mOriginInfos.Remove(origin); - - // Some other thread could increase the size without blocking (increasing - // the origin usage without hitting the limit), but no more than this one. - NS_ASSERTION(mSize < end, "This shouldn't happen!"); - - mSize = end; - - return true; - } - - mOriginInfo->mUsage = newUsage; - mSize = end; - - return true; -} - -#ifdef DEBUG -void -OriginInfo::LockedClearOriginInfos() -{ - QuotaManager* quotaManager = QuotaManager::Get(); - NS_ASSERTION(quotaManager, "Shouldn't be null!"); - - quotaManager->mQuotaMutex.AssertCurrentThreadOwns(); - - mQuotaObjects.EnumerateRead(ClearOriginInfoCallback, nullptr); -} -#endif - -// static -PLDHashOperator -OriginInfo::ClearOriginInfoCallback(const nsAString& aKey, - QuotaObject* aValue, - void* aUserArg) -{ - NS_ASSERTION(!aKey.IsEmpty(), "Empty key!"); - NS_ASSERTION(aValue, "Null pointer!"); - - aValue->mOriginInfo = nullptr; - - return PL_DHASH_NEXT; -} - QuotaManager::QuotaManager() : mCurrentWindowIndex(BAD_TLS_INDEX), mQuotaMutex("QuotaManager.mQuotaMutex") { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(!gInstance, "More than one instance!"); } QuotaManager::~QuotaManager() { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(!gInstance || gInstance == this, "Different instances!"); + gInstance = nullptr; } // static QuotaManager* QuotaManager::GetOrCreate() { + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + if (IsShuttingDown()) { + NS_ERROR("Calling GetOrCreate() after shutdown!"); + return nullptr; + } + if (!gInstance) { - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + nsRefPtr instance(new QuotaManager()); - nsAutoPtr instance(new QuotaManager()); + nsresult rv = instance->Init(); + NS_ENSURE_SUCCESS(rv, nullptr); - NS_ENSURE_TRUE(instance->Init(), nullptr); + nsCOMPtr obs = mozilla::services::GetObserverService(); + NS_ENSURE_TRUE(obs, nullptr); - gInstance = instance.forget(); + // We need this callback to know when to shut down all our threads. + rv = obs->AddObserver(instance, PROFILE_BEFORE_CHANGE_OBSERVER_ID, false); + NS_ENSURE_SUCCESS(rv, nullptr); - ClearOnShutdown(&gInstance); + // The observer service will hold our last reference, don't AddRef here. + gInstance = instance; } return gInstance; @@ -228,7 +397,25 @@ QuotaManager::Get() return gInstance; } +// static +QuotaManager* +QuotaManager::FactoryCreate() +{ + // Returns a raw pointer that carries an owning reference! Lame, but the + // singleton factory macros force this. + QuotaManager* quotaManager = GetOrCreate(); + NS_IF_ADDREF(quotaManager); + return quotaManager; +} + +// static bool +QuotaManager::IsShuttingDown() +{ + return !!gShutdown; +} + +nsresult QuotaManager::Init() { // We need a thread-local to hold the current window. @@ -237,13 +424,59 @@ QuotaManager::Init() if (PR_NewThreadPrivateIndex(&mCurrentWindowIndex, nullptr) != PR_SUCCESS) { NS_ERROR("PR_NewThreadPrivateIndex failed, QuotaManager disabled"); mCurrentWindowIndex = BAD_TLS_INDEX; - return false; + return NS_ERROR_FAILURE; + } + + nsresult rv; + if (IsMainProcess()) { + nsCOMPtr dbBaseDirectory; + rv = NS_GetSpecialDirectory(NS_APP_INDEXEDDB_PARENT_DIR, + getter_AddRefs(dbBaseDirectory)); + if (NS_FAILED(rv)) { + rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(dbBaseDirectory)); + } + NS_ENSURE_SUCCESS(rv, rv); + + rv = dbBaseDirectory->Append(NS_LITERAL_STRING("indexedDB")); + NS_ENSURE_SUCCESS(rv, rv); + + rv = dbBaseDirectory->GetPath(mStorageBasePath); + NS_ENSURE_SUCCESS(rv, rv); + + // Make a lazy thread for any IO we need (like clearing or enumerating the + // contents of storage directories). + mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, + NS_LITERAL_CSTRING("Storage I/O"), + LazyIdleThread::ManualShutdown); + + // Make a timer here to avoid potential failures later. We don't actually + // initialize the timer until shutdown. + mShutdownTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + NS_ENSURE_TRUE(mShutdownTimer, NS_ERROR_FAILURE); + } + + if (NS_FAILED(Preferences::AddIntVarCache(&gStorageQuotaMB, + PREF_STORAGE_QUOTA, + DEFAULT_QUOTA_MB))) { + NS_WARNING("Unable to respond to quota pref changes!"); + gStorageQuotaMB = DEFAULT_QUOTA_MB; } mOriginInfos.Init(); mCheckQuotaHelpers.Init(); + mLiveStorages.Init(); - return true; + MOZ_STATIC_ASSERT(Client::IDB == 0 && Client::TYPE_MAX == 1, + "Fix the registration!"); + + NS_ASSERTION(mClients.Capacity() == Client::TYPE_MAX, + "Should be using an auto array with correct capacity!"); + + // Register IndexedDB + mClients.AppendElement(new indexedDB::Client()); + + return NS_OK; } void @@ -345,6 +578,712 @@ QuotaManager::GetQuotaObject(const nsACString& aOrigin, return GetQuotaObject(aOrigin, file); } +bool +QuotaManager::RegisterStorage(nsIOfflineStorage* aStorage) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(aStorage, "Null pointer!"); + + // Don't allow any new storages to be created after shutdown. + if (IsShuttingDown()) { + return false; + } + + // Add this storage to its origin info if it exists, create it otherwise. + const nsACString& origin = aStorage->Origin(); + ArrayCluster* cluster; + if (!mLiveStorages.Get(origin, &cluster)) { + cluster = new ArrayCluster(); + mLiveStorages.Put(origin, cluster); + } + (*cluster)[aStorage->GetClient()->GetType()].AppendElement(aStorage); + + return true; +} + +void +QuotaManager::UnregisterStorage(nsIOfflineStorage* aStorage) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(aStorage, "Null pointer!"); + + // Remove this storage from its origin array, maybe remove the array if it + // is then empty. + const nsACString& origin = aStorage->Origin(); + ArrayCluster* cluster; + if (mLiveStorages.Get(origin, &cluster) && + (*cluster)[aStorage->GetClient()->GetType()].RemoveElement(aStorage)) { + if (cluster->IsEmpty()) { + mLiveStorages.Remove(origin); + } + return; + } + NS_ERROR("Didn't know anything about this storage!"); +} + +void +QuotaManager::OnStorageClosed(nsIOfflineStorage* aStorage) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(aStorage, "Null pointer!"); + + // Check through the list of SynchronizedOps to see if any are waiting for + // this storage to close before proceeding. + SynchronizedOp* op = + FindSynchronizedOp(aStorage->Origin(), aStorage->Id()); + if (op) { + Client::Type clientType = aStorage->GetClient()->GetType(); + + // This storage is in the scope of this SynchronizedOp. Remove it + // from the list if necessary. + if (op->mStorages[clientType].RemoveElement(aStorage)) { + // Now set up the helper if there are no more live storages. + NS_ASSERTION(op->mListener, + "How did we get rid of the listener before removing the " + "last storage?"); + if (op->mStorages[clientType].IsEmpty()) { + // At this point, all storages are closed, so no new transactions + // can be started. There may, however, still be outstanding + // transactions that have not completed. We need to wait for those + // before we dispatch the helper. + if (NS_FAILED(RunSynchronizedOp(aStorage, op))) { + NS_WARNING("Failed to run synchronized op!"); + } + } + } + } +} + +void +QuotaManager::AbortCloseStoragesForWindow(nsPIDOMWindow* aWindow) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(aWindow, "Null pointer!"); + + FileService* service = FileService::Get(); + + StorageMatcher > liveStorages; + liveStorages.Find(mLiveStorages); + + for (uint32_t i = 0; i < Client::TYPE_MAX; i++) { + nsRefPtr& client = mClients[i]; + bool utilized = service && client->IsFileServiceUtilized(); + bool activated = client->IsTransactionServiceActivated(); + + nsTArray& array = liveStorages[i]; + for (uint32_t j = 0; j < array.Length(); j++) { + nsIOfflineStorage*& storage = array[j]; + + if (storage->IsOwned(aWindow)) { + if (NS_FAILED(storage->Close())) { + NS_WARNING("Failed to close storage for dying window!"); + } + + if (utilized) { + service->AbortLockedFilesForStorage(storage); + } + + if (activated) { + client->AbortTransactionsForStorage(storage); + } + } + } + } +} + +bool +QuotaManager::HasOpenTransactions(nsPIDOMWindow* aWindow) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(aWindow, "Null pointer!"); + + FileService* service = FileService::Get(); + + nsAutoPtr > > liveStorages; + + for (uint32_t i = 0; i < Client::TYPE_MAX; i++) { + nsRefPtr& client = mClients[i]; + bool utilized = service && client->IsFileServiceUtilized(); + bool activated = client->IsTransactionServiceActivated(); + + if (utilized || activated) { + if (!liveStorages) { + liveStorages = new StorageMatcher >(); + liveStorages->Find(mLiveStorages); + } + + nsTArray& storages = liveStorages->ArrayAt(i); + for (uint32_t j = 0; j < storages.Length(); j++) { + nsIOfflineStorage*& storage = storages[j]; + + if (storage->IsOwned(aWindow) && + ((utilized && service->HasLockedFilesForStorage(storage)) || + (activated && client->HasTransactionsForStorage(storage)))) { + return true; + } + } + } + } + + return false; +} + +nsresult +QuotaManager::WaitForOpenAllowed(const OriginOrPatternString& aOriginOrPattern, + nsIAtom* aId, + nsIRunnable* aRunnable) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(!aOriginOrPattern.IsEmpty(), "Empty pattern!"); + NS_ASSERTION(aRunnable, "Null pointer!"); + + nsAutoPtr op(new SynchronizedOp(aOriginOrPattern, aId)); + + // See if this runnable needs to wait. + bool delayed = false; + for (uint32_t index = mSynchronizedOps.Length(); index > 0; index--) { + nsAutoPtr& existingOp = mSynchronizedOps[index - 1]; + if (op->MustWaitFor(*existingOp)) { + existingOp->DelayRunnable(aRunnable); + delayed = true; + break; + } + } + + // Otherwise, dispatch it immediately. + if (!delayed) { + nsresult rv = NS_DispatchToCurrentThread(aRunnable); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Adding this to the synchronized ops list will block any additional + // ops from proceeding until this one is done. + mSynchronizedOps.AppendElement(op.forget()); + + return NS_OK; +} + +void +QuotaManager::AllowNextSynchronizedOp( + const OriginOrPatternString& aOriginOrPattern, + nsIAtom* aId) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(!aOriginOrPattern.IsEmpty(), "Empty origin/pattern!"); + + uint32_t count = mSynchronizedOps.Length(); + for (uint32_t index = 0; index < count; index++) { + nsAutoPtr& op = mSynchronizedOps[index]; + if (op->mOriginOrPattern.IsOrigin() == aOriginOrPattern.IsOrigin() && + op->mOriginOrPattern == aOriginOrPattern) { + if (op->mId == aId) { + NS_ASSERTION(op->mStorages.IsEmpty(), "How did this happen?"); + + op->DispatchDelayedRunnables(); + + mSynchronizedOps.RemoveElementAt(index); + return; + } + + // If one or the other is for an origin clear, we should have matched + // solely on origin. + NS_ASSERTION(op->mId && aId, "Why didn't we match earlier?"); + } + } + + NS_NOTREACHED("Why didn't we find a SynchronizedOp?"); +} + +nsresult +QuotaManager::GetDirectoryForOrigin(const nsACString& aASCIIOrigin, + nsIFile** aDirectory) const +{ + nsresult rv; + nsCOMPtr directory = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = directory->InitWithPath(mStorageBasePath); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString originSanitized(aASCIIOrigin); + SanitizeOriginString(originSanitized); + + rv = directory->Append(NS_ConvertASCIItoUTF16(originSanitized)); + NS_ENSURE_SUCCESS(rv, rv); + + directory.forget(aDirectory); + return NS_OK; +} + +nsresult +QuotaManager::EnsureOriginIsInitialized(const nsACString& aOrigin, + StoragePrivilege aPrivilege, + nsIFile** aDirectory) +{ +#ifdef DEBUG + { + bool correctThread; + NS_ASSERTION(NS_SUCCEEDED(mIOThread->IsOnCurrentThread(&correctThread)) && + correctThread, + "Running on the wrong thread!"); + } +#endif + + nsCOMPtr directory; + nsresult rv = GetDirectoryForOrigin(aOrigin, getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists; + rv = directory->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + + if (exists) { + bool isDirectory; + rv = directory->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(isDirectory, NS_ERROR_UNEXPECTED); + } + else { + rv = directory->Create(nsIFile::DIRECTORY_TYPE, 0755); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr metadataFile; + rv = directory->Clone(getter_AddRefs(metadataFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = metadataFile->Append(NS_LITERAL_STRING(METADATA_FILE_NAME)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = metadataFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (mInitializedOrigins.Contains(aOrigin)) { + NS_ADDREF(*aDirectory = directory); + return NS_OK; + } + + rv = MaybeUpgradeOriginDirectory(directory); + NS_ENSURE_SUCCESS(rv, rv); + + // We need to initialize directories of all clients if they exists and also + // get the total usage to initialize the quota. + nsAutoPtr runnable; + if (aPrivilege != Chrome) { + runnable = new UsageRunnable(); + } + + nsCOMPtr entries; + rv = directory->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore; + while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { + nsCOMPtr entry; + rv = entries->GetNext(getter_AddRefs(entry)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr file = do_QueryInterface(entry); + NS_ENSURE_TRUE(file, NS_NOINTERFACE); + + nsString leafName; + rv = file->GetLeafName(leafName); + NS_ENSURE_SUCCESS(rv, rv); + + if (leafName.EqualsLiteral(METADATA_FILE_NAME)) { + continue; + } + + bool isDirectory; + rv = file->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + + if (!isDirectory) { + NS_WARNING("Unknown file found!"); + return NS_ERROR_UNEXPECTED; + } + + Client::Type clientType; + rv = Client::TypeFromText(leafName, clientType); + if (NS_FAILED(rv)) { + NS_WARNING("Unknown directory found!"); + return NS_ERROR_UNEXPECTED; + } + + rv = mClients[clientType]->InitOrigin(aOrigin, runnable); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (aPrivilege != Chrome) { + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "Shouldn't be null!"); + + quotaManager->InitQuotaForOrigin(aOrigin, GetStorageQuotaMB(), + runnable->TotalUsage()); + } + + mInitializedOrigins.AppendElement(aOrigin); + + NS_ADDREF(*aDirectory = directory); + return NS_OK; +} + +void +QuotaManager::UninitializeOriginsByPattern(const nsACString& aPattern) +{ +#ifdef DEBUG + { + bool correctThread; + NS_ASSERTION(NS_SUCCEEDED(mIOThread->IsOnCurrentThread(&correctThread)) && + correctThread, + "Running on the wrong thread!"); + } +#endif + + for (int32_t i = mInitializedOrigins.Length() - 1; i >= 0; i--) { + if (PatternMatchesOrigin(aPattern, mInitializedOrigins[i])) { + mInitializedOrigins.RemoveElementAt(i); + } + } +} + +already_AddRefed +QuotaManager::GetClient(Client::Type aClientType) +{ + nsRefPtr client = mClients.SafeElementAt(aClientType); + return client.forget(); +} + +// static +uint32_t +QuotaManager::GetStorageQuotaMB() +{ + return uint32_t(std::max(gStorageQuotaMB, 0)); +} + +// static +already_AddRefed +QuotaManager::GetStorageId(const nsACString& aOrigin, + const nsAString& aName) +{ + nsCString str(aOrigin); + str.Append("*"); + str.Append(NS_ConvertUTF16toUTF8(aName)); + + nsCOMPtr atom = do_GetAtom(str); + NS_ENSURE_TRUE(atom, nullptr); + + return atom.forget(); +} + +// static +nsresult +QuotaManager::GetASCIIOriginFromURI(nsIURI* aURI, + uint32_t aAppId, + bool aInMozBrowser, + nsACString& aASCIIOrigin) +{ + NS_ASSERTION(aURI, "Null uri!"); + + nsCString origin; + mozilla::GetExtendedOrigin(aURI, aAppId, aInMozBrowser, origin); + + if (origin.IsEmpty()) { + NS_WARNING("GetExtendedOrigin returned empty string!"); + return NS_ERROR_FAILURE; + } + + aASCIIOrigin.Assign(origin); + return NS_OK; +} + +// static +nsresult +QuotaManager::GetASCIIOriginFromPrincipal(nsIPrincipal* aPrincipal, + nsACString& aASCIIOrigin) +{ + NS_ASSERTION(aPrincipal, "Don't hand me a null principal!"); + + static const char kChromeOrigin[] = "chrome"; + + nsCString origin; + if (nsContentUtils::IsSystemPrincipal(aPrincipal)) { + origin.AssignLiteral(kChromeOrigin); + } + else { + bool isNullPrincipal; + nsresult rv = aPrincipal->GetIsNullPrincipal(&isNullPrincipal); + NS_ENSURE_SUCCESS(rv, rv); + + if (isNullPrincipal) { + NS_WARNING("IndexedDB not supported from this principal!"); + return NS_ERROR_FAILURE; + } + + rv = aPrincipal->GetExtendedOrigin(origin); + NS_ENSURE_SUCCESS(rv, rv); + + if (origin.EqualsLiteral(kChromeOrigin)) { + NS_WARNING("Non-chrome principal can't use chrome origin!"); + return NS_ERROR_FAILURE; + } + } + + aASCIIOrigin.Assign(origin); + return NS_OK; +} + +// static +nsresult +QuotaManager::GetASCIIOriginFromWindow(nsPIDOMWindow* aWindow, + nsACString& aASCIIOrigin) +{ + NS_ASSERTION(NS_IsMainThread(), + "We're about to touch a window off the main thread!"); + + if (!aWindow) { + aASCIIOrigin.AssignLiteral("chrome"); + NS_ASSERTION(nsContentUtils::IsCallerChrome(), + "Null window but not chrome!"); + return NS_OK; + } + + nsCOMPtr sop = do_QueryInterface(aWindow); + NS_ENSURE_TRUE(sop, NS_ERROR_FAILURE); + + nsCOMPtr principal = sop->GetPrincipal(); + NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE); + + nsresult rv = GetASCIIOriginFromPrincipal(principal, aASCIIOrigin); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS2(QuotaManager, nsIQuotaManager, nsIObserver) + +NS_IMETHODIMP +QuotaManager::GetUsageForURI(nsIURI* aURI, + nsIUsageCallback* aCallback, + uint32_t aAppId, + bool aInMozBrowserOnly, + uint8_t aOptionalArgCount, + nsIQuotaRequest** _retval) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + NS_ENSURE_ARG_POINTER(aURI); + NS_ENSURE_ARG_POINTER(aCallback); + + // This only works from the main process. + NS_ENSURE_TRUE(IsMainProcess(), NS_ERROR_NOT_AVAILABLE); + + if (!aOptionalArgCount) { + aAppId = nsIScriptSecurityManager::NO_APP_ID; + } + + // Figure out which origin we're dealing with. + nsCString origin; + nsresult rv = GetASCIIOriginFromURI(aURI, aAppId, aInMozBrowserOnly, origin); + NS_ENSURE_SUCCESS(rv, rv); + + OriginOrPatternString oops = OriginOrPatternString::FromOrigin(origin); + + nsRefPtr runnable = + new AsyncUsageRunnable(aAppId, aInMozBrowserOnly, oops, aURI, aCallback); + + // Put the computation runnable in the queue. + rv = WaitForOpenAllowed(oops, nullptr, runnable); + NS_ENSURE_SUCCESS(rv, rv); + + runnable->AdvanceState(); + + runnable.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP +QuotaManager::ClearStoragesForURI(nsIURI* aURI, + uint32_t aAppId, + bool aInMozBrowserOnly, + uint8_t aOptionalArgCount) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + NS_ENSURE_ARG_POINTER(aURI); + + // This only works from the main process. + NS_ENSURE_TRUE(IsMainProcess(), NS_ERROR_NOT_AVAILABLE); + + if (!aOptionalArgCount) { + aAppId = nsIScriptSecurityManager::NO_APP_ID; + } + + // Figure out which origin we're dealing with. + nsCString origin; + nsresult rv = GetASCIIOriginFromURI(aURI, aAppId, aInMozBrowserOnly, origin); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString pattern; + GetOriginPatternString(aAppId, aInMozBrowserOnly, origin, pattern); + + // If there is a pending or running clear operation for this origin, return + // immediately. + if (IsClearOriginPending(pattern)) { + return NS_OK; + } + + OriginOrPatternString oops = OriginOrPatternString::FromPattern(pattern); + + // Queue up the origin clear runnable. + nsRefPtr runnable = new OriginClearRunnable(oops); + + rv = WaitForOpenAllowed(oops, nullptr, runnable); + NS_ENSURE_SUCCESS(rv, rv); + + runnable->AdvanceState(); + + // Give the runnable some help by invalidating any storages in the way. + StorageMatcher > matches; + matches.Find(mLiveStorages, pattern); + + for (uint32_t index = 0; index < matches.Length(); index++) { + // We need to grab references to any live storages here to prevent them + // from dying while we invalidate them. + nsCOMPtr storage = matches[index]; + storage->Invalidate(); + } + + // After everything has been invalidated the helper should be dispatched to + // the end of the event queue. + return NS_OK; +} + +NS_IMETHODIMP +QuotaManager::Observe(nsISupports* aSubject, + const char* aTopic, + const PRUnichar* aData) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + if (!strcmp(aTopic, PROFILE_BEFORE_CHANGE_OBSERVER_ID)) { + // Setting this flag prevents the service from being recreated and prevents + // further storagess from being created. + if (PR_ATOMIC_SET(&gShutdown, 1)) { + NS_ERROR("Shutdown more than once?!"); + } + + if (IsMainProcess()) { + FileService* service = FileService::Get(); + if (service) { + // This should only wait for storages registered in this manager + // to complete. Other storages may still have running locked files. + // If the necko service (thread pool) gets the shutdown notification + // first then the sync loop won't be processed at all, otherwise it will + // lock the main thread until all storages registered in this manager + // are finished. + + nsTArray indexes; + for (uint32_t index = 0; index < Client::TYPE_MAX; index++) { + if (mClients[index]->IsFileServiceUtilized()) { + indexes.AppendElement(index); + } + } + + StorageMatcher > > liveStorages; + liveStorages.Find(mLiveStorages, &indexes); + + if (!liveStorages.IsEmpty()) { + nsRefPtr runnable = + new WaitForLockedFilesToFinishRunnable(); + + service->WaitForStoragesToComplete(liveStorages, runnable); + + nsIThread* thread = NS_GetCurrentThread(); + while (runnable->IsBusy()) { + if (!NS_ProcessNextEvent(thread)) { + NS_ERROR("Failed to process next event!"); + break; + } + } + } + } + + // Make sure to join with our IO thread. + if (NS_FAILED(mIOThread->Shutdown())) { + NS_WARNING("Failed to shutdown IO thread!"); + } + + // Kick off the shutdown timer. + if (NS_FAILED(mShutdownTimer->Init(this, DEFAULT_SHUTDOWN_TIMER_MS, + nsITimer::TYPE_ONE_SHOT))) { + NS_WARNING("Failed to initialize shutdown timer!"); + } + + // Each client will spin the event loop while we wait on all the threads + // to close. Our timer may fire during that loop. + for (uint32_t index = 0; index < Client::TYPE_MAX; index++) { + mClients[index]->ShutdownTransactionService(); + } + + // Cancel the timer regardless of whether it actually fired. + if (NS_FAILED(mShutdownTimer->Cancel())) { + NS_WARNING("Failed to cancel shutdown timer!"); + } + } + + for (uint32_t index = 0; index < Client::TYPE_MAX; index++) { + mClients[index]->OnShutdownCompleted(); + } + + return NS_OK; + } + + if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC)) { + NS_ASSERTION(IsMainProcess(), "Should only happen in the main process!"); + + NS_WARNING("Some storage operations are taking longer than expected " + "during shutdown and will be aborted!"); + + // Grab all live storages, for all origins. + StorageMatcher > liveStorages; + liveStorages.Find(mLiveStorages); + + // Invalidate them all. + if (!liveStorages.IsEmpty()) { + uint32_t count = liveStorages.Length(); + for (uint32_t index = 0; index < count; index++) { + liveStorages[index]->Invalidate(); + } + } + + return NS_OK; + } + + if (!strcmp(aTopic, TOPIC_WEB_APP_CLEAR_DATA)) { + nsCOMPtr params = + do_QueryInterface(aSubject); + NS_ENSURE_TRUE(params, NS_ERROR_UNEXPECTED); + + uint32_t appId; + nsresult rv = params->GetAppId(&appId); + NS_ENSURE_SUCCESS(rv, rv); + + bool browserOnly; + rv = params->GetBrowserOnly(&browserOnly); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ClearStoragesForApp(appId, browserOnly); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; + } + + NS_NOTREACHED("Unknown topic!"); + return NS_ERROR_UNEXPECTED; +} + void QuotaManager::SetCurrentWindowInternal(nsPIDOMWindow* aWindow) { @@ -424,3 +1363,815 @@ QuotaManager::LockedQuotaIsLifted() return result; } + +nsresult +QuotaManager::AcquireExclusiveAccess(const nsACString& aPattern, + nsIOfflineStorage* aStorage, + AcquireListener* aListener, + WaitingOnStoragesCallback aCallback, + void* aClosure) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(aListener, "Need a listener!"); + + // Find the right SynchronizedOp. + SynchronizedOp* op = + FindSynchronizedOp(aPattern, aStorage ? aStorage->Id() : nullptr); + + NS_ASSERTION(op, "We didn't find a SynchronizedOp?"); + NS_ASSERTION(!op->mListener, "SynchronizedOp already has a listener?!?"); + + nsTArray > liveStorages; + + if (aStorage) { + // We need to wait for the storages to go away. + // Hold on to all storage objects that represent the same storage file + // (except the one that is requesting this version change). + + Client::Type clientType = aStorage->GetClient()->GetType(); + + StorageMatcher > matches; + matches.Find(mLiveStorages, aPattern, clientType); + + if (!matches.IsEmpty()) { + // Grab all storages that are not yet closed but whose storage id match + // the one we're looking for. + for (uint32_t index = 0; index < matches.Length(); index++) { + nsIOfflineStorage*& storage = matches[index]; + if (!storage->IsClosed() && + storage != aStorage && + storage->Id() == aStorage->Id()) { + liveStorages.AppendElement(storage); + } + } + } + + if (!liveStorages.IsEmpty()) { + NS_ASSERTION(op->mStorages[clientType].IsEmpty(), + "How do we already have storages here?"); + op->mStorages[clientType].AppendElements(liveStorages); + } + } + else { + StorageMatcher > matches; + matches.Find(mLiveStorages, aPattern); + + if (!matches.IsEmpty()) { + // We want *all* storages, even those that are closed, when we're going to + // clear the origin. + matches.AppendElementsTo(liveStorages); + + NS_ASSERTION(op->mStorages.IsEmpty(), + "How do we already have storages here?"); + matches.SwapElements(op->mStorages); + } + } + + op->mListener = aListener; + + if (!liveStorages.IsEmpty()) { + // Give our callback the storages so it can decide what to do with them. + aCallback(liveStorages, aClosure); + + NS_ASSERTION(liveStorages.IsEmpty(), + "Should have done something with the array!"); + + if (aStorage) { + // Wait for those storages to close. + return NS_OK; + } + } + + // If we're trying to open a storage and nothing blocks it, or if we're + // clearing an origin, then go ahead and schedule the op. + nsresult rv = RunSynchronizedOp(aStorage, op); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +QuotaManager::RunSynchronizedOp(nsIOfflineStorage* aStorage, + SynchronizedOp* aOp) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(aOp, "Null pointer!"); + NS_ASSERTION(aOp->mListener, "No listener on this op!"); + NS_ASSERTION(!aStorage || + aOp->mStorages[aStorage->GetClient()->GetType()].IsEmpty(), + "This op isn't ready to run!"); + + ArrayCluster storages; + + uint32_t startIndex; + uint32_t endIndex; + + if (aStorage) { + Client::Type clientType = aStorage->GetClient()->GetType(); + + storages[clientType].AppendElement(aStorage); + + startIndex = clientType; + endIndex = clientType + 1; + } + else { + aOp->mStorages.SwapElements(storages); + + startIndex = 0; + endIndex = Client::TYPE_MAX; + } + + nsRefPtr runnable = + new WaitForTransactionsToFinishRunnable(aOp); + + // Ask the file service to call us back when it's done with this storage. + FileService* service = FileService::Get(); + + if (service) { + // Have to copy here in case a transaction service needs a list too. + nsTArray > array; + + for (uint32_t index = startIndex; index < endIndex; index++) { + if (!storages[index].IsEmpty() && + mClients[index]->IsFileServiceUtilized()) { + array.AppendElements(storages[index]); + } + } + + if (!array.IsEmpty()) { + runnable->AddRun(); + + service->WaitForStoragesToComplete(array, runnable); + } + } + + // Ask each transaction service to call us back when they're done with this + // storage. + for (uint32_t index = startIndex; index < endIndex; index++) { + nsRefPtr& client = mClients[index]; + if (!storages[index].IsEmpty() && client->IsTransactionServiceActivated()) { + runnable->AddRun(); + + client->WaitForStoragesToComplete(storages[index], runnable); + } + } + + nsresult rv = runnable->Run(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +SynchronizedOp* +QuotaManager::FindSynchronizedOp(const nsACString& aPattern, + nsISupports* aId) +{ + for (uint32_t index = 0; index < mSynchronizedOps.Length(); index++) { + const nsAutoPtr& currentOp = mSynchronizedOps[index]; + if (PatternMatchesOrigin(aPattern, currentOp->mOriginOrPattern) && + (!currentOp->mId || currentOp->mId == aId)) { + return currentOp; + } + } + + return nullptr; +} + +nsresult +QuotaManager::ClearStoragesForApp(uint32_t aAppId, bool aBrowserOnly) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID, + "Bad appId!"); + + // This only works from the main process. + NS_ENSURE_TRUE(IsMainProcess(), NS_ERROR_NOT_AVAILABLE); + + nsAutoCString pattern; + GetOriginPatternStringMaybeIgnoreBrowser(aAppId, aBrowserOnly, pattern); + + // If there is a pending or running clear operation for this app, return + // immediately. + if (IsClearOriginPending(pattern)) { + return NS_OK; + } + + OriginOrPatternString oops = OriginOrPatternString::FromPattern(pattern); + + // Queue up the origin clear runnable. + nsRefPtr runnable = new OriginClearRunnable(oops); + + nsresult rv = WaitForOpenAllowed(oops, nullptr, runnable); + NS_ENSURE_SUCCESS(rv, rv); + + runnable->AdvanceState(); + + // Give the runnable some help by invalidating any storages in the way. + StorageMatcher > matches; + matches.Find(mLiveStorages, pattern); + + for (uint32_t index = 0; index < matches.Length(); index++) { + // We need to grab references here to prevent the storage from dying while + // we invalidate it. + nsCOMPtr storage = matches[index]; + storage->Invalidate(); + } + + return NS_OK; +} + +nsresult +QuotaManager::MaybeUpgradeOriginDirectory(nsIFile* aDirectory) +{ + NS_ASSERTION(aDirectory, "Null pointer!"); + + nsCOMPtr metadataFile; + nsresult rv = aDirectory->Clone(getter_AddRefs(metadataFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = metadataFile->Append(NS_LITERAL_STRING(METADATA_FILE_NAME)); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists; + rv = metadataFile->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + + if (!exists) { + // Directory structure upgrade needed. + // Move all files to IDB specific directory. + + nsString idbDirectoryName; + rv = Client::TypeToText(Client::IDB, idbDirectoryName); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr idbDirectory; + rv = aDirectory->Clone(getter_AddRefs(idbDirectory)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = idbDirectory->Append(idbDirectoryName); + NS_ENSURE_SUCCESS(rv, rv); + + rv = idbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755); + if (NS_FAILED(rv)) { + NS_WARNING("IDB directory already exists!"); + } + + nsCOMPtr entries; + rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore; + while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { + nsCOMPtr entry; + rv = entries->GetNext(getter_AddRefs(entry)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr file = do_QueryInterface(entry); + NS_ENSURE_TRUE(file, NS_NOINTERFACE); + + nsString leafName; + rv = file->GetLeafName(leafName); + NS_ENSURE_SUCCESS(rv, rv); + + if (!leafName.Equals(idbDirectoryName)) { + rv = file->MoveTo(idbDirectory, EmptyString()); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + rv = metadataFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +void +QuotaManager::GetOriginPatternString(uint32_t aAppId, + MozBrowserPatternFlag aBrowserFlag, + const nsACString& aOrigin, + nsAutoCString& _retval) +{ + NS_ASSERTION(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID, + "Bad appId!"); + NS_ASSERTION(aOrigin.IsEmpty() || aBrowserFlag != IgnoreMozBrowser, + "Bad args!"); + + if (aOrigin.IsEmpty()) { + _retval.Truncate(); + + _retval.AppendInt(aAppId); + _retval.Append('+'); + + if (aBrowserFlag != IgnoreMozBrowser) { + if (aBrowserFlag == MozBrowser) { + _retval.Append('t'); + } + else { + _retval.Append('f'); + } + _retval.Append('+'); + } + + return; + } + +#ifdef DEBUG + if (aAppId != nsIScriptSecurityManager::NO_APP_ID || + aBrowserFlag == MozBrowser) { + nsAutoCString pattern; + GetOriginPatternString(aAppId, aBrowserFlag, EmptyCString(), pattern); + NS_ASSERTION(PatternMatchesOrigin(pattern, aOrigin), + "Origin doesn't match parameters!"); + } +#endif + + _retval = aOrigin; +} + +SynchronizedOp::SynchronizedOp(const OriginOrPatternString& aOriginOrPattern, + nsISupports* aId) +: mOriginOrPattern(aOriginOrPattern), mId(aId) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + MOZ_COUNT_CTOR(SynchronizedOp); +} + +SynchronizedOp::~SynchronizedOp() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + MOZ_COUNT_DTOR(SynchronizedOp); +} + +bool +SynchronizedOp::MustWaitFor(const SynchronizedOp& aExistingOp) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + bool match; + + if (aExistingOp.mOriginOrPattern.IsOrigin()) { + if (mOriginOrPattern.IsOrigin()) { + match = aExistingOp.mOriginOrPattern.Equals(mOriginOrPattern); + } + else { + match = PatternMatchesOrigin(mOriginOrPattern, aExistingOp.mOriginOrPattern); + } + } + else if (mOriginOrPattern.IsOrigin()) { + match = PatternMatchesOrigin(aExistingOp.mOriginOrPattern, mOriginOrPattern); + } + else { + match = PatternMatchesOrigin(mOriginOrPattern, aExistingOp.mOriginOrPattern) || + PatternMatchesOrigin(aExistingOp.mOriginOrPattern, mOriginOrPattern); + } + + // If the origins don't match, the second can proceed. + if (!match) { + return false; + } + + // If the origins and the ids match, the second must wait. + if (aExistingOp.mId == mId) { + return true; + } + + // Waiting is required if either one corresponds to an origin clearing + // (a null Id). + if (!aExistingOp.mId || !mId) { + return true; + } + + // Otherwise, things for the same origin but different storages can proceed + // independently. + return false; +} + +void +SynchronizedOp::DelayRunnable(nsIRunnable* aRunnable) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(mDelayedRunnables.IsEmpty() || !mId, + "Only ClearOrigin operations can delay multiple runnables!"); + + mDelayedRunnables.AppendElement(aRunnable); +} + +void +SynchronizedOp::DispatchDelayedRunnables() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(!mListener, "Any listener should be gone by now!"); + + uint32_t count = mDelayedRunnables.Length(); + for (uint32_t index = 0; index < count; index++) { + NS_DispatchToCurrentThread(mDelayedRunnables[index]); + } + + mDelayedRunnables.Clear(); +} + +nsresult +OriginClearRunnable::OnExclusiveAccessAcquired() +{ + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "This should never fail!"); + + nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +// static +void +OriginClearRunnable::InvalidateOpenedStorages( + nsTArray >& aStorages, + void* aClosure) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + nsTArray > storages; + storages.SwapElements(aStorages); + + for (uint32_t index = 0; index < storages.Length(); index++) { + storages[index]->Invalidate(); + } +} + +void +OriginClearRunnable::DeleteFiles(QuotaManager* aQuotaManager) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(aQuotaManager, "Don't pass me null!"); + + nsresult rv; + + nsCOMPtr directory = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS_VOID(rv); + + rv = directory->InitWithPath(aQuotaManager->GetBaseDirectory()); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr entries; + if (NS_FAILED(directory->GetDirectoryEntries(getter_AddRefs(entries))) || + !entries) { + return; + } + + nsCString originSanitized(mOriginOrPattern); + SanitizeOriginString(originSanitized); + + bool hasMore; + while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { + nsCOMPtr entry; + rv = entries->GetNext(getter_AddRefs(entry)); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr file = do_QueryInterface(entry); + NS_ASSERTION(file, "Don't know what this is!"); + + bool isDirectory; + rv = file->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS_VOID(rv); + + if (!isDirectory) { + NS_WARNING("Something in the IndexedDB directory that doesn't belong!"); + continue; + } + + nsString leafName; + rv = file->GetLeafName(leafName); + NS_ENSURE_SUCCESS_VOID(rv); + + // Skip storages for other apps. + if (!PatternMatchesOrigin(originSanitized, + NS_ConvertUTF16toUTF8(leafName))) { + continue; + } + + if (NS_FAILED(file->Remove(true))) { + // This should never fail if we've closed all storage connections + // correctly... + NS_ERROR("Failed to remove directory!"); + } + } + + aQuotaManager->RemoveQuotaForPattern(mOriginOrPattern); + + aQuotaManager->UninitializeOriginsByPattern(mOriginOrPattern); +} + +NS_IMPL_THREADSAFE_ISUPPORTS1(OriginClearRunnable, nsIRunnable) + +NS_IMETHODIMP +OriginClearRunnable::Run() +{ + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "This should never fail!"); + + switch (mCallbackState) { + case Pending: { + NS_NOTREACHED("Should never get here without being dispatched!"); + return NS_ERROR_UNEXPECTED; + } + + case OpenAllowed: { + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + AdvanceState(); + + // Now we have to wait until the thread pool is done with all of the + // storages we care about. + nsresult rv = + quotaManager->AcquireExclusiveAccess(mOriginOrPattern, this, + InvalidateOpenedStorages, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; + } + + case IO: { + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + AdvanceState(); + + DeleteFiles(quotaManager); + + // Now dispatch back to the main thread. + if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) { + NS_WARNING("Failed to dispatch to main thread!"); + return NS_ERROR_FAILURE; + } + + return NS_OK; + } + + case Complete: { + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + for (uint32_t index = 0; index < Client::TYPE_MAX; index++) { + quotaManager->mClients[index]->OnOriginClearCompleted(mOriginOrPattern); + } + + // Tell the QuotaManager that we're done. + quotaManager->AllowNextSynchronizedOp(mOriginOrPattern, nullptr); + + return NS_OK; + } + + default: + NS_ERROR("Unknown state value!"); + return NS_ERROR_UNEXPECTED; + } + + NS_NOTREACHED("Should never get here!"); + return NS_ERROR_UNEXPECTED; +} + +AsyncUsageRunnable::AsyncUsageRunnable(uint32_t aAppId, + bool aInMozBrowserOnly, + const OriginOrPatternString& aOrigin, + nsIURI* aURI, + nsIUsageCallback* aCallback) +: mURI(aURI), + mCallback(aCallback), + mAppId(aAppId), + mOrigin(aOrigin), + mCallbackState(Pending), + mInMozBrowserOnly(aInMozBrowserOnly) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(aURI, "Null pointer!"); + NS_ASSERTION(aOrigin.IsOrigin(), "Expect origin only here!"); + NS_ASSERTION(!aOrigin.IsEmpty(), "Empty origin!"); + NS_ASSERTION(aCallback, "Null pointer!"); +} + +nsresult +AsyncUsageRunnable::TakeShortcut() +{ + NS_ASSERTION(mCallbackState == Pending, "Huh?"); + + nsresult rv = NS_DispatchToCurrentThread(this); + NS_ENSURE_SUCCESS(rv, rv); + + mCallbackState = Shortcut; + return NS_OK; +} + +nsresult +AsyncUsageRunnable::RunInternal() +{ + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "This should never fail!"); + + nsresult rv; + + switch (mCallbackState) { + case Pending: { + NS_NOTREACHED("Should never get here without being dispatched!"); + return NS_ERROR_UNEXPECTED; + } + + case OpenAllowed: { + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + AdvanceState(); + + rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to dispatch to the IO thread!"); + } + + return NS_OK; + } + + case IO: { + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + AdvanceState(); + + // Get the directory that contains all the storage files we care about. + nsCOMPtr directory; + rv = quotaManager->GetDirectoryForOrigin(mOrigin, + getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists; + rv = directory->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + + // If the directory exists then enumerate all the files inside, adding up + // the sizes to get the final usage statistic. + if (exists && !mCanceled) { + bool initialized = quotaManager->mInitializedOrigins.Contains(mOrigin); + + if (!initialized) { + rv = quotaManager->MaybeUpgradeOriginDirectory(directory); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr entries; + rv = directory->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore; + while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && + hasMore && !mCanceled) { + nsCOMPtr entry; + rv = entries->GetNext(getter_AddRefs(entry)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr file = do_QueryInterface(entry); + NS_ENSURE_TRUE(file, NS_NOINTERFACE); + + nsString leafName; + rv = file->GetLeafName(leafName); + NS_ENSURE_SUCCESS(rv, rv); + + if (leafName.EqualsLiteral(METADATA_FILE_NAME)) { + continue; + } + + if (!initialized) { + bool isDirectory; + rv = file->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + + if (!isDirectory) { + NS_WARNING("Unknown file found!"); + return NS_ERROR_UNEXPECTED; + } + } + + Client::Type clientType; + rv = Client::TypeFromText(leafName, clientType); + if (NS_FAILED(rv)) { + NS_WARNING("Unknown directory found!"); + if (!initialized) { + return NS_ERROR_UNEXPECTED; + } + continue; + } + + nsRefPtr& client = quotaManager->mClients[clientType]; + + if (!initialized) { + rv = client->InitOrigin(mOrigin, this); + } + else { + rv = client->GetUsageForOrigin(mOrigin, this); + } + NS_ENSURE_SUCCESS(rv, rv); + } + } + + // Run dispatches us back to the main thread. + return NS_OK; + } + + case Complete: // Fall through + case Shortcut: { + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + // Call the callback unless we were canceled. + if (!mCanceled) { + mCallback->OnUsageResult(mURI, TotalUsage(), FileUsage(), mAppId, + mInMozBrowserOnly); + } + + // Clean up. + mURI = nullptr; + mCallback = nullptr; + + // And tell the QuotaManager that we're done. + if (mCallbackState == Complete) { + quotaManager->AllowNextSynchronizedOp(mOrigin, nullptr); + } + + return NS_OK; + } + + default: + NS_ERROR("Unknown state value!"); + return NS_ERROR_UNEXPECTED; + } + + NS_NOTREACHED("Should never get here!"); + return NS_ERROR_UNEXPECTED; +} + +NS_IMPL_THREADSAFE_ISUPPORTS2(AsyncUsageRunnable, + nsIRunnable, + nsIQuotaRequest) + +NS_IMETHODIMP +AsyncUsageRunnable::Run() +{ + nsresult rv = RunInternal(); + + if (!NS_IsMainThread()) { + if (NS_FAILED(rv)) { + ResetUsage(); + } + + if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) { + NS_WARNING("Failed to dispatch to main thread!"); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +AsyncUsageRunnable::Cancel() +{ + if (PR_ATOMIC_SET(&mCanceled, 1)) { + NS_WARNING("Canceled more than once?!"); + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +NS_IMPL_THREADSAFE_ISUPPORTS1(WaitForTransactionsToFinishRunnable, nsIRunnable) + +NS_IMETHODIMP +WaitForTransactionsToFinishRunnable::Run() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(mOp, "Null op!"); + NS_ASSERTION(mOp->mListener, "Nothing to run!"); + NS_ASSERTION(mCountdown, "Wrong countdown!"); + + if (--mCountdown) { + return NS_OK; + } + + // Don't hold the listener alive longer than necessary. + nsRefPtr listener; + listener.swap(mOp->mListener); + + mOp = nullptr; + + nsresult rv = listener->OnExclusiveAccessAcquired(); + NS_ENSURE_SUCCESS(rv, rv); + + // The listener is responsible for calling + // QuotaManager::AllowNextSynchronizedOp. + return NS_OK; +} + +NS_IMPL_THREADSAFE_ISUPPORTS1(WaitForLockedFilesToFinishRunnable, nsIRunnable) + +NS_IMETHODIMP +WaitForLockedFilesToFinishRunnable::Run() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + mBusy = false; + + return NS_OK; +} diff --git a/dom/quota/QuotaManager.h b/dom/quota/QuotaManager.h index 3a35f868aeb9..e22d2c1600cb 100644 --- a/dom/quota/QuotaManager.h +++ b/dom/quota/QuotaManager.h @@ -9,95 +9,62 @@ #include "QuotaCommon.h" +#include "nsIObserver.h" +#include "nsIQuotaManager.h" + #include "mozilla/Mutex.h" -#include "nsDataHashtable.h" +#include "nsClassHashtable.h" #include "nsRefPtrHashtable.h" #include "nsThreadUtils.h" +#include "ArrayCluster.h" +#include "Client.h" +#include "StoragePrivilege.h" + +#define QUOTA_MANAGER_CONTRACTID "@mozilla.org/dom/quota/manager;1" + +class nsIAtom; +class nsIOfflineStorage; +class nsIPrincipal; +class nsIThread; +class nsITimer; +class nsIURI; class nsPIDOMWindow; BEGIN_QUOTA_NAMESPACE +class AcquireListener; +class AsyncUsageRunnable; class CheckQuotaHelper; +class OriginClearRunnable; class OriginInfo; -class QuotaManager; +class OriginOrPatternString; +class QuotaObject; +struct SynchronizedOp; -class QuotaObject +class QuotaManager MOZ_FINAL : public nsIQuotaManager, + public nsIObserver { - friend class OriginInfo; - friend class QuotaManager; - -public: - void - AddRef(); - - void - Release(); - - void - UpdateSize(int64_t aSize); - - bool - MaybeAllocateMoreSpace(int64_t aOffset, int32_t aCount); - -private: - QuotaObject(OriginInfo* aOriginInfo, const nsAString& aPath, int64_t aSize) - : mOriginInfo(aOriginInfo), mPath(aPath), mSize(aSize) - { } - - virtual ~QuotaObject() - { } - - nsAutoRefCnt mRefCnt; - - OriginInfo* mOriginInfo; - nsString mPath; - int64_t mSize; -}; - -class OriginInfo -{ - friend class QuotaManager; - friend class QuotaObject; - -public: - OriginInfo(const nsACString& aOrigin, int64_t aLimit, int64_t aUsage) - : mOrigin(aOrigin), mLimit(aLimit), mUsage(aUsage) - { - mQuotaObjects.Init(); - } - - NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OriginInfo) - -private: - void -#ifdef DEBUG - LockedClearOriginInfos(); -#else - LockedClearOriginInfos() - { - mQuotaObjects.EnumerateRead(ClearOriginInfoCallback, nullptr); - } -#endif - - static PLDHashOperator - ClearOriginInfoCallback(const nsAString& aKey, - QuotaObject* aValue, void* aUserArg); - - nsDataHashtable mQuotaObjects; - - nsCString mOrigin; - int64_t mLimit; - int64_t mUsage; -}; - -class QuotaManager -{ - friend class nsAutoPtr; + friend class AsyncUsageRunnable; + friend class OriginClearRunnable; friend class OriginInfo; friend class QuotaObject; + enum MozBrowserPatternFlag + { + MozBrowser = 0, + NotMozBrowser, + IgnoreMozBrowser + }; + + typedef void + (*WaitingOnStoragesCallback)(nsTArray >&, void*); + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIQUOTAMANAGER + NS_DECL_NSIOBSERVER + // Returns a non-owning reference. static QuotaManager* GetOrCreate(); @@ -106,6 +73,13 @@ public: static QuotaManager* Get(); + // Returns an owning reference! No one should call this but the factory. + static QuotaManager* + FactoryCreate(); + + // Returns true if we've begun the shutdown process. + static bool IsShuttingDown(); + void InitQuotaForOrigin(const nsACString& aOrigin, int64_t aLimit, @@ -148,12 +122,143 @@ public: quotaManager->CancelPromptsForWindowInternal(aWindow); } + // Called when a storage is created. + bool + RegisterStorage(nsIOfflineStorage* aStorage); + + // Called when a storage is being unlinked or destroyed. + void + UnregisterStorage(nsIOfflineStorage* aStorage); + + // Called when a storage has been closed. + void + OnStorageClosed(nsIOfflineStorage* aStorage); + + // Called when a window is being purged from the bfcache or the user leaves + // a page which isn't going into the bfcache. Forces any live storage + // objects to close themselves and aborts any running transactions. + void + AbortCloseStoragesForWindow(nsPIDOMWindow* aWindow); + + // Used to check if there are running transactions in a given window. + bool + HasOpenTransactions(nsPIDOMWindow* aWindow); + + // Waits for storages to be cleared and for version change transactions to + // complete before dispatching the given runnable. + nsresult + WaitForOpenAllowed(const OriginOrPatternString& aOriginOrPattern, + nsIAtom* aId, + nsIRunnable* aRunnable); + + // Acquire exclusive access to the storage given (waits for all others to + // close). If storages need to close first, the callback will be invoked + // with an array of said storages. + nsresult + AcquireExclusiveAccess(nsIOfflineStorage* aStorage, + const nsACString& aOrigin, + AcquireListener* aListener, + WaitingOnStoragesCallback aCallback, + void* aClosure) + { + NS_ASSERTION(aStorage, "Need a storage here!"); + return AcquireExclusiveAccess(aOrigin, aStorage, aListener, aCallback, + aClosure); + } + + nsresult + AcquireExclusiveAccess(const nsACString& aOrigin, + AcquireListener* aListener, + WaitingOnStoragesCallback aCallback, + void* aClosure) + { + return AcquireExclusiveAccess(aOrigin, nullptr, aListener, aCallback, + aClosure); + } + + void + AllowNextSynchronizedOp(const OriginOrPatternString& aOriginOrPattern, + nsIAtom* aId); + + bool + IsClearOriginPending(const nsACString& aPattern) + { + return !!FindSynchronizedOp(aPattern, nullptr); + } + + nsresult + GetDirectoryForOrigin(const nsACString& aASCIIOrigin, + nsIFile** aDirectory) const; + + nsresult + EnsureOriginIsInitialized(const nsACString& aOrigin, + StoragePrivilege aPrivilege, + nsIFile** aDirectory); + + void + UninitializeOriginsByPattern(const nsACString& aPattern); + + nsIThread* + IOThread() + { + NS_ASSERTION(mIOThread, "This should never be null!"); + return mIOThread; + } + + already_AddRefed + GetClient(Client::Type aClientType); + + const nsString& + GetBaseDirectory() const + { + return mStorageBasePath; + } + + static uint32_t + GetStorageQuotaMB(); + + static already_AddRefed + GetStorageId(const nsACString& aOrigin, + const nsAString& aName); + + static nsresult + GetASCIIOriginFromURI(nsIURI* aURI, + uint32_t aAppId, + bool aInMozBrowser, + nsACString& aASCIIOrigin); + + static nsresult + GetASCIIOriginFromPrincipal(nsIPrincipal* aPrincipal, + nsACString& aASCIIOrigin); + + static nsresult + GetASCIIOriginFromWindow(nsPIDOMWindow* aWindow, + nsACString& aASCIIOrigin); + + static void + GetOriginPatternString(uint32_t aAppId, bool aBrowserOnly, + const nsACString& aOrigin, nsAutoCString& _retval) + { + return GetOriginPatternString(aAppId, + aBrowserOnly ? MozBrowser : NotMozBrowser, + aOrigin, _retval); + } + + static void + GetOriginPatternStringMaybeIgnoreBrowser(uint32_t aAppId, bool aBrowserOnly, + nsAutoCString& _retval) + { + return GetOriginPatternString(aAppId, + aBrowserOnly ? MozBrowser : IgnoreMozBrowser, + EmptyCString(), _retval); + } + private: QuotaManager(); virtual ~QuotaManager(); - bool + nsresult Init(); void @@ -167,6 +272,33 @@ private: bool LockedQuotaIsLifted(); + nsresult + AcquireExclusiveAccess(const nsACString& aOrigin, + nsIOfflineStorage* aStorage, + AcquireListener* aListener, + WaitingOnStoragesCallback aCallback, + void* aClosure); + + nsresult + RunSynchronizedOp(nsIOfflineStorage* aStorage, + SynchronizedOp* aOp); + + SynchronizedOp* + FindSynchronizedOp(const nsACString& aPattern, + nsISupports* aId); + + nsresult + ClearStoragesForApp(uint32_t aAppId, bool aBrowserOnly); + + nsresult + MaybeUpgradeOriginDirectory(nsIFile* aDirectory); + + static void + GetOriginPatternString(uint32_t aAppId, + MozBrowserPatternFlag aBrowserFlag, + const nsACString& aOrigin, + nsAutoCString& _retval); + // TLS storage index for the current thread's window. unsigned int mCurrentWindowIndex; @@ -177,6 +309,27 @@ private: // A map of Windows to the corresponding quota helper. nsRefPtrHashtable, CheckQuotaHelper> mCheckQuotaHelpers; + + // Maintains a list of live storages per origin. + nsClassHashtable > mLiveStorages; + + // Maintains a list of synchronized operatons that are in progress or queued. + nsAutoTArray, 5> mSynchronizedOps; + + // Thread on which IO is performed. + nsCOMPtr mIOThread; + + // A timer that gets activated at shutdown to ensure we close all storages. + nsCOMPtr mShutdownTimer; + + // A list of all successfully initialized origins. This list isn't protected + // by any mutex but it is only ever touched on the IO thread. + nsTArray mInitializedOrigins; + + nsAutoTArray, Client::TYPE_MAX> mClients; + + nsString mStorageBasePath; }; class AutoEnterWindow diff --git a/dom/quota/QuotaObject.cpp b/dom/quota/QuotaObject.cpp new file mode 100644 index 000000000000..410b557bdfc2 --- /dev/null +++ b/dom/quota/QuotaObject.cpp @@ -0,0 +1,159 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "QuotaObject.h" + +#include "QuotaManager.h" + +USING_QUOTA_NAMESPACE + +void +QuotaObject::AddRef() +{ + QuotaManager* quotaManager = QuotaManager::Get(); + if (!quotaManager) { + NS_ERROR("Null quota manager, this shouldn't happen, possible leak!"); + + NS_AtomicIncrementRefcnt(mRefCnt); + + return; + } + + MutexAutoLock lock(quotaManager->mQuotaMutex); + + ++mRefCnt; +} + +void +QuotaObject::Release() +{ + QuotaManager* quotaManager = QuotaManager::Get(); + if (!quotaManager) { + NS_ERROR("Null quota manager, this shouldn't happen, possible leak!"); + + nsrefcnt count = NS_AtomicDecrementRefcnt(mRefCnt); + if (count == 0) { + mRefCnt = 1; + delete this; + } + + return; + } + + { + MutexAutoLock lock(quotaManager->mQuotaMutex); + + --mRefCnt; + + if (mRefCnt > 0) { + return; + } + + if (mOriginInfo) { + mOriginInfo->mQuotaObjects.Remove(mPath); + } + } + + delete this; +} + +void +QuotaObject::UpdateSize(int64_t aSize) +{ + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "Shouldn't be null!"); + + MutexAutoLock lock(quotaManager->mQuotaMutex); + + if (mOriginInfo) { + mOriginInfo->mUsage -= mSize; + mSize = aSize; + mOriginInfo->mUsage += mSize; + } +} + +bool +QuotaObject::MaybeAllocateMoreSpace(int64_t aOffset, int32_t aCount) +{ + int64_t end = aOffset + aCount; + + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "Shouldn't be null!"); + + MutexAutoLock lock(quotaManager->mQuotaMutex); + + if (mSize >= end || !mOriginInfo) { + return true; + } + + int64_t newUsage = mOriginInfo->mUsage - mSize + end; + if (newUsage > mOriginInfo->mLimit) { + // This will block the thread, but it will also drop the mutex while + // waiting. The mutex will be reacquired again when the waiting is finished. + if (!quotaManager->LockedQuotaIsLifted()) { + return false; + } + + // Threads raced, the origin info removal has been done by some other + // thread. + if (!mOriginInfo) { + // The other thread could allocate more space. + if (end > mSize) { + mSize = end; + } + + return true; + } + + nsCString origin = mOriginInfo->mOrigin; + + mOriginInfo->LockedClearOriginInfos(); + NS_ASSERTION(!mOriginInfo, + "Should have cleared in LockedClearOriginInfos!"); + + quotaManager->mOriginInfos.Remove(origin); + + // Some other thread could increase the size without blocking (increasing + // the origin usage without hitting the limit), but no more than this one. + NS_ASSERTION(mSize < end, "This shouldn't happen!"); + + mSize = end; + + return true; + } + + mOriginInfo->mUsage = newUsage; + mSize = end; + + return true; +} + +#ifdef DEBUG +void +OriginInfo::LockedClearOriginInfos() +{ + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "Shouldn't be null!"); + + quotaManager->mQuotaMutex.AssertCurrentThreadOwns(); + + mQuotaObjects.EnumerateRead(ClearOriginInfoCallback, nullptr); +} +#endif + +// static +PLDHashOperator +OriginInfo::ClearOriginInfoCallback(const nsAString& aKey, + QuotaObject* aValue, + void* aUserArg) +{ + NS_ASSERTION(!aKey.IsEmpty(), "Empty key!"); + NS_ASSERTION(aValue, "Null pointer!"); + + aValue->mOriginInfo = nullptr; + + return PL_DHASH_NEXT; +} diff --git a/dom/quota/QuotaObject.h b/dom/quota/QuotaObject.h new file mode 100644 index 000000000000..0ec3447a605c --- /dev/null +++ b/dom/quota/QuotaObject.h @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_quota_quotaobject_h__ +#define mozilla_dom_quota_quotaobject_h__ + +#include "mozilla/dom/quota/QuotaCommon.h" + +#include "nsDataHashtable.h" + +BEGIN_QUOTA_NAMESPACE + +class OriginInfo; +class QuotaManager; + +class QuotaObject +{ + friend class OriginInfo; + friend class QuotaManager; + +public: + void + AddRef(); + + void + Release(); + + void + UpdateSize(int64_t aSize); + + bool + MaybeAllocateMoreSpace(int64_t aOffset, int32_t aCount); + +private: + QuotaObject(OriginInfo* aOriginInfo, const nsAString& aPath, int64_t aSize) + : mOriginInfo(aOriginInfo), mPath(aPath), mSize(aSize) + { } + + virtual ~QuotaObject() + { } + + nsAutoRefCnt mRefCnt; + + OriginInfo* mOriginInfo; + nsString mPath; + int64_t mSize; +}; + +class OriginInfo +{ + friend class QuotaManager; + friend class QuotaObject; + +public: + OriginInfo(const nsACString& aOrigin, int64_t aLimit, int64_t aUsage) + : mOrigin(aOrigin), mLimit(aLimit), mUsage(aUsage) + { + mQuotaObjects.Init(); + } + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OriginInfo) + +private: + void +#ifdef DEBUG + LockedClearOriginInfos(); +#else + LockedClearOriginInfos() + { + mQuotaObjects.EnumerateRead(ClearOriginInfoCallback, nullptr); + } +#endif + + static PLDHashOperator + ClearOriginInfoCallback(const nsAString& aKey, + QuotaObject* aValue, void* aUserArg); + + nsDataHashtable mQuotaObjects; + + nsCString mOrigin; + int64_t mLimit; + int64_t mUsage; +}; + +END_QUOTA_NAMESPACE + +#endif // mozilla_dom_quota_quotaobject_h__ diff --git a/dom/quota/StorageMatcher.h b/dom/quota/StorageMatcher.h new file mode 100644 index 000000000000..156f4c55add4 --- /dev/null +++ b/dom/quota/StorageMatcher.h @@ -0,0 +1,191 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_quota_patternmatcher_h__ +#define mozilla_dom_quota_patternmatcher_h__ + +#include "mozilla/dom/quota/QuotaCommon.h" + +#include "ArrayCluster.h" +#include "Utilities.h" + +BEGIN_QUOTA_NAMESPACE + +template > +class StorageMatcher : public ValueType +{ + typedef StorageMatcher SelfType; + + struct Closure + { + Closure(SelfType& aSelf) + : mSelf(aSelf), mPattern(EmptyCString()), mIndexes(nullptr) + { } + + Closure(SelfType& aSelf, const nsACString& aPattern) + : mSelf(aSelf), mPattern(aPattern), mIndexes(nullptr) + { } + + Closure(SelfType& aSelf, const nsTArray* aIndexes) + : mSelf(aSelf), mPattern(EmptyCString()), mIndexes(aIndexes) + { } + + Closure(SelfType& aSelf, const nsACString& aPattern, + const nsTArray* aIndexes) + : mSelf(aSelf), mPattern(aPattern), mIndexes(aIndexes) + { } + + SelfType& mSelf; + const nsACString& mPattern; + const nsTArray* mIndexes; + }; + +public: + template + void + Find(const nsBaseHashtable& aHashtable, + const nsACString& aPattern) + { + SelfType::Clear(); + + Closure closure(*this, aPattern); + aHashtable.EnumerateRead(SelfType::MatchPattern, &closure); + } + + template + void + Find(const nsBaseHashtable& aHashtable, + const nsTArray* aIndexes) + { + SelfType::Clear(); + + Closure closure(*this, aIndexes); + aHashtable.EnumerateRead(SelfType::MatchIndexes, &closure); + } + + template + void + Find(const nsBaseHashtable& aHashtable, + uint32_t aIndex) + { + nsAutoTArray indexes; + indexes.AppendElement(aIndex); + + Find(aHashtable, &indexes); + } + + template + void + Find(const nsBaseHashtable& aHashtable, + const nsACString& aPattern, + const nsTArray* aIndexes) + { + SelfType::Clear(); + + Closure closure(*this, aPattern, aIndexes); + aHashtable.EnumerateRead(SelfType::MatchPatternAndIndexes, &closure); + } + + template + void + Find(const nsBaseHashtable& aHashtable, + const nsACString& aPattern, + uint32_t aIndex) + { + nsAutoTArray indexes; + indexes.AppendElement(aIndex); + + Find(aHashtable, aPattern, &indexes); + } + + template + void + Find(const nsBaseHashtable& aHashtable) + { + SelfType::Clear(); + + Closure closure(*this); + aHashtable.EnumerateRead(SelfType::MatchAll, &closure); + } + +private: + static PLDHashOperator + MatchPattern(const nsACString& aKey, + BaseType* aValue, + void* aUserArg) + { + MOZ_ASSERT(!aKey.IsEmpty(), "Empty key!"); + MOZ_ASSERT(aValue, "Null pointer!"); + MOZ_ASSERT(aUserArg, "Null pointer!"); + + Closure* closure = static_cast(aUserArg); + + if (PatternMatchesOrigin(closure->mPattern, aKey)) { + aValue->AppendElementsTo(closure->mSelf); + } + + return PL_DHASH_NEXT; + } + + static PLDHashOperator + MatchIndexes(const nsACString& aKey, + BaseType* aValue, + void* aUserArg) + { + MOZ_ASSERT(!aKey.IsEmpty(), "Empty key!"); + MOZ_ASSERT(aValue, "Null pointer!"); + MOZ_ASSERT(aUserArg, "Null pointer!"); + + Closure* closure = static_cast(aUserArg); + + for (uint32_t index = 0; index < closure->mIndexes->Length(); index++) { + aValue->AppendElementsTo(closure->mIndexes->ElementAt(index), + closure->mSelf); + } + + return PL_DHASH_NEXT; + } + + static PLDHashOperator + MatchPatternAndIndexes(const nsACString& aKey, + BaseType* aValue, + void* aUserArg) + { + MOZ_ASSERT(!aKey.IsEmpty(), "Empty key!"); + MOZ_ASSERT(aValue, "Null pointer!"); + MOZ_ASSERT(aUserArg, "Null pointer!"); + + Closure* closure = static_cast(aUserArg); + + if (PatternMatchesOrigin(closure->mPattern, aKey)) { + for (uint32_t index = 0; index < closure->mIndexes->Length(); index++) { + aValue->AppendElementsTo(closure->mIndexes->ElementAt(index), + closure->mSelf); + } + } + + return PL_DHASH_NEXT; + } + + static PLDHashOperator + MatchAll(const nsACString& aKey, + BaseType* aValue, + void* aUserArg) + { + MOZ_ASSERT(!aKey.IsEmpty(), "Empty key!"); + MOZ_ASSERT(aValue, "Null pointer!"); + MOZ_ASSERT(aUserArg, "Null pointer!"); + + Closure* closure = static_cast(aUserArg); + aValue->AppendElementsTo(closure->mSelf); + + return PL_DHASH_NEXT; + } +}; + +END_QUOTA_NAMESPACE + +#endif // mozilla_dom_quota_patternmatcher_h__ diff --git a/dom/quota/StoragePrivilege.h b/dom/quota/StoragePrivilege.h new file mode 100644 index 000000000000..6cd9c7b75964 --- /dev/null +++ b/dom/quota/StoragePrivilege.h @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_quota_storageprivilege_h__ +#define mozilla_dom_quota_storageprivilege_h__ + +#include "mozilla/dom/quota/QuotaCommon.h" + +BEGIN_QUOTA_NAMESPACE + +enum StoragePrivilege { + Content, + Chrome +}; + +END_QUOTA_NAMESPACE + +#endif // mozilla_dom_quota_storageprivilege_h__ diff --git a/dom/quota/UsageRunnable.h b/dom/quota/UsageRunnable.h new file mode 100644 index 000000000000..3542d862d66d --- /dev/null +++ b/dom/quota/UsageRunnable.h @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_quota_usagerunnable_h__ +#define mozilla_dom_quota_usagerunnable_h__ + +#include "mozilla/dom/quota/QuotaCommon.h" + +#include "Utilities.h" + +BEGIN_QUOTA_NAMESPACE + +class UsageRunnable +{ +public: + UsageRunnable() + : mCanceled(0), mDatabaseUsage(0), mFileUsage(0) + { } + + virtual ~UsageRunnable() + { } + + bool + Canceled() + { + return mCanceled; + } + + void + AppendToDatabaseUsage(uint64_t aUsage) + { + IncrementUsage(&mDatabaseUsage, aUsage); + } + + void + AppendToFileUsage(uint64_t aUsage) + { + IncrementUsage(&mFileUsage, aUsage); + } + + uint64_t + DatabaseUsage() + { + return mDatabaseUsage; + } + + uint64_t + FileUsage() + { + return mFileUsage; + } + + uint64_t + TotalUsage() + { + uint64_t totalUsage = mDatabaseUsage; + IncrementUsage(&totalUsage, mFileUsage); + return totalUsage; + } + + void + ResetUsage() + { + mDatabaseUsage = 0; + mFileUsage = 0; + } + +protected: + int32_t mCanceled; + +private: + uint64_t mDatabaseUsage; + uint64_t mFileUsage; +}; + +END_QUOTA_NAMESPACE + +#endif // mozilla_dom_quota_usagerunnable_h__ diff --git a/dom/quota/Utilities.h b/dom/quota/Utilities.h new file mode 100644 index 000000000000..7b35f246ca92 --- /dev/null +++ b/dom/quota/Utilities.h @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_quota_utilities_h__ +#define mozilla_dom_quota_utilities_h__ + +#include "mozilla/dom/quota/QuotaCommon.h" + +BEGIN_QUOTA_NAMESPACE + +inline void +IncrementUsage(uint64_t* aUsage, uint64_t aDelta) +{ + // Watch for overflow! + if ((UINT64_MAX - *aUsage) < aDelta) { + NS_WARNING("Usage exceeds the maximum!"); + *aUsage = UINT64_MAX; + } + else { + *aUsage += aDelta; + } +} + +inline bool +PatternMatchesOrigin(const nsACString& aPatternString, const nsACString& aOrigin) +{ + // Aren't we smart! + return StringBeginsWith(aOrigin, aPatternString); +} + +END_QUOTA_NAMESPACE + +#endif // mozilla_dom_quota_utilities_h__ diff --git a/dom/quota/moz.build b/dom/quota/moz.build index 1c46131196ba..e9d12b779e2f 100644 --- a/dom/quota/moz.build +++ b/dom/quota/moz.build @@ -3,6 +3,12 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. +XPIDL_SOURCES += [ + 'nsIQuotaManager.idl', + 'nsIQuotaRequest.idl', + 'nsIUsageCallback.idl', +] + XPIDL_MODULE = 'dom_quota' MODULE = 'dom' diff --git a/dom/quota/nsIOfflineStorage.h b/dom/quota/nsIOfflineStorage.h new file mode 100644 index 000000000000..8155b52f85bc --- /dev/null +++ b/dom/quota/nsIOfflineStorage.h @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsIOfflineStorage_h__ +#define nsIOfflineStorage_h__ + +#include "nsIFileStorage.h" + +#define NS_OFFLINESTORAGE_IID \ + {0xe531b6e0, 0x55b8, 0x4f39, \ + { 0x95, 0xbb, 0x97, 0x21, 0x4c, 0xb0, 0xf6, 0x1a } } + +namespace mozilla { +namespace dom { +namespace quota { +class Client; +} +} +} + +class nsIOfflineStorage : public nsIFileStorage +{ +public: + typedef mozilla::dom::quota::Client Client; + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_OFFLINESTORAGE_IID) + + NS_IMETHOD_(Client*) + GetClient() = 0; + + NS_IMETHOD_(bool) + IsOwned(nsPIDOMWindow* aOwner) = 0; + + NS_IMETHOD_(const nsACString&) + Origin() = 0; + + // Implementation of this method should close the storage (without aborting + // running operations nor discarding pending operations). + NS_IMETHOD_(nsresult) + Close() = 0; + + // Whether or not the storage has had Close called on it. + NS_IMETHOD_(bool) + IsClosed() = 0; + + // Implementation of this method should close the storage, all running + // operations should be aborted and pending operations should be discarded. + NS_IMETHOD_(void) + Invalidate() = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIOfflineStorage, NS_OFFLINESTORAGE_IID) + +#define NS_DECL_NSIOFFLINESTORAGE \ + NS_IMETHOD_(Client*) \ + GetClient() MOZ_OVERRIDE; \ + \ + NS_IMETHOD_(bool) \ + IsOwned(nsPIDOMWindow* aOwner) MOZ_OVERRIDE; \ + \ + NS_IMETHOD_(const nsACString&) \ + Origin() MOZ_OVERRIDE; \ + \ + NS_IMETHOD_(nsresult) \ + Close() MOZ_OVERRIDE; \ + \ + NS_IMETHOD_(bool) \ + IsClosed() MOZ_OVERRIDE; \ + \ + NS_IMETHOD_(void) \ + Invalidate() MOZ_OVERRIDE; + +#define NS_DECL_NSIOFFLINESTORAGE_NOCLOSE \ + NS_IMETHOD_(Client*) \ + GetClient() MOZ_OVERRIDE; \ + \ + NS_IMETHOD_(bool) \ + IsOwned(nsPIDOMWindow* aOwner) MOZ_OVERRIDE; \ + \ + NS_IMETHOD_(const nsACString&) \ + Origin() MOZ_OVERRIDE; \ + \ + NS_IMETHOD_(bool) \ + IsClosed() MOZ_OVERRIDE; \ + \ + NS_IMETHOD_(void) \ + Invalidate() MOZ_OVERRIDE; + +#endif // nsIOfflineStorage_h__ diff --git a/dom/quota/nsIQuotaManager.idl b/dom/quota/nsIQuotaManager.idl new file mode 100644 index 000000000000..0e9195badd3e --- /dev/null +++ b/dom/quota/nsIQuotaManager.idl @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIQuotaRequest; +interface nsIURI; +interface nsIUsageCallback; + +[scriptable, builtinclass, uuid(8d74e6f8-81c3-4045-9bb7-70bdb7b11b25)] +interface nsIQuotaManager : nsISupports +{ + /** + * Schedules an asynchronous callback that will return the total amount of + * disk space being used by storages for the given origin. + * + * @param aURI + * The URI whose usage is being queried. + * @param aCallback + * The callback that will be called when the usage is available. + */ + [optional_argc] + nsIQuotaRequest + getUsageForURI(in nsIURI aURI, + in nsIUsageCallback aCallback, + [optional] in unsigned long aAppId, + [optional] in boolean aInMozBrowserOnly); + + /** + * Removes all storages stored for the given URI. The files may not be + * deleted immediately depending on prohibitive concurrent operations. + * + * @param aURI + * The URI whose storages are to be cleared. + */ + [optional_argc] + void + clearStoragesForURI(in nsIURI aURI, + [optional] in unsigned long aAppId, + [optional] in boolean aInMozBrowserOnly); +}; diff --git a/dom/quota/nsIQuotaRequest.idl b/dom/quota/nsIQuotaRequest.idl new file mode 100644 index 000000000000..8cf2fdc78adb --- /dev/null +++ b/dom/quota/nsIQuotaRequest.idl @@ -0,0 +1,14 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, function, uuid(d96769ed-63ac-4070-ac5a-4b0e1728618a)] +interface nsIQuotaRequest : nsISupports +{ + void + cancel(); +}; diff --git a/dom/quota/nsIUsageCallback.idl b/dom/quota/nsIUsageCallback.idl new file mode 100644 index 000000000000..eb365e428fa8 --- /dev/null +++ b/dom/quota/nsIUsageCallback.idl @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIURI; + +[scriptable, function, uuid(7b0f9928-0ddc-42c7-b9f2-6b2308b90b18)] +interface nsIUsageCallback : nsISupports +{ + void + onUsageResult(in nsIURI aURI, + in unsigned long long aUsage, + in unsigned long long aFileUsage, + in unsigned long aAppId, + in boolean aInMozBrowserOnly); +}; diff --git a/js/xpconnect/src/dom_quickstubs.qsconf b/js/xpconnect/src/dom_quickstubs.qsconf index 9221c4c5ee0a..2e80c9f371d2 100644 --- a/js/xpconnect/src/dom_quickstubs.qsconf +++ b/js/xpconnect/src/dom_quickstubs.qsconf @@ -215,13 +215,17 @@ members = [ 'nsIIDBTransaction.*', 'nsIIDBOpenDBRequest.*', 'nsIIDBVersionChangeEvent.*', - 'nsIIndexedDatabaseUsageCallback.*', 'nsIIndexedDatabaseManager.*', 'nsIDOMDOMError.*', # dom/file 'nsIDOMLockedFile.*', + + # dom/quota + 'nsIQuotaManager.*', + 'nsIQuotaRequest.*', + 'nsIUsageCallback.*', ] # Most interfaces can be found by searching the includePath; to find diff --git a/layout/build/nsLayoutCID.h b/layout/build/nsLayoutCID.h index 3e24d438a6ea..b07f20cddee2 100644 --- a/layout/build/nsLayoutCID.h +++ b/layout/build/nsLayoutCID.h @@ -95,4 +95,8 @@ #define DOMREQUEST_SERVICE_CID \ { 0x3160e271, 0x138d, 0x4cc7, { 0x9d, 0x63, 0x64, 0x29, 0xf1, 0x69, 0x57, 0xc7 } } +// {5a75c25a-5e7e-4d90-8f7c-07eb15cc0aa8} +#define QUOTA_MANAGER_CID \ +{ 0x5a75c25a, 0x5e7e, 0x4d90, { 0x8f, 0x7c, 0x07, 0xeb, 0x15, 0xcc, 0x0a, 0xa8 } } + #endif /* nsLayoutCID_h__ */ diff --git a/layout/build/nsLayoutModule.cpp b/layout/build/nsLayoutModule.cpp index 8228e1838fb1..c557afc6ce62 100644 --- a/layout/build/nsLayoutModule.cpp +++ b/layout/build/nsLayoutModule.cpp @@ -90,6 +90,7 @@ #include "mozilla/dom/EventSource.h" #include "mozilla/dom/indexedDB/IndexedDatabaseManager.h" #include "mozilla/dom/network/TCPSocketChild.h" +#include "mozilla/dom/quota/QuotaManager.h" #include "mozilla/OSFileConstants.h" #include "mozilla/Services.h" @@ -248,6 +249,7 @@ using namespace mozilla::dom::mobilemessage; using mozilla::dom::alarm::AlarmHalService; using mozilla::dom::indexedDB::IndexedDatabaseManager; using mozilla::dom::power::PowerManagerService; +using mozilla::dom::quota::QuotaManager; using mozilla::dom::TCPSocketChild; using mozilla::dom::time::TimeService; @@ -280,6 +282,8 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(IndexedDatabaseManager, IndexedDatabaseManager::FactoryCreate) NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(DOMRequestService, DOMRequestService::FactoryCreate) +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(QuotaManager, + QuotaManager::FactoryCreate) #ifdef MOZ_B2G_RIL NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(SystemWorkerManager, SystemWorkerManager::FactoryCreate) @@ -783,6 +787,7 @@ NS_DEFINE_NAMED_CID(NS_DOMJSON_CID); NS_DEFINE_NAMED_CID(NS_TEXTEDITOR_CID); NS_DEFINE_NAMED_CID(INDEXEDDB_MANAGER_CID); NS_DEFINE_NAMED_CID(DOMREQUEST_SERVICE_CID); +NS_DEFINE_NAMED_CID(QUOTA_MANAGER_CID); #ifdef MOZ_B2G_RIL NS_DEFINE_NAMED_CID(SYSTEMWORKERMANAGER_CID); #endif @@ -1069,6 +1074,7 @@ static const mozilla::Module::CIDEntry kLayoutCIDs[] = { { &kNS_TEXTEDITOR_CID, false, NULL, nsPlaintextEditorConstructor }, { &kINDEXEDDB_MANAGER_CID, false, NULL, IndexedDatabaseManagerConstructor }, { &kDOMREQUEST_SERVICE_CID, false, NULL, DOMRequestServiceConstructor }, + { &kQUOTA_MANAGER_CID, false, NULL, QuotaManagerConstructor }, #ifdef MOZ_B2G_RIL { &kSYSTEMWORKERMANAGER_CID, true, NULL, SystemWorkerManagerConstructor }, #endif @@ -1216,6 +1222,7 @@ static const mozilla::Module::ContractIDEntry kLayoutContracts[] = { { "@mozilla.org/editor/texteditor;1", &kNS_TEXTEDITOR_CID }, { INDEXEDDB_MANAGER_CONTRACTID, &kINDEXEDDB_MANAGER_CID }, { DOMREQUEST_SERVICE_CONTRACTID, &kDOMREQUEST_SERVICE_CID }, + { QUOTA_MANAGER_CONTRACTID, &kQUOTA_MANAGER_CID }, #ifdef MOZ_B2G_RIL { SYSTEMWORKERMANAGER_CONTRACTID, &kSYSTEMWORKERMANAGER_CID }, #endif @@ -1294,7 +1301,7 @@ static const mozilla::Module::CategoryEntry kLayoutCategories[] = { { "net-channel-event-sinks", "CSPService", CSPSERVICE_CONTRACTID }, { JAVASCRIPT_GLOBAL_STATIC_NAMESET_CATEGORY, "PrivilegeManager", NS_SECURITYNAMESET_CONTRACTID }, { "app-startup", "Script Security Manager", "service," NS_SCRIPTSECURITYMANAGER_CONTRACTID }, - { TOPIC_WEB_APP_CLEAR_DATA, "IndexedDatabaseManager", "service," INDEXEDDB_MANAGER_CONTRACTID }, + { TOPIC_WEB_APP_CLEAR_DATA, "QuotaManager", "service," QUOTA_MANAGER_CONTRACTID }, #ifdef MOZ_WIDGET_GONK { "app-startup", "Volume Service", "service," NS_VOLUMESERVICE_CONTRACTID }, #endif diff --git a/mobile/android/installer/package-manifest.in b/mobile/android/installer/package-manifest.in index 0d2efcd999fc..2068dde29680 100644 --- a/mobile/android/installer/package-manifest.in +++ b/mobile/android/installer/package-manifest.in @@ -131,6 +131,7 @@ @BINPATH@/components/dom_json.xpt @BINPATH@/components/dom_browserelement.xpt @BINPATH@/components/dom_power.xpt +@BINPATH@/components/dom_quota.xpt @BINPATH@/components/dom_range.xpt @BINPATH@/components/dom_settings.xpt @BINPATH@/components/dom_sidebar.xpt diff --git a/storage/src/TelemetryVFS.cpp b/storage/src/TelemetryVFS.cpp index e4fce091f725..fd4abe311f0e 100644 --- a/storage/src/TelemetryVFS.cpp +++ b/storage/src/TelemetryVFS.cpp @@ -11,6 +11,7 @@ #include "nsThreadUtils.h" #include "mozilla/Util.h" #include "mozilla/dom/quota/QuotaManager.h" +#include "mozilla/dom/quota/QuotaObject.h" /** * This preference is a workaround to allow users/sysadmins to identify diff --git a/toolkit/forgetaboutsite/ForgetAboutSite.jsm b/toolkit/forgetaboutsite/ForgetAboutSite.jsm index 66e27004a35e..5e4a180054dd 100644 --- a/toolkit/forgetaboutsite/ForgetAboutSite.jsm +++ b/toolkit/forgetaboutsite/ForgetAboutSite.jsm @@ -209,9 +209,9 @@ this.ForgetAboutSite = { } } - // Indexed DB - let (idbm = Cc["@mozilla.org/dom/indexeddb/manager;1"]. - getService(Ci.nsIIndexedDatabaseManager)) { + // Offline Storages + let (qm = Cc["@mozilla.org/dom/quota/manager;1"]. + getService(Ci.nsIQuotaManager)) { // delete data from both HTTP and HTTPS sites let caUtils = {}; let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]. @@ -220,8 +220,8 @@ this.ForgetAboutSite = { caUtils); let httpURI = caUtils.makeURI("http://" + aDomain); let httpsURI = caUtils.makeURI("https://" + aDomain); - idbm.clearDatabasesForURI(httpURI); - idbm.clearDatabasesForURI(httpsURI); + qm.clearStoragesForURI(httpURI); + qm.clearStoragesForURI(httpsURI); } // Everybody else (including extensions)