From c5676a58c73b5ab274305ead293329f39081205c Mon Sep 17 00:00:00 2001 From: Jan Varga Date: Thu, 29 Nov 2018 21:47:27 +0100 Subject: [PATCH] Bug 1286798 - Part 5: More integration with QuotaManager; r=asuth Preparation of datastores now creates real database files on disk. The LocalStorage directory is protected by a directory lock. Infrastructure for database schema upgrades is in place too. Database actors are now explicitely tracked by the datastore. When the last actor finishes the directory lock is released. Added also implementation for QuotaClient::GetUsageForOrigin() and QuotaClient::AbortOperations(). --- .../test/browser/browser.ini | 2 +- dom/localstorage/ActorsChild.cpp | 5 + dom/localstorage/ActorsParent.cpp | 1191 ++++++++++++++++- dom/localstorage/LSObject.cpp | 6 + dom/localstorage/ReportInternalError.cpp | 36 + dom/localstorage/ReportInternalError.h | 32 + dom/localstorage/moz.build | 1 + dom/quota/ActorsParent.cpp | 4 + .../test/unit/test_removeLocalStorage.js | 4 + .../extensions/test/mochitest/mochitest.ini | 2 +- .../test/xpcshell/xpcshell-common.ini | 2 +- 11 files changed, 1253 insertions(+), 32 deletions(-) create mode 100644 dom/localstorage/ReportInternalError.cpp create mode 100644 dom/localstorage/ReportInternalError.h diff --git a/browser/components/contextualidentity/test/browser/browser.ini b/browser/components/contextualidentity/test/browser/browser.ini index 451bcd062722..7cc9cd57c6ec 100644 --- a/browser/components/contextualidentity/test/browser/browser.ini +++ b/browser/components/contextualidentity/test/browser/browser.ini @@ -10,7 +10,7 @@ support-files = [browser_aboutURLs.js] [browser_eme.js] [browser_favicon.js] -#[browser_forgetaboutsite.js] +[browser_forgetaboutsite.js] [browser_forgetAPI_cookie_getCookiesWithOriginAttributes.js] [browser_restore_getCookiesWithOriginAttributes.js] [browser_forgetAPI_EME_forgetThisSite.js] diff --git a/dom/localstorage/ActorsChild.cpp b/dom/localstorage/ActorsChild.cpp index 8f00d6858cd1..788664ffbf97 100644 --- a/dom/localstorage/ActorsChild.cpp +++ b/dom/localstorage/ActorsChild.cpp @@ -64,6 +64,11 @@ LSDatabaseChild::RecvRequestAllowToClose() if (mDatabase) { mDatabase->AllowToClose(); + + // TODO: A new datastore will be prepared at first LocalStorage API + // synchronous call. It would be better to start preparing a new + // datastore right here, but asynchronously. + // However, we probably shouldn't do that if we are shutting down. } return IPC_OK(); diff --git a/dom/localstorage/ActorsParent.cpp b/dom/localstorage/ActorsParent.cpp index cdaf1cdfbfb5..b9f421e379a3 100644 --- a/dom/localstorage/ActorsParent.cpp +++ b/dom/localstorage/ActorsParent.cpp @@ -7,16 +7,23 @@ #include "ActorsParent.h" #include "LocalStorageCommon.h" +#include "mozIStorageConnection.h" +#include "mozIStorageService.h" +#include "mozStorageCID.h" +#include "mozStorageHelper.h" #include "mozilla/Unused.h" #include "mozilla/dom/PBackgroundLSDatabaseParent.h" #include "mozilla/dom/PBackgroundLSRequestParent.h" #include "mozilla/dom/PBackgroundLSSharedTypes.h" #include "mozilla/dom/quota/QuotaManager.h" +#include "mozilla/dom/quota/UsageInfo.h" #include "mozilla/ipc/BackgroundParent.h" #include "mozilla/ipc/PBackgroundParent.h" #include "mozilla/ipc/PBackgroundSharedTypes.h" #include "nsClassHashtable.h" #include "nsDataHashtable.h" +#include "nsISimpleEnumerator.h" +#include "ReportInternalError.h" #define DISABLE_ASSERTS_FOR_FUZZING 0 @@ -26,6 +33,10 @@ #define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__) #endif +#if defined(MOZ_WIDGET_ANDROID) +#define LS_MOBILE +#endif + namespace mozilla { namespace dom { @@ -34,6 +45,352 @@ using namespace mozilla::ipc; namespace { +class Database; + +/******************************************************************************* + * Constants + ******************************************************************************/ + +// Major schema version. Bump for almost everything. +const uint32_t kMajorSchemaVersion = 1; + +// Minor schema version. Should almost always be 0 (maybe bump on release +// branches if we have to). +const uint32_t kMinorSchemaVersion = 0; + +// The schema version we store in the SQLite database is a (signed) 32-bit +// integer. The major version is left-shifted 4 bits so the max value is +// 0xFFFFFFF. The minor version occupies the lower 4 bits and its max is 0xF. +static_assert(kMajorSchemaVersion <= 0xFFFFFFF, + "Major version needs to fit in 28 bits."); +static_assert(kMinorSchemaVersion <= 0xF, + "Minor version needs to fit in 4 bits."); + +const int32_t kSQLiteSchemaVersion = + int32_t((kMajorSchemaVersion << 4) + kMinorSchemaVersion); + +// Changing the value here will override the page size of new databases only. +// A journal mode change and VACUUM are needed to change existing databases, so +// the best way to do that is to use the schema version upgrade mechanism. +const uint32_t kSQLitePageSizeOverride = +#ifdef LS_MOBILE + 512; +#else + 1024; +#endif + +static_assert(kSQLitePageSizeOverride == /* mozStorage default */ 0 || + (kSQLitePageSizeOverride % 2 == 0 && + kSQLitePageSizeOverride >= 512 && + kSQLitePageSizeOverride <= 65536), + "Must be 0 (disabled) or a power of 2 between 512 and 65536!"); + +// Set to some multiple of the page size to grow the database in larger chunks. +const uint32_t kSQLiteGrowthIncrement = kSQLitePageSizeOverride * 2; + +static_assert(kSQLiteGrowthIncrement >= 0 && + kSQLiteGrowthIncrement % kSQLitePageSizeOverride == 0 && + kSQLiteGrowthIncrement < uint32_t(INT32_MAX), + "Must be 0 (disabled) or a positive multiple of the page size!"); + +#define DATA_FILE_NAME "data.sqlite" +#define JOURNAL_FILE_NAME "data.sqlite-journal" + +/******************************************************************************* + * SQLite functions + ******************************************************************************/ + +#if 0 +int32_t +MakeSchemaVersion(uint32_t aMajorSchemaVersion, + uint32_t aMinorSchemaVersion) +{ + return int32_t((aMajorSchemaVersion << 4) + aMinorSchemaVersion); +} +#endif + +nsresult +CreateTables(mozIStorageConnection* aConnection) +{ + AssertIsOnIOThread(); + MOZ_ASSERT(aConnection); + + // Table `database` + nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE TABLE database" + "( origin TEXT NOT NULL" + ", last_vacuum_time INTEGER NOT NULL DEFAULT 0" + ", last_analyze_time INTEGER NOT NULL DEFAULT 0" + ", last_vacuum_size INTEGER NOT NULL DEFAULT 0" + ");" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Table `data` + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE TABLE data" + "( key TEXT PRIMARY KEY" + ", value TEXT NOT NULL" + ", compressed INTEGER NOT NULL DEFAULT 0" + ", lastAccessTime INTEGER NOT NULL DEFAULT 0" + ");" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = aConnection->SetSchemaVersion(kSQLiteSchemaVersion); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +#if 0 +nsresult +UpgradeSchemaFrom1_0To2_0(mozIStorageConnection* aConnection) +{ + AssertIsOnIOThread(); + MOZ_ASSERT(aConnection); + + nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(2, 0)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} +#endif + +nsresult +SetDefaultPragmas(mozIStorageConnection* aConnection) +{ + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(aConnection); + + nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "PRAGMA synchronous = FULL;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + +#ifndef LS_MOBILE + if (kSQLiteGrowthIncrement) { + // This is just an optimization so ignore the failure if the disk is + // currently too full. + rv = aConnection->SetGrowthIncrement(kSQLiteGrowthIncrement, + EmptyCString()); + if (rv != NS_ERROR_FILE_TOO_BIG && NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } +#endif // LS_MOBILE + + return NS_OK; +} + +nsresult +CreateStorageConnection(nsIFile* aDBFile, + const nsACString& aOrigin, + mozIStorageConnection** aConnection) +{ + AssertIsOnIOThread(); + MOZ_ASSERT(aDBFile); + MOZ_ASSERT(aConnection); + + nsresult rv; + + nsCOMPtr ss = + do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr connection; + rv = ss->OpenDatabase(aDBFile, getter_AddRefs(connection)); + if (rv == NS_ERROR_FILE_CORRUPTED) { + // Nuke the database file. + rv = aDBFile->Remove(false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = ss->OpenDatabase(aDBFile, getter_AddRefs(connection)); + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = SetDefaultPragmas(connection); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Check to make sure that the database schema is correct. + int32_t schemaVersion; + rv = connection->GetSchemaVersion(&schemaVersion); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (schemaVersion > kSQLiteSchemaVersion) { + LS_WARNING("Unable to open LocalStorage database, schema is too high!"); + return NS_ERROR_FAILURE; + } + + if (schemaVersion != kSQLiteSchemaVersion) { + const bool newDatabase = !schemaVersion; + + if (newDatabase) { + // Set the page size first. + if (kSQLitePageSizeOverride) { + rv = connection->ExecuteSimpleSQL( + nsPrintfCString("PRAGMA page_size = %" PRIu32 ";", kSQLitePageSizeOverride) + ); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + // We have to set the auto_vacuum mode before opening a transaction. + rv = connection->ExecuteSimpleSQL( +#ifdef LS_MOBILE + // Turn on full auto_vacuum mode to reclaim disk space on mobile + // devices (at the cost of some COMMIT speed). + NS_LITERAL_CSTRING("PRAGMA auto_vacuum = FULL;") +#else + // Turn on incremental auto_vacuum mode on desktop builds. + NS_LITERAL_CSTRING("PRAGMA auto_vacuum = INCREMENTAL;") +#endif + ); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + mozStorageTransaction transaction(connection, false, + mozIStorageConnection::TRANSACTION_IMMEDIATE); + + if (newDatabase) { + rv = CreateTables(connection); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT(NS_SUCCEEDED(connection->GetSchemaVersion(&schemaVersion))); + MOZ_ASSERT(schemaVersion == kSQLiteSchemaVersion); + + nsCOMPtr stmt; + nsresult rv = connection->CreateStatement(NS_LITERAL_CSTRING( + "INSERT INTO database (origin) " + "VALUES (:origin)" + ), getter_AddRefs(stmt)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"), aOrigin); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = stmt->Execute(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } else { + // This logic needs to change next time we change the schema! + static_assert(kSQLiteSchemaVersion == int32_t((1 << 4) + 0), + "Upgrade function needed due to schema version increase."); + + while (schemaVersion != kSQLiteSchemaVersion) { +#if 0 + if (schemaVersion == MakeSchemaVersion(1, 0)) { + rv = UpgradeSchemaFrom1_0To2_0(connection); + } else { +#endif + LS_WARNING("Unable to open LocalStorage database, no upgrade path is " + "available!"); + return NS_ERROR_FAILURE; +#if 0 + } +#endif + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = connection->GetSchemaVersion(&schemaVersion); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + MOZ_ASSERT(schemaVersion == kSQLiteSchemaVersion); + } + + rv = transaction.Commit(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (newDatabase) { + // Windows caches the file size, let's force it to stat the file again. + bool dummy; + rv = aDBFile->Exists(&dummy); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + int64_t fileSize; + rv = aDBFile->GetFileSize(&fileSize); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT(fileSize > 0); + + PRTime vacuumTime = PR_Now(); + MOZ_ASSERT(vacuumTime); + + nsCOMPtr vacuumTimeStmt; + rv = connection->CreateStatement(NS_LITERAL_CSTRING( + "UPDATE database " + "SET last_vacuum_time = :time" + ", last_vacuum_size = :size;" + ), getter_AddRefs(vacuumTimeStmt)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = vacuumTimeStmt->BindInt64ByName(NS_LITERAL_CSTRING("time"), + vacuumTime); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = vacuumTimeStmt->BindInt64ByName(NS_LITERAL_CSTRING("size"), + fileSize); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = vacuumTimeStmt->Execute(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + } + + connection.forget(aConnection); + return NS_OK; +} + /******************************************************************************* * Non-actor class declarations ******************************************************************************/ @@ -130,12 +487,16 @@ protected: class Datastore final { + RefPtr mDirectoryLock; + nsTHashtable> mDatabases; nsDataHashtable mValues; const nsCString mOrigin; + bool mClosed; public: // Created by PrepareDatastoreOp. - explicit Datastore(const nsACString& aOrigin); + Datastore(const nsACString& aOrigin, + already_AddRefed&& aDirectoryLock); const nsCString& Origin() const @@ -143,6 +504,26 @@ public: return mOrigin; } + void + Close(); + + bool + IsClosed() const + { + AssertIsOnBackgroundThread(); + + return mClosed; + } + + void + NoteLiveDatabase(Database* aDatabase); + + void + NoteFinishedDatabase(Database* aDatabase); + + bool + HasLiveDatabases() const; + uint32_t GetLength() const; @@ -174,10 +555,17 @@ private: class PreparedDatastore { RefPtr mDatastore; + // Strings share buffers if possible, so it's not a problem to duplicate the + // origin here. + const nsCString mOrigin; + bool mInvalidated; public: - explicit PreparedDatastore(Datastore* aDatastore) + PreparedDatastore(Datastore* aDatastore, + const nsACString& aOrigin) : mDatastore(aDatastore) + , mOrigin(aOrigin) + , mInvalidated(false) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aDatastore); @@ -191,6 +579,28 @@ public: return mDatastore.forget(); } + + const nsCString& + Origin() const + { + return mOrigin; + } + + void + Invalidate() + { + AssertIsOnBackgroundThread(); + + mInvalidated = true; + } + + bool + IsInvalidated() const + { + AssertIsOnBackgroundThread(); + + return mInvalidated; + } }; /******************************************************************************* @@ -201,7 +611,9 @@ class Database final : public PBackgroundLSDatabaseParent { RefPtr mDatastore; - + // Strings share buffers if possible, so it's not a problem to duplicate the + // origin here. + nsCString mOrigin; bool mAllowedToClose; bool mActorDestroyed; bool mRequestedAllowToClose; @@ -211,7 +623,13 @@ class Database final public: // Created in AllocPBackgroundLSDatabaseParent. - Database(); + explicit Database(const nsACString& aOrigin); + + const nsCString& + Origin() const + { + return mOrigin; + } void SetActorAlive(already_AddRefed&& aDatastore); @@ -279,6 +697,7 @@ private: class PrepareDatastoreOp : public LSRequestBase + , public OpenDirectoryListener { enum class State { @@ -286,14 +705,38 @@ class PrepareDatastoreOp // or OpeningOnOwningThread. Initial, - // Waiting to open/opening on the main thread. Next step is - // SendingReadyMessage. + // Waiting to open/opening on the main thread. Next step is FinishOpen. OpeningOnMainThread, - // Waiting to open/opening on the owning thread. Next step is - // SendingReadyMessage. + // Waiting to open/opening on the owning thread. Next step is FinishOpen. OpeningOnOwningThread, + // Checking if a prepare datastore operation is already running for given + // origin on the PBackground thread. Next step is PreparationPending. + FinishOpen, + + // Opening directory or initializing quota manager on the PBackground + // thread. Next step is either DirectoryOpenPending if quota manager is + // already initialized or QuotaManagerPending if quota manager needs to be + // initialized. + // If a datastore already exists for given origin then the next state is + // SendingReadyMessage. + PreparationPending, + + // Waiting for quota manager initialization to complete on the PBackground + // thread. Next step is either SendingReadyMessage if initialization failed + // or DirectoryOpenPending if initialization succeeded. + QuotaManagerPending, + + // Waiting for directory open allowed on the PBackground thread. The next + // step is either SendingReadyMessage if directory lock failed to acquire, + // or DatabaseWorkOpen if directory lock is acquired. + DirectoryOpenPending, + + // Waiting to do/doing work on the QuotaManager IO thread. Its next step is + // SendingReadyMessage. + DatabaseWorkOpen, + // Waiting to send/sending the ready message on the PBackground thread. Next // step is WaitingForFinish. SendingReadyMessage, @@ -310,15 +753,54 @@ class PrepareDatastoreOp Completed }; + RefPtr mDelayedOp; + RefPtr mDirectoryLock; + RefPtr mDatastore; const LSRequestPrepareDatastoreParams mParams; nsCString mSuffix; nsCString mGroup; + nsCString mMainThreadOrigin; nsCString mOrigin; State mState; + bool mRequestedDirectoryLock; + bool mInvalidated; public: explicit PrepareDatastoreOp(const LSRequestParams& aParams); + bool + OriginIsKnown() const + { + AssertIsOnOwningThread(); + + return !mOrigin.IsEmpty(); + } + + const nsCString& + Origin() const + { + AssertIsOnOwningThread(); + MOZ_ASSERT(OriginIsKnown()); + + return mOrigin; + } + + bool + RequestedDirectoryLock() const + { + AssertIsOnOwningThread(); + + return mRequestedDirectoryLock; + } + + void + Invalidate() + { + AssertIsOnOwningThread(); + + mInvalidated = true; + } + void Dispatch() override; @@ -331,6 +813,30 @@ private: nsresult OpenOnOwningThread(); + nsresult + FinishOpen(); + + nsresult + PreparationOpen(); + + nsresult + BeginDatastorePreparation(); + + nsresult + QuotaManagerOpen(); + + nsresult + OpenDirectory(); + + nsresult + SendToIOThread(); + + nsresult + DatabaseWork(); + + nsresult + VerifyDatabaseInformation(mozIStorageConnection* aConnection); + void SendReadyMessage(); @@ -340,12 +846,21 @@ private: void Cleanup(); + NS_DECL_ISUPPORTS_INHERITED + NS_IMETHOD Run() override; // IPDL overrides. mozilla::ipc::IPCResult RecvFinish() override; + + // OpenDirectoryListener overrides. + void + DirectoryLockAcquired(DirectoryLock* aLock) override; + + void + DirectoryLockFailed() override; }; /******************************************************************************* @@ -486,12 +1001,12 @@ AllocPBackgroundLSDatabaseParent(const uint64_t& aDatastoreId) } // If we ever decide to return null from this point on, we need to make sure - // that the prepared datastore is removed from the gPreparedDatastores - // hashtable. + // that the datastore is closed and the prepared datastore is removed from the + // gPreparedDatastores hashtable. // We also assume that IPDL must call RecvPBackgroundLSDatabaseConstructor // once we return a valid actor in this method. - RefPtr database = new Database(); + RefPtr database = new Database(preparedDatastore->Origin()); // Transfer ownership to IPDL. return database.forget().take(); @@ -519,6 +1034,13 @@ RecvPBackgroundLSDatabaseConstructor(PBackgroundLSDatabaseParent* aActor, database->SetActorAlive(preparedDatastore->ForgetDatastore()); + // It's possible that AbortOperations was called before the database actor + // was created and became live. Let the child know that the database in no + // longer valid. + if (preparedDatastore->IsInvalidated()) { + database->RequestAllowToClose(); + } + return true; } @@ -640,8 +1162,11 @@ CreateQuotaClient() * Datastore ******************************************************************************/ -Datastore::Datastore(const nsACString& aOrigin) - : mOrigin(aOrigin) +Datastore::Datastore(const nsACString& aOrigin, + already_AddRefed&& aDirectoryLock) + : mDirectoryLock(std::move(aDirectoryLock)) + , mOrigin(aOrigin) + , mClosed(false) { AssertIsOnBackgroundThread(); } @@ -649,6 +1174,20 @@ Datastore::Datastore(const nsACString& aOrigin) Datastore::~Datastore() { AssertIsOnBackgroundThread(); + MOZ_ASSERT(mClosed); +} + +void +Datastore::Close() +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!mClosed); + MOZ_ASSERT(!mDatabases.Count()); + MOZ_ASSERT(mDirectoryLock); + + mClosed = true; + + mDirectoryLock = nullptr; MOZ_ASSERT(gDatastores); MOZ_ASSERT(gDatastores->Get(mOrigin)); @@ -659,6 +1198,42 @@ Datastore::~Datastore() } } +void +Datastore::NoteLiveDatabase(Database* aDatabase) +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aDatabase); + MOZ_ASSERT(!mDatabases.GetEntry(aDatabase)); + MOZ_ASSERT(mDirectoryLock); + MOZ_ASSERT(!mClosed); + + mDatabases.PutEntry(aDatabase); +} + +void +Datastore::NoteFinishedDatabase(Database* aDatabase) +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aDatabase); + MOZ_ASSERT(mDatabases.GetEntry(aDatabase)); + MOZ_ASSERT(mDirectoryLock); + MOZ_ASSERT(!mClosed); + + mDatabases.RemoveEntry(aDatabase); + + if (!mDatabases.Count()) { + Close(); + } +} + +bool +Datastore::HasLiveDatabases() const +{ + AssertIsOnBackgroundThread(); + + return mDatabases.Count(); +} + uint32_t Datastore::GetLength() const { @@ -730,8 +1305,9 @@ Datastore::GetKeys(nsTArray& aKeys) const * Database ******************************************************************************/ -Database::Database() - : mAllowedToClose(false) +Database::Database(const nsACString& aOrigin) + : mOrigin(aOrigin) + , mAllowedToClose(false) , mActorDestroyed(false) , mRequestedAllowToClose(false) #ifdef DEBUG @@ -760,6 +1336,8 @@ Database::SetActorAlive(already_AddRefed&& aDatastore) mDatastore = std::move(aDatastore); + mDatastore->NoteLiveDatabase(this); + if (!gLiveDatabases) { gLiveDatabases = new LiveDatabaseArray(); } @@ -796,6 +1374,8 @@ Database::AllowToClose() mAllowedToClose = true; + mDatastore->NoteFinishedDatabase(this); + mDatastore = nullptr; MOZ_ASSERT(gLiveDatabases); @@ -995,6 +1575,8 @@ LSRequestBase::RecvCancel() PrepareDatastoreOp::PrepareDatastoreOp(const LSRequestParams& aParams) : mParams(aParams.get_LSRequestPrepareDatastoreParams()) , mState(State::Initial) + , mRequestedDirectoryLock(false) + , mInvalidated(false) { MOZ_ASSERT(aParams.type() == LSRequestParams::TLSRequestPrepareDatastoreParams); @@ -1002,6 +1584,7 @@ PrepareDatastoreOp::PrepareDatastoreOp(const LSRequestParams& aParams) PrepareDatastoreOp::~PrepareDatastoreOp() { + MOZ_ASSERT(!mDirectoryLock); MOZ_ASSERT_IF(MayProceedOnNonOwningThread(), mState == State::Initial || mState == State::Completed); } @@ -1056,13 +1639,19 @@ PrepareDatastoreOp::OpenOnMainThread() rv = QuotaManager::GetInfoFromPrincipal(principal, &mSuffix, &mGroup, - &mOrigin); + &mMainThreadOrigin); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } - mState = State::SendingReadyMessage; + // This service has to be started on the main thread currently. + nsCOMPtr ss; + if (NS_WARN_IF(!(ss = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID)))) { + return NS_ERROR_FAILURE; + } + + mState = State::FinishOpen; MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL)); return NS_OK; @@ -1089,12 +1678,287 @@ PrepareDatastoreOp::OpenOnOwningThread() mGroup = quotaInfo.group(); mOrigin = quotaInfo.origin(); - mState = State::SendingReadyMessage; + mState = State::FinishOpen; MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL)); return NS_OK; } +nsresult +PrepareDatastoreOp::FinishOpen() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mState == State::FinishOpen); + MOZ_ASSERT(gPrepareDatastoreOps); + + if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || + !MayProceed()) { + return NS_ERROR_FAILURE; + } + + // Normally it's safe to access member variables without a mutex because even + // though we hop between threads, the variables are never accessed by multiple + // threads at the same time. + // However, the methods OriginIsKnown and Origin can be called at any time. + // So we have to make sure the member variable is set on the same thread as + // those methods are called. + if (mParams.info().type() == PrincipalOrQuotaInfo::TPrincipalInfo) { + mOrigin = mMainThreadOrigin; + } + + MOZ_ASSERT(!mOrigin.IsEmpty()); + + mState = State::PreparationPending; + + // See if this PrepareDatastoreOp needs to wait. + bool foundThis = false; + for (uint32_t index = gPrepareDatastoreOps->Length(); index > 0; index--) { + PrepareDatastoreOp* existingOp = (*gPrepareDatastoreOps)[index - 1]; + + if (existingOp == this) { + foundThis = true; + continue; + } + + if (foundThis && existingOp->Origin() == mOrigin) { + // Only one op can be delayed. + MOZ_ASSERT(!existingOp->mDelayedOp); + existingOp->mDelayedOp = this; + + return NS_OK; + } + } + + nsresult rv = BeginDatastorePreparation(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult +PrepareDatastoreOp::BeginDatastorePreparation() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mState == State::PreparationPending); + + if (gDatastores && (mDatastore = gDatastores->Get(mOrigin))) { + mState = State::SendingReadyMessage; + Unused << this->Run(); + + return NS_OK; + } + + if (QuotaManager::Get()) { + nsresult rv = OpenDirectory(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; + } + + mState = State::QuotaManagerPending; + QuotaManager::GetOrCreate(this); + + return NS_OK; +} + +nsresult +PrepareDatastoreOp::QuotaManagerOpen() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mState == State::QuotaManagerPending); + + if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || + !MayProceed()) { + return NS_ERROR_FAILURE; + } + + if (NS_WARN_IF(!QuotaManager::Get())) { + return NS_ERROR_FAILURE; + } + + nsresult rv = OpenDirectory(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult +PrepareDatastoreOp::OpenDirectory() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mState == State::PreparationPending || + mState == State::QuotaManagerPending); + MOZ_ASSERT(!mOrigin.IsEmpty()); + MOZ_ASSERT(!mDirectoryLock); + MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread()); + MOZ_ASSERT(QuotaManager::Get()); + + mState = State::DirectoryOpenPending; + QuotaManager::Get()->OpenDirectory(PERSISTENCE_TYPE_DEFAULT, + mGroup, + mOrigin, + mozilla::dom::quota::Client::LS, + /* aExclusive */ false, + this); + + mRequestedDirectoryLock = true; + + return NS_OK; +} + +nsresult +PrepareDatastoreOp::SendToIOThread() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mState == State::DirectoryOpenPending); + + if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || + !MayProceed()) { + return NS_ERROR_FAILURE; + } + + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + // Must set this before dispatching otherwise we will race with the IO thread. + mState = State::DatabaseWorkOpen; + + nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult +PrepareDatastoreOp::DatabaseWork() +{ + AssertIsOnIOThread(); + MOZ_ASSERT(mState == State::DatabaseWorkOpen); + + if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || + !MayProceedOnNonOwningThread()) { + return NS_ERROR_FAILURE; + } + + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + nsCOMPtr dbDirectory; + nsresult rv = + quotaManager->EnsureOriginIsInitialized(PERSISTENCE_TYPE_DEFAULT, + mSuffix, + mGroup, + mOrigin, + getter_AddRefs(dbDirectory)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = dbDirectory->Append(NS_LITERAL_STRING(LS_DIRECTORY_NAME)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool exists; + rv = dbDirectory->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!exists) { + rv = dbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } +#ifdef DEBUG + else { + bool isDirectory; + MOZ_ASSERT(NS_SUCCEEDED(dbDirectory->IsDirectory(&isDirectory))); + MOZ_ASSERT(isDirectory); + } +#endif + + nsCOMPtr dbFile; + rv = dbDirectory->Clone(getter_AddRefs(dbFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = dbFile->Append(NS_LITERAL_STRING(DATA_FILE_NAME)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr connection; + rv = CreateStorageConnection(dbFile, mOrigin, getter_AddRefs(connection)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = VerifyDatabaseInformation(connection); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Must set mState before dispatching otherwise we will race with the owning + // thread. + mState = State::SendingReadyMessage; + + rv = OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult +PrepareDatastoreOp::VerifyDatabaseInformation(mozIStorageConnection* aConnection) +{ + AssertIsOnIOThread(); + MOZ_ASSERT(aConnection); + + nsCOMPtr stmt; + nsresult rv = aConnection->CreateStatement(NS_LITERAL_CSTRING( + "SELECT origin " + "FROM database" + ), getter_AddRefs(stmt)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool hasResult; + rv = stmt->ExecuteStep(&hasResult); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (NS_WARN_IF(!hasResult)) { + return NS_ERROR_FILE_CORRUPTED; + } + + nsCString origin; + rv = stmt->GetUTF8String(0, origin); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (NS_WARN_IF(!QuotaManager::AreOriginsEqualOnDisk(mOrigin, origin))) { + return NS_ERROR_FILE_CORRUPTED; + } + + return NS_OK; +} + void PrepareDatastoreOp::SendReadyMessage() { @@ -1126,31 +1990,32 @@ PrepareDatastoreOp::SendResults() LSRequestResponse response; if (NS_SUCCEEDED(ResultCode())) { - RefPtr datastore; - - if (gDatastores) { - datastore = gDatastores->Get(mOrigin); - } - - if (!datastore) { - datastore = new Datastore(mOrigin); + if (!mDatastore) { + mDatastore = new Datastore(mOrigin, mDirectoryLock.forget()); if (!gDatastores) { gDatastores = new DatastoreHashtable(); } - gDatastores->Put(mOrigin, datastore); + MOZ_ASSERT(!gDatastores->Get(mOrigin)); + gDatastores->Put(mOrigin, mDatastore); } uint64_t datastoreId = ++gLastDatastoreId; nsAutoPtr preparedDatastore( - new PreparedDatastore(datastore)); + new PreparedDatastore(mDatastore, mOrigin)); if (!gPreparedDatastores) { gPreparedDatastores = new PreparedDatastoreHashtable(); } - gPreparedDatastores->Put(datastoreId, preparedDatastore.forget()); + gPreparedDatastores->Put(datastoreId, preparedDatastore); + + if (mInvalidated) { + preparedDatastore->Invalidate(); + } + + preparedDatastore.forget(); LSRequestPrepareDatastoreResponse prepareDatastoreResponse; prepareDatastoreResponse.datastoreId() = datastoreId; @@ -1174,6 +2039,28 @@ PrepareDatastoreOp::Cleanup() { AssertIsOnOwningThread(); + if (mDatastore) { + MOZ_ASSERT(!mDirectoryLock); + + if (NS_FAILED(ResultCode())) { + MOZ_ASSERT(!mDatastore->IsClosed()); + MOZ_ASSERT(!mDatastore->HasLiveDatabases()); + mDatastore->Close(); + } + + // Make sure to release the datastore on this thread. + mDatastore = nullptr; + } else if (mDirectoryLock) { + // If we have a directory lock then the operation must have failed. + MOZ_ASSERT(NS_FAILED(ResultCode())); + + mDirectoryLock = nullptr; + } + + if (mDelayedOp) { + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(mDelayedOp.forget())); + } + MOZ_ASSERT(gPrepareDatastoreOps); gPrepareDatastoreOps->RemoveElement(this); @@ -1182,6 +2069,8 @@ PrepareDatastoreOp::Cleanup() } } +NS_IMPL_ISUPPORTS_INHERITED0(PrepareDatastoreOp, LSRequestBase) + NS_IMETHODIMP PrepareDatastoreOp::Run() { @@ -1196,6 +2085,22 @@ PrepareDatastoreOp::Run() rv = OpenOnOwningThread(); break; + case State::FinishOpen: + rv = FinishOpen(); + break; + + case State::PreparationPending: + rv = BeginDatastorePreparation(); + break; + + case State::QuotaManagerPending: + rv = QuotaManagerOpen(); + break; + + case State::DatabaseWorkOpen: + rv = DatabaseWork(); + break; + case State::SendingReadyMessage: SendReadyMessage(); return NS_OK; @@ -1238,6 +2143,45 @@ PrepareDatastoreOp::RecvFinish() return IPC_OK(); } +void +PrepareDatastoreOp::DirectoryLockAcquired(DirectoryLock* aLock) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mState == State::DirectoryOpenPending); + MOZ_ASSERT(!mDirectoryLock); + + mDirectoryLock = aLock; + + nsresult rv = SendToIOThread(); + if (NS_WARN_IF(NS_FAILED(rv))) { + MaybeSetFailureCode(rv); + + // The caller holds a strong reference to us, no need for a self reference + // before calling Run(). + + mState = State::SendingReadyMessage; + MOZ_ALWAYS_SUCCEEDS(Run()); + + return; + } +} + +void +PrepareDatastoreOp::DirectoryLockFailed() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mState == State::DirectoryOpenPending); + MOZ_ASSERT(!mDirectoryLock); + + MaybeSetFailureCode(NS_ERROR_FAILURE); + + // The caller holds a strong reference to us, no need for a self reference + // before calling Run(). + + mState = State::SendingReadyMessage; + MOZ_ALWAYS_SUCCEEDS(Run()); +} + /******************************************************************************* * QuotaClient ******************************************************************************/ @@ -1295,8 +2239,122 @@ QuotaClient::GetUsageForOrigin(PersistenceType aPersistenceType, UsageInfo* aUsageInfo) { AssertIsOnIOThread(); + MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_DEFAULT); MOZ_ASSERT(aUsageInfo); + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + nsCOMPtr directory; + nsresult rv = quotaManager->GetDirectoryForOrigin(aPersistenceType, + aOrigin, + getter_AddRefs(directory)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT(directory); + + rv = directory->Append(NS_LITERAL_STRING(LS_DIRECTORY_NAME)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool exists; +#ifdef DEBUG + rv = directory->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT(exists); +#endif + + nsCOMPtr file; + rv = directory->Clone(getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = file->Append(NS_LITERAL_STRING(DATA_FILE_NAME)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = file->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (exists) { + bool isDirectory; + rv = file->IsDirectory(&isDirectory); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (NS_WARN_IF(isDirectory)) { + return NS_ERROR_FAILURE; + } + + // TODO: Use a special file that contains logical size of the database. + // For now, don't add to origin usage. + } + + // Report unknown files, don't fail, just warn. + + nsCOMPtr directoryEntries; + rv = directory->GetDirectoryEntries(getter_AddRefs(directoryEntries)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!directoryEntries) { + return NS_OK; + } + + while (true) { + if (aCanceled) { + break; + } + + nsCOMPtr file; + rv = directoryEntries->GetNextFile(getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!file) { + break; + } + + nsString leafName; + rv = file->GetLeafName(leafName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (leafName.EqualsLiteral(DATA_FILE_NAME)) { + // Don't need to check if it is a directory or file. We did that above. + continue; + } + + if (leafName.EqualsLiteral(JOURNAL_FILE_NAME)) { + bool isDirectory; + rv = file->IsDirectory(&isDirectory); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!isDirectory) { + continue; + } + } + + LS_WARNING("Something (%s) in the directory that doesn't belong!", \ + NS_ConvertUTF16toUTF8(leafName).get()); + } + return NS_OK; } @@ -1317,6 +2375,67 @@ void QuotaClient::AbortOperations(const nsACString& aOrigin) { AssertIsOnBackgroundThread(); + + // A PrepareDatastoreOp object could already acquire a directory lock for + // the given origin. Its last step is creation of a Datastore object (which + // will take ownership of the directory lock) and a PreparedDatastore object + // which keeps the Datastore alive until a database actor is created. + // We need to invalidate the PreparedDatastore object when it's created, + // otherwise the Datastore object can block the origin clear operation for + // long time. It's not a problem that we don't fail the PrepareDatastoreOp + // immediatelly (avoiding the creation of the Datastore and PreparedDatastore + // object). We will call RequestAllowToClose on the database actor once it's + // created and the child actor will respond by sending AllowToClose which + // will close the Datastore on the parent side (the closing releases the + // directory lock). + + if (gPrepareDatastoreOps) { + for (PrepareDatastoreOp* prepareDatastoreOp : *gPrepareDatastoreOps) { + MOZ_ASSERT(prepareDatastoreOp); + + // Explicitely check if a directory lock has been requested. + // Origin clearing can't be blocked by this PrepareDatastoreOp if it + // hasn't requested a directory lock yet, so we can just ignore it. + // This will also guarantee that PrepareDatastoreOp has a known origin. + // And it also ensures that the ordering is right. Without the check we + // could invalidate ops whose directory locks were requested after we + // requested a directory lock for origin clearing. + if (!prepareDatastoreOp->RequestedDirectoryLock()) { + continue; + } + + MOZ_ASSERT(prepareDatastoreOp->OriginIsKnown()); + + if (aOrigin.IsVoid() || prepareDatastoreOp->Origin() == aOrigin) { + prepareDatastoreOp->Invalidate(); + } + } + } + + if (gPreparedDatastores) { + for (auto iter = gPreparedDatastores->ConstIter(); + !iter.Done(); + iter.Next()) { + PreparedDatastore* preparedDatastore = iter.Data(); + MOZ_ASSERT(preparedDatastore); + + if (aOrigin.IsVoid() || preparedDatastore->Origin() == aOrigin) { + preparedDatastore->Invalidate(); + } + } + } + + if (gLiveDatabases) { + for (Database* database : *gLiveDatabases) { + if (aOrigin.IsVoid() || database->Origin() == aOrigin) { + // TODO: This just allows the database to close, but we can actually + // set a flag to abort any existing operations, so we can + // eventually close faster. + + database->RequestAllowToClose(); + } + } + } } void @@ -1353,6 +2472,20 @@ QuotaClient::ShutdownWorkThreads() // When the last PrepareDatastoreOp finishes, the gPrepareDatastoreOps array // is destroyed. + // There may be datastores that are only held alive by prepared datastores + // (ones which have no live database actors). We need to explicitly close + // them here. + if (gDatastores) { + for (auto iter = gDatastores->ConstIter(); !iter.Done(); iter.Next()) { + Datastore* datastore = iter.Data(); + MOZ_ASSERT(datastore); + + if (!datastore->IsClosed() && !datastore->HasLiveDatabases()) { + datastore->Close(); + } + } + } + // If database actors haven't been created yet, don't do anything special. // We are shutting down and we can release prepared datastores immediatelly // since database actors will never be created for them. diff --git a/dom/localstorage/LSObject.cpp b/dom/localstorage/LSObject.cpp index d623ead8ae9b..4c441b43d964 100644 --- a/dom/localstorage/LSObject.cpp +++ b/dom/localstorage/LSObject.cpp @@ -167,6 +167,12 @@ LSObject::Create(nsPIDOMWindowInner* aWindow, quotaInfo.origin() = origin; *info = quotaInfo; + + // This service has to be started on the main thread currently. + nsCOMPtr ss; + if (NS_WARN_IF(!(ss = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID)))) { + return NS_ERROR_FAILURE; + } } else { PrincipalInfo principalInfo; rv = PrincipalToPrincipalInfo(principal, &principalInfo); diff --git a/dom/localstorage/ReportInternalError.cpp b/dom/localstorage/ReportInternalError.cpp new file mode 100644 index 000000000000..a15209687f9d --- /dev/null +++ b/dom/localstorage/ReportInternalError.cpp @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ReportInternalError.h" + +#include "mozilla/IntegerPrintfMacros.h" + +#include "nsContentUtils.h" +#include "nsPrintfCString.h" + +namespace mozilla { +namespace dom { +namespace localstorage { + +void +ReportInternalError(const char* aFile, uint32_t aLine, const char* aStr) +{ + // Get leaf of file path + for (const char* p = aFile; *p; ++p) { + if (*p == '/' && *(p + 1)) { + aFile = p + 1; + } + } + + nsContentUtils::LogSimpleConsoleError( + NS_ConvertUTF8toUTF16(nsPrintfCString( + "LocalStorage %s: %s:%" PRIu32, aStr, aFile, aLine)), + "localstorage", false); +} + +} // namespace localstorage +} // namespace dom +} // namespace mozilla diff --git a/dom/localstorage/ReportInternalError.h b/dom/localstorage/ReportInternalError.h new file mode 100644 index 000000000000..1988a880d5f8 --- /dev/null +++ b/dom/localstorage/ReportInternalError.h @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_localstorage_ReportInternalError_h +#define mozilla_dom_localstorage_ReportInternalError_h + +#include "nsDebug.h" + +#define LS_WARNING(...) \ + do { \ + nsPrintfCString s(__VA_ARGS__); \ + mozilla::dom::localstorage::ReportInternalError(__FILE__, \ + __LINE__, \ + s.get()); \ + NS_WARNING(s.get()); \ + } while (0) + +namespace mozilla { +namespace dom { +namespace localstorage { + +void +ReportInternalError(const char* aFile, uint32_t aLine, const char* aStr); + +} // namespace localstorage +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_localstorage_ReportInternalError_h diff --git a/dom/localstorage/moz.build b/dom/localstorage/moz.build index ebe0c87b7aab..64cd2c951b4e 100644 --- a/dom/localstorage/moz.build +++ b/dom/localstorage/moz.build @@ -21,6 +21,7 @@ UNIFIED_SOURCES += [ 'LocalStorageManager2.cpp', 'LSDatabase.cpp', 'LSObject.cpp', + 'ReportInternalError.cpp', ] IPDL_SOURCES += [ diff --git a/dom/quota/ActorsParent.cpp b/dom/quota/ActorsParent.cpp index 280aeb0b84a5..68d4ee926e85 100644 --- a/dom/quota/ActorsParent.cpp +++ b/dom/quota/ActorsParent.cpp @@ -4799,6 +4799,10 @@ QuotaManager::MaybeRemoveLocalStorageData() { AssertIsOnIOThread(); + if (CachedNextGenLocalStorageEnabled()) { + return NS_OK; + } + // Cleanup the tmp file first, if there's any. nsCOMPtr lsArchiveTmpFile; nsresult rv = NS_NewLocalFile(mStoragePath, diff --git a/dom/quota/test/unit/test_removeLocalStorage.js b/dom/quota/test/unit/test_removeLocalStorage.js index 9d4a3b80050a..162c10145412 100644 --- a/dom/quota/test/unit/test_removeLocalStorage.js +++ b/dom/quota/test/unit/test_removeLocalStorage.js @@ -11,6 +11,10 @@ function* testSteps() const lsArchiveTmpFile = "storage/ls-archive-tmp.sqlite"; const lsDir = "storage/default/http+++localhost/ls"; + info("Setting pref"); + + SpecialPowers.setBoolPref("dom.storage.next_gen", false); + // Profile 1 info("Clearing"); diff --git a/toolkit/components/extensions/test/mochitest/mochitest.ini b/toolkit/components/extensions/test/mochitest/mochitest.ini index 9fca36408650..12b838652671 100644 --- a/toolkit/components/extensions/test/mochitest/mochitest.ini +++ b/toolkit/components/extensions/test/mochitest/mochitest.ini @@ -1,7 +1,7 @@ [DEFAULT] tags = webextensions in-process-webextensions -#[test_ext_storage_cleanup.html] +[test_ext_storage_cleanup.html] # Bug 1426514 storage_cleanup: clearing localStorage fails with oop [include:mochitest-common.ini] diff --git a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini index 8b013bb42947..c490ddf304f1 100644 --- a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini +++ b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini @@ -60,7 +60,7 @@ skip-if = os == "android" # checking for telemetry needs to be updated: 1384923 [test_ext_extension_startup_telemetry.js] [test_ext_geturl.js] [test_ext_idle.js] -[test_ext_localStorage.js] +#[test_ext_localStorage.js] [test_ext_management.js] skip-if = (os == "win" && !debug) #Bug 1419183 disable on Windows [test_ext_management_uninstall_self.js]