summaryrefslogtreecommitdiffstats
path: root/dom/indexedDB/IDBDatabase.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /dom/indexedDB/IDBDatabase.cpp
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/indexedDB/IDBDatabase.cpp')
-rw-r--r--dom/indexedDB/IDBDatabase.cpp1192
1 files changed, 1192 insertions, 0 deletions
diff --git a/dom/indexedDB/IDBDatabase.cpp b/dom/indexedDB/IDBDatabase.cpp
new file mode 100644
index 0000000000..0dd0cf74f4
--- /dev/null
+++ b/dom/indexedDB/IDBDatabase.cpp
@@ -0,0 +1,1192 @@
+/* -*- 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 "IDBDatabase.h"
+
+#include "IDBEvents.h"
+#include "IDBFactory.h"
+#include "IDBIndex.h"
+#include "IDBMutableFile.h"
+#include "IDBObjectStore.h"
+#include "IDBRequest.h"
+#include "IDBTransaction.h"
+#include "IndexedDatabaseInlines.h"
+#include "IndexedDatabaseManager.h"
+#include "IndexedDBCommon.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/EventDispatcher.h"
+#include "MainThreadUtils.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/Services.h"
+#include "mozilla/storage.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/DOMStringListBinding.h"
+#include "mozilla/dom/Exceptions.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/IDBDatabaseBinding.h"
+#include "mozilla/dom/IDBObjectStoreBinding.h"
+#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseFileChild.h"
+#include "mozilla/dom/indexedDB/PBackgroundIDBSharedTypes.h"
+#include "mozilla/dom/IPCBlobUtils.h"
+#include "mozilla/dom/quota/QuotaManager.h"
+#include "mozilla/dom/quota/ResultExtensions.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/FileDescriptor.h"
+#include "mozilla/ipc/InputStreamParams.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "nsCOMPtr.h"
+#include "mozilla/dom/Document.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIScriptError.h"
+#include "nsISupportsPrimitives.h"
+#include "nsThreadUtils.h"
+#include "nsIWeakReferenceUtils.h"
+#include "ProfilerHelpers.h"
+#include "ReportInternalError.h"
+#include "ScriptErrorHelper.h"
+#include "nsQueryObject.h"
+
+// Include this last to avoid path problems on Windows.
+#include "ActorsChild.h"
+
+namespace mozilla::dom {
+
+using namespace mozilla::dom::indexedDB;
+using namespace mozilla::dom::quota;
+using namespace mozilla::ipc;
+using namespace mozilla::services;
+
+namespace {
+
+const char kCycleCollectionObserverTopic[] = "cycle-collector-end";
+const char kMemoryPressureObserverTopic[] = "memory-pressure";
+const char kWindowObserverTopic[] = "inner-window-destroyed";
+
+class CancelableRunnableWrapper final : public CancelableRunnable {
+ nsCOMPtr<nsIRunnable> mRunnable;
+
+ public:
+ explicit CancelableRunnableWrapper(nsCOMPtr<nsIRunnable> aRunnable)
+ : CancelableRunnable("dom::CancelableRunnableWrapper"),
+ mRunnable(std::move(aRunnable)) {
+ MOZ_ASSERT(mRunnable);
+ }
+
+ private:
+ ~CancelableRunnableWrapper() = default;
+
+ NS_DECL_NSIRUNNABLE
+ nsresult Cancel() override;
+};
+
+class DatabaseFile final : public PBackgroundIDBDatabaseFileChild {
+ IDBDatabase* mDatabase;
+
+ public:
+ explicit DatabaseFile(IDBDatabase* aDatabase) : mDatabase(aDatabase) {
+ MOZ_ASSERT(aDatabase);
+ aDatabase->AssertIsOnOwningThread();
+
+ MOZ_COUNT_CTOR(DatabaseFile);
+ }
+
+ private:
+ ~DatabaseFile() {
+ MOZ_ASSERT(!mDatabase);
+
+ MOZ_COUNT_DTOR(DatabaseFile);
+ }
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override {
+ MOZ_ASSERT(mDatabase);
+ mDatabase->AssertIsOnOwningThread();
+
+ if (aWhy != Deletion) {
+ RefPtr<IDBDatabase> database = mDatabase;
+ database->NoteFinishedFileActor(this);
+ }
+
+#ifdef DEBUG
+ mDatabase = nullptr;
+#endif
+ }
+};
+
+} // namespace
+
+class IDBDatabase::Observer final : public nsIObserver {
+ IDBDatabase* mWeakDatabase;
+ const uint64_t mWindowId;
+
+ public:
+ Observer(IDBDatabase* aDatabase, uint64_t aWindowId)
+ : mWeakDatabase(aDatabase), mWindowId(aWindowId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aDatabase);
+ }
+
+ void Revoke() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mWeakDatabase = nullptr;
+ }
+
+ NS_DECL_ISUPPORTS
+
+ private:
+ ~Observer() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mWeakDatabase);
+ }
+
+ NS_DECL_NSIOBSERVER
+};
+
+IDBDatabase::IDBDatabase(IDBOpenDBRequest* aRequest,
+ SafeRefPtr<IDBFactory> aFactory,
+ BackgroundDatabaseChild* aActor,
+ UniquePtr<DatabaseSpec> aSpec)
+ : DOMEventTargetHelper(aRequest),
+ mFactory(std::move(aFactory)),
+ mSpec(std::move(aSpec)),
+ mBackgroundActor(aActor),
+ mFileHandleDisabled(aRequest->IsFileHandleDisabled()),
+ mClosed(false),
+ mInvalidated(false),
+ mQuotaExceeded(false),
+ mIncreasedActiveDatabaseCount(false) {
+ MOZ_ASSERT(aRequest);
+ MOZ_ASSERT(mFactory);
+ mFactory->AssertIsOnOwningThread();
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(mSpec);
+}
+
+IDBDatabase::~IDBDatabase() {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(!mBackgroundActor);
+ MOZ_ASSERT(!mIncreasedActiveDatabaseCount);
+}
+
+// static
+RefPtr<IDBDatabase> IDBDatabase::Create(IDBOpenDBRequest* aRequest,
+ SafeRefPtr<IDBFactory> aFactory,
+ BackgroundDatabaseChild* aActor,
+ UniquePtr<DatabaseSpec> aSpec) {
+ MOZ_ASSERT(aRequest);
+ MOZ_ASSERT(aFactory);
+ aFactory->AssertIsOnOwningThread();
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(aSpec);
+
+ RefPtr<IDBDatabase> db =
+ new IDBDatabase(aRequest, aFactory.clonePtr(), aActor, std::move(aSpec));
+
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aFactory->GetParentObject());
+ if (window) {
+ uint64_t windowId = window->WindowID();
+
+ RefPtr<Observer> observer = new Observer(db, windowId);
+
+ nsCOMPtr<nsIObserverService> obsSvc = GetObserverService();
+ MOZ_ASSERT(obsSvc);
+
+ // This topic must be successfully registered.
+ MOZ_ALWAYS_SUCCEEDS(
+ obsSvc->AddObserver(observer, kWindowObserverTopic, false));
+
+ // These topics are not crucial.
+ QM_WARNONLY_TRY(QM_TO_RESULT(
+ obsSvc->AddObserver(observer, kCycleCollectionObserverTopic, false)));
+ QM_WARNONLY_TRY(QM_TO_RESULT(
+ obsSvc->AddObserver(observer, kMemoryPressureObserverTopic, false)));
+
+ db->mObserver = std::move(observer);
+ }
+ }
+
+ db->IncreaseActiveDatabaseCount();
+
+ return db;
+}
+
+#ifdef DEBUG
+
+void IDBDatabase::AssertIsOnOwningThread() const {
+ MOZ_ASSERT(mFactory);
+ mFactory->AssertIsOnOwningThread();
+}
+
+#endif // DEBUG
+
+nsIEventTarget* IDBDatabase::EventTarget() const {
+ AssertIsOnOwningThread();
+ return mFactory->EventTarget();
+}
+
+void IDBDatabase::CloseInternal() {
+ AssertIsOnOwningThread();
+
+ if (!mClosed) {
+ mClosed = true;
+
+ ExpireFileActors(/* aExpireAll */ true);
+
+ if (mObserver) {
+ mObserver->Revoke();
+
+ nsCOMPtr<nsIObserverService> obsSvc = GetObserverService();
+ if (obsSvc) {
+ // These might not have been registered.
+ obsSvc->RemoveObserver(mObserver, kCycleCollectionObserverTopic);
+ obsSvc->RemoveObserver(mObserver, kMemoryPressureObserverTopic);
+
+ MOZ_ALWAYS_SUCCEEDS(
+ obsSvc->RemoveObserver(mObserver, kWindowObserverTopic));
+ }
+
+ mObserver = nullptr;
+ }
+
+ if (mBackgroundActor && !mInvalidated) {
+ mBackgroundActor->SendClose();
+ }
+
+ // Decrease the number of active databases right after the database is
+ // closed.
+ MaybeDecreaseActiveDatabaseCount();
+ }
+}
+
+void IDBDatabase::InvalidateInternal() {
+ AssertIsOnOwningThread();
+
+ InvalidateMutableFiles();
+ AbortTransactions(/* aShouldWarn */ true);
+
+ CloseInternal();
+}
+
+void IDBDatabase::EnterSetVersionTransaction(uint64_t aNewVersion) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aNewVersion);
+ MOZ_ASSERT(!RunningVersionChangeTransaction());
+ MOZ_ASSERT(mSpec);
+ MOZ_ASSERT(!mPreviousSpec);
+
+ mPreviousSpec = MakeUnique<DatabaseSpec>(*mSpec);
+
+ mSpec->metadata().version() = aNewVersion;
+}
+
+void IDBDatabase::ExitSetVersionTransaction() {
+ AssertIsOnOwningThread();
+
+ if (mPreviousSpec) {
+ mPreviousSpec = nullptr;
+ }
+}
+
+void IDBDatabase::RevertToPreviousState() {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(RunningVersionChangeTransaction());
+ MOZ_ASSERT(mPreviousSpec);
+
+ // Hold the current spec alive until RefreshTransactionsSpecEnumerator has
+ // finished!
+ auto currentSpec = std::move(mSpec);
+
+ mSpec = std::move(mPreviousSpec);
+
+ RefreshSpec(/* aMayDelete */ true);
+}
+
+void IDBDatabase::RefreshSpec(bool aMayDelete) {
+ AssertIsOnOwningThread();
+
+ for (auto* weakTransaction : mTransactions) {
+ const auto transaction =
+ SafeRefPtr{weakTransaction, AcquireStrongRefFromRawPtr{}};
+ MOZ_ASSERT(transaction);
+ transaction->AssertIsOnOwningThread();
+ transaction->RefreshSpec(aMayDelete);
+ }
+}
+
+const nsString& IDBDatabase::Name() const {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mSpec);
+
+ return mSpec->metadata().name();
+}
+
+uint64_t IDBDatabase::Version() const {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mSpec);
+
+ return mSpec->metadata().version();
+}
+
+RefPtr<DOMStringList> IDBDatabase::ObjectStoreNames() const {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mSpec);
+
+ return CreateSortedDOMStringList(
+ mSpec->objectStores(),
+ [](const auto& objectStore) { return objectStore.metadata().name(); });
+}
+
+RefPtr<Document> IDBDatabase::GetOwnerDocument() const {
+ if (nsPIDOMWindowInner* window = GetOwner()) {
+ return window->GetExtantDoc();
+ }
+ return nullptr;
+}
+
+RefPtr<IDBObjectStore> IDBDatabase::CreateObjectStore(
+ const nsAString& aName, const IDBObjectStoreParameters& aOptionalParameters,
+ ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+
+ const auto transaction = IDBTransaction::MaybeCurrent();
+ if (!transaction || transaction->Database() != this ||
+ transaction->GetMode() != IDBTransaction::Mode::VersionChange) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+ return nullptr;
+ }
+
+ if (!transaction->IsActive()) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
+ return nullptr;
+ }
+
+ QM_INFOONLY_TRY_UNWRAP(const auto maybeKeyPath,
+ KeyPath::Parse(aOptionalParameters.mKeyPath));
+ if (!maybeKeyPath) {
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return nullptr;
+ }
+
+ const auto& keyPath = maybeKeyPath.ref();
+
+ auto& objectStores = mSpec->objectStores();
+ const auto end = objectStores.cend();
+ const auto foundIt = std::find_if(
+ objectStores.cbegin(), end, [&aName](const auto& objectStore) {
+ return aName == objectStore.metadata().name();
+ });
+ if (foundIt != end) {
+ aRv.ThrowConstraintError(nsPrintfCString(
+ "Object store named '%s' already exists at index '%zu'",
+ NS_ConvertUTF16toUTF8(aName).get(), foundIt.GetIndex()));
+ return nullptr;
+ }
+
+ if (!keyPath.IsAllowedForObjectStore(aOptionalParameters.mAutoIncrement)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ return nullptr;
+ }
+
+ const ObjectStoreSpec* oldSpecElements =
+ objectStores.IsEmpty() ? nullptr : objectStores.Elements();
+
+ ObjectStoreSpec* newSpec = objectStores.AppendElement();
+ newSpec->metadata() =
+ ObjectStoreMetadata(transaction->NextObjectStoreId(), nsString(aName),
+ keyPath, aOptionalParameters.mAutoIncrement);
+
+ if (oldSpecElements && oldSpecElements != objectStores.Elements()) {
+ MOZ_ASSERT(objectStores.Length() > 1);
+
+ // Array got moved, update the spec pointers for all live objectStores and
+ // indexes.
+ RefreshSpec(/* aMayDelete */ false);
+ }
+
+ auto objectStore = transaction->CreateObjectStore(*newSpec);
+ MOZ_ASSERT(objectStore);
+
+ // Don't do this in the macro because we always need to increment the serial
+ // number to keep in sync with the parent.
+ const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber();
+
+ IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
+ "database(%s).transaction(%s).createObjectStore(%s)",
+ "IDBDatabase.createObjectStore(%.0s%.0s%.0s)",
+ transaction->LoggingSerialNumber(), requestSerialNumber,
+ IDB_LOG_STRINGIFY(this), IDB_LOG_STRINGIFY(*transaction),
+ IDB_LOG_STRINGIFY(objectStore));
+
+ return objectStore;
+}
+
+void IDBDatabase::DeleteObjectStore(const nsAString& aName, ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+
+ const auto transaction = IDBTransaction::MaybeCurrent();
+ if (!transaction || transaction->Database() != this ||
+ transaction->GetMode() != IDBTransaction::Mode::VersionChange) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+ return;
+ }
+
+ if (!transaction->IsActive()) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
+ return;
+ }
+
+ auto& specArray = mSpec->objectStores();
+ const auto end = specArray.end();
+ const auto foundIt =
+ std::find_if(specArray.begin(), end, [&aName](const auto& objectStore) {
+ const ObjectStoreMetadata& metadata = objectStore.metadata();
+ MOZ_ASSERT(metadata.id());
+
+ return aName == metadata.name();
+ });
+
+ if (foundIt == end) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR);
+ return;
+ }
+
+ // Must do this before altering the metadata array!
+ transaction->DeleteObjectStore(foundIt->metadata().id());
+
+ specArray.RemoveElementAt(foundIt);
+ RefreshSpec(/* aMayDelete */ false);
+
+ // Don't do this in the macro because we always need to increment the serial
+ // number to keep in sync with the parent.
+ const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber();
+
+ IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
+ "database(%s).transaction(%s).deleteObjectStore(\"%s\")",
+ "IDBDatabase.deleteObjectStore(%.0s%.0s%.0s)",
+ transaction->LoggingSerialNumber(), requestSerialNumber,
+ IDB_LOG_STRINGIFY(this), IDB_LOG_STRINGIFY(*transaction),
+ NS_ConvertUTF16toUTF8(aName).get());
+}
+
+RefPtr<IDBTransaction> IDBDatabase::Transaction(
+ JSContext* aCx, const StringOrStringSequence& aStoreNames,
+ IDBTransactionMode aMode, ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+
+ if ((aMode == IDBTransactionMode::Readwriteflush ||
+ aMode == IDBTransactionMode::Cleanup) &&
+ !StaticPrefs::dom_indexedDB_experimental()) {
+ // Pretend that this mode doesn't exist. We don't have a way to annotate
+ // certain enum values as depending on preferences so we just duplicate the
+ // normal exception generation here.
+ aRv.ThrowTypeError<MSG_INVALID_ENUM_VALUE>("argument 2", "readwriteflush",
+ "IDBTransactionMode");
+ return nullptr;
+ }
+
+ if (QuotaManager::IsShuttingDown()) {
+ IDB_REPORT_INTERNAL_ERR();
+ aRv.ThrowUnknownError("Can't start IndexedDB transaction during shutdown");
+ return nullptr;
+ }
+
+ // https://w3c.github.io/IndexedDB/#dom-idbdatabase-transaction
+ // Step 1.
+ if (RunningVersionChangeTransaction()) {
+ aRv.ThrowInvalidStateError(
+ "Can't start a transaction while running an upgrade transaction");
+ return nullptr;
+ }
+
+ // Step 2.
+ if (mClosed) {
+ aRv.ThrowInvalidStateError(
+ "Can't start a transaction on a closed database");
+ return nullptr;
+ }
+
+ // Step 3.
+ AutoTArray<nsString, 1> stackSequence;
+
+ if (aStoreNames.IsString()) {
+ stackSequence.AppendElement(aStoreNames.GetAsString());
+ } else {
+ MOZ_ASSERT(aStoreNames.IsStringSequence());
+ // Step 5, but it can be done before step 4 because those steps
+ // can't both throw.
+ if (aStoreNames.GetAsStringSequence().IsEmpty()) {
+ aRv.ThrowInvalidAccessError("Empty scope passed in");
+ return nullptr;
+ }
+ }
+
+ // Step 4.
+ const nsTArray<nsString>& storeNames =
+ aStoreNames.IsString() ? stackSequence
+ : static_cast<const nsTArray<nsString>&>(
+ aStoreNames.GetAsStringSequence());
+ MOZ_ASSERT(!storeNames.IsEmpty());
+
+ const nsTArray<ObjectStoreSpec>& objectStores = mSpec->objectStores();
+ const uint32_t nameCount = storeNames.Length();
+
+ nsTArray<nsString> sortedStoreNames;
+ sortedStoreNames.SetCapacity(nameCount);
+
+ // While collecting object store names, check if the corresponding object
+ // stores actually exist.
+ const auto begin = objectStores.cbegin();
+ const auto end = objectStores.cend();
+ for (const auto& name : storeNames) {
+ const auto foundIt =
+ std::find_if(begin, end, [&name](const auto& objectStore) {
+ return objectStore.metadata().name() == name;
+ });
+ if (foundIt == end) {
+ Telemetry::ScalarAdd(
+ storeNames.IsEmpty()
+ ? Telemetry::ScalarID::
+ IDB_FAILURE_UNKNOWN_OBJECTSTORE_EMPTY_DATABASE
+ : Telemetry::ScalarID::
+ IDB_FAILURE_UNKNOWN_OBJECTSTORE_NON_EMPTY_DATABASE,
+ 1);
+
+ // Not using nsPrintfCString in case "name" has embedded nulls.
+ aRv.ThrowNotFoundError("'"_ns + NS_ConvertUTF16toUTF8(name) +
+ "' is not a known object store name"_ns);
+ return nullptr;
+ }
+
+ sortedStoreNames.EmplaceBack(name);
+ }
+ sortedStoreNames.Sort();
+
+ // Remove any duplicates.
+ sortedStoreNames.SetLength(
+ std::unique(sortedStoreNames.begin(), sortedStoreNames.end()).GetIndex());
+
+ IDBTransaction::Mode mode;
+ switch (aMode) {
+ case IDBTransactionMode::Readonly:
+ mode = IDBTransaction::Mode::ReadOnly;
+ break;
+ case IDBTransactionMode::Readwrite:
+ if (mQuotaExceeded) {
+ mode = IDBTransaction::Mode::Cleanup;
+ mQuotaExceeded = false;
+ } else {
+ mode = IDBTransaction::Mode::ReadWrite;
+ }
+ break;
+ case IDBTransactionMode::Readwriteflush:
+ mode = IDBTransaction::Mode::ReadWriteFlush;
+ break;
+ case IDBTransactionMode::Cleanup:
+ mode = IDBTransaction::Mode::Cleanup;
+ mQuotaExceeded = false;
+ break;
+ case IDBTransactionMode::Versionchange:
+ // Step 6.
+ aRv.ThrowTypeError("Invalid transaction mode");
+ return nullptr;
+
+ default:
+ MOZ_CRASH("Unknown mode!");
+ }
+
+ SafeRefPtr<IDBTransaction> transaction =
+ IDBTransaction::Create(aCx, this, sortedStoreNames, mode);
+ if (NS_WARN_IF(!transaction)) {
+ IDB_REPORT_INTERNAL_ERR();
+ MOZ_ASSERT(!NS_IsMainThread(),
+ "Transaction creation can only fail on workers");
+ aRv.ThrowUnknownError("Failed to create IndexedDB transaction on worker");
+ return nullptr;
+ }
+
+ BackgroundTransactionChild* actor =
+ new BackgroundTransactionChild(transaction.clonePtr());
+
+ IDB_LOG_MARK_CHILD_TRANSACTION(
+ "database(%s).transaction(%s)", "IDBDatabase.transaction(%.0s%.0s)",
+ transaction->LoggingSerialNumber(), IDB_LOG_STRINGIFY(this),
+ IDB_LOG_STRINGIFY(*transaction));
+
+ MOZ_ALWAYS_TRUE(mBackgroundActor->SendPBackgroundIDBTransactionConstructor(
+ actor, sortedStoreNames, mode));
+
+ transaction->SetBackgroundActor(actor);
+
+ if (mode == IDBTransaction::Mode::Cleanup) {
+ ExpireFileActors(/* aExpireAll */ true);
+ }
+
+ return AsRefPtr(std::move(transaction));
+}
+
+RefPtr<IDBRequest> IDBDatabase::CreateMutableFile(
+ JSContext* aCx, const nsAString& aName, const Optional<nsAString>& aType,
+ ErrorResult& aRv) {
+ AssertIsOnOwningThread();
+
+ if (aName.IsEmpty()) {
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return nullptr;
+ }
+
+ if (QuotaManager::IsShuttingDown()) {
+ IDB_REPORT_INTERNAL_ERR();
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+ return nullptr;
+ }
+
+ if (mClosed || mFileHandleDisabled) {
+ aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+ return nullptr;
+ }
+
+ nsString type;
+ if (aType.WasPassed()) {
+ type = aType.Value();
+ }
+
+ CreateFileParams params(nsString(aName), type);
+
+ auto request = IDBRequest::Create(aCx, this, nullptr).unwrap();
+
+ BackgroundDatabaseRequestChild* actor =
+ new BackgroundDatabaseRequestChild(this, request);
+
+ IDB_LOG_MARK_CHILD_REQUEST(
+ "database(%s).createMutableFile(%s)",
+ "IDBDatabase.createMutableFile(%.0s%.0s)", request->LoggingSerialNumber(),
+ IDB_LOG_STRINGIFY(this), NS_ConvertUTF16toUTF8(aName).get());
+
+ mBackgroundActor->SendPBackgroundIDBDatabaseRequestConstructor(actor, params);
+
+ return request;
+}
+
+void IDBDatabase::RegisterTransaction(IDBTransaction& aTransaction) {
+ AssertIsOnOwningThread();
+ aTransaction.AssertIsOnOwningThread();
+ MOZ_ASSERT(!mTransactions.Contains(&aTransaction));
+
+ mTransactions.Insert(&aTransaction);
+}
+
+void IDBDatabase::UnregisterTransaction(IDBTransaction& aTransaction) {
+ AssertIsOnOwningThread();
+ aTransaction.AssertIsOnOwningThread();
+ MOZ_ASSERT(mTransactions.Contains(&aTransaction));
+
+ mTransactions.Remove(&aTransaction);
+}
+
+void IDBDatabase::AbortTransactions(bool aShouldWarn) {
+ AssertIsOnOwningThread();
+
+ constexpr size_t StackExceptionLimit = 20;
+ using StrongTransactionArray =
+ AutoTArray<SafeRefPtr<IDBTransaction>, StackExceptionLimit>;
+ using WeakTransactionArray = AutoTArray<IDBTransaction*, StackExceptionLimit>;
+
+ if (!mTransactions.Count()) {
+ // Return early as an optimization, the remainder is a no-op in this
+ // case.
+ return;
+ }
+
+ // XXX TransformIfIntoNewArray might be generalized to allow specifying the
+ // type of nsTArray to create, so that it can create an AutoTArray as well; an
+ // TransformIf (without AbortOnErr) might be added, which could be used here.
+ StrongTransactionArray transactionsToAbort;
+ transactionsToAbort.SetCapacity(mTransactions.Count());
+
+ for (IDBTransaction* const transaction : mTransactions) {
+ MOZ_ASSERT(transaction);
+
+ transaction->AssertIsOnOwningThread();
+
+ // Transactions that are already done can simply be ignored. Otherwise
+ // there is a race here and it's possible that the transaction has not
+ // been successfully committed yet so we will warn the user.
+ if (!transaction->IsFinished()) {
+ transactionsToAbort.EmplaceBack(transaction,
+ AcquireStrongRefFromRawPtr{});
+ }
+ }
+ MOZ_ASSERT(transactionsToAbort.Length() <= mTransactions.Count());
+
+ if (transactionsToAbort.IsEmpty()) {
+ // Return early as an optimization, the remainder is a no-op in this
+ // case.
+ return;
+ }
+
+ // We want to abort transactions as soon as possible so we iterate the
+ // transactions once and abort them all first, collecting the transactions
+ // that need to have a warning issued along the way. Those that need a
+ // warning will be a subset of those that are aborted, so we don't need
+ // additional strong references here.
+ WeakTransactionArray transactionsThatNeedWarning;
+
+ for (const auto& transaction : transactionsToAbort) {
+ MOZ_ASSERT(transaction);
+ MOZ_ASSERT(!transaction->IsFinished());
+
+ // We warn for any transactions that could have written data, but
+ // ignore read-only transactions.
+ if (aShouldWarn && transaction->IsWriteAllowed()) {
+ transactionsThatNeedWarning.AppendElement(transaction.unsafeGetRawPtr());
+ }
+
+ transaction->Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
+ }
+
+ static const char kWarningMessage[] = "IndexedDBTransactionAbortNavigation";
+
+ for (IDBTransaction* transaction : transactionsThatNeedWarning) {
+ MOZ_ASSERT(transaction);
+
+ nsString filename;
+ uint32_t lineNo, column;
+ transaction->GetCallerLocation(filename, &lineNo, &column);
+
+ LogWarning(kWarningMessage, filename, lineNo, column);
+ }
+}
+
+PBackgroundIDBDatabaseFileChild* IDBDatabase::GetOrCreateFileActorForBlob(
+ Blob& aBlob) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mBackgroundActor);
+
+ // We use the File's nsIWeakReference as the key to the table because
+ // a) it is unique per blob, b) it is reference-counted so that we can
+ // guarantee that it stays alive, and c) it doesn't hold the actual File
+ // alive.
+ nsWeakPtr weakRef = do_GetWeakReference(&aBlob);
+ MOZ_ASSERT(weakRef);
+
+ PBackgroundIDBDatabaseFileChild* actor = nullptr;
+
+ if (!mFileActors.Get(weakRef, &actor)) {
+ BlobImpl* blobImpl = aBlob.Impl();
+ MOZ_ASSERT(blobImpl);
+
+ IPCBlob ipcBlob;
+ nsresult rv = IPCBlobUtils::Serialize(blobImpl, ipcBlob);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ auto* dbFile = new DatabaseFile(this);
+
+ actor = mBackgroundActor->SendPBackgroundIDBDatabaseFileConstructor(
+ dbFile, ipcBlob);
+ if (NS_WARN_IF(!actor)) {
+ return nullptr;
+ }
+
+ mFileActors.InsertOrUpdate(weakRef, actor);
+ }
+
+ MOZ_ASSERT(actor);
+
+ return actor;
+}
+
+void IDBDatabase::NoteFinishedFileActor(
+ PBackgroundIDBDatabaseFileChild* aFileActor) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aFileActor);
+
+ mFileActors.RemoveIf([aFileActor](const auto& iter) {
+ MOZ_ASSERT(iter.Key());
+ PBackgroundIDBDatabaseFileChild* actor = iter.Data();
+ MOZ_ASSERT(actor);
+
+ return actor == aFileActor;
+ });
+}
+
+void IDBDatabase::NoteActiveTransaction() {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mFactory);
+
+ // Increase the number of active transactions.
+ mFactory->UpdateActiveTransactionCount(1);
+}
+
+void IDBDatabase::NoteInactiveTransaction() {
+ AssertIsOnOwningThread();
+
+ if (!mBackgroundActor || !mFileActors.Count()) {
+ MOZ_ASSERT(mFactory);
+ mFactory->UpdateActiveTransactionCount(-1);
+ return;
+ }
+
+ RefPtr<Runnable> runnable =
+ NewRunnableMethod("IDBDatabase::NoteInactiveTransactionDelayed", this,
+ &IDBDatabase::NoteInactiveTransactionDelayed);
+ MOZ_ASSERT(runnable);
+
+ if (!NS_IsMainThread()) {
+ // Wrap as a nsICancelableRunnable to make workers happy.
+ runnable = MakeRefPtr<CancelableRunnableWrapper>(runnable.forget());
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(
+ EventTarget()->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL));
+}
+
+nsresult IDBDatabase::GetQuotaInfo(nsACString& aOrigin,
+ PersistenceType* aPersistenceType) {
+ using mozilla::dom::quota::QuotaManager;
+
+ MOZ_ASSERT(NS_IsMainThread(), "This can't work off the main thread!");
+
+ if (aPersistenceType) {
+ *aPersistenceType = mSpec->metadata().persistenceType();
+ MOZ_ASSERT(*aPersistenceType != PERSISTENCE_TYPE_INVALID);
+ }
+
+ PrincipalInfo* principalInfo = mFactory->GetPrincipalInfo();
+ MOZ_ASSERT(principalInfo);
+
+ switch (principalInfo->type()) {
+ case PrincipalInfo::TNullPrincipalInfo:
+ MOZ_CRASH("Is this needed?!");
+
+ case PrincipalInfo::TSystemPrincipalInfo:
+ aOrigin = QuotaManager::GetOriginForChrome();
+ return NS_OK;
+
+ case PrincipalInfo::TContentPrincipalInfo: {
+ QM_TRY_UNWRAP(auto principal, PrincipalInfoToPrincipal(*principalInfo));
+
+ QM_TRY_UNWRAP(aOrigin, QuotaManager::GetOriginFromPrincipal(principal));
+
+ return NS_OK;
+ }
+
+ default:
+ MOZ_CRASH("Unknown PrincipalInfo type!");
+ }
+
+ MOZ_CRASH("Should never get here!");
+}
+
+void IDBDatabase::ExpireFileActors(bool aExpireAll) {
+ AssertIsOnOwningThread();
+
+ if (mBackgroundActor && mFileActors.Count()) {
+ for (auto iter = mFileActors.Iter(); !iter.Done(); iter.Next()) {
+ nsISupports* key = iter.Key();
+ PBackgroundIDBDatabaseFileChild* actor = iter.Data();
+ MOZ_ASSERT(key);
+ MOZ_ASSERT(actor);
+
+ bool shouldExpire = aExpireAll;
+ if (!shouldExpire) {
+ nsWeakPtr weakRef = do_QueryInterface(key);
+ MOZ_ASSERT(weakRef);
+
+ nsCOMPtr<nsISupports> referent = do_QueryReferent(weakRef);
+ shouldExpire = !referent;
+ }
+
+ if (shouldExpire) {
+ PBackgroundIDBDatabaseFileChild::Send__delete__(actor);
+
+ if (!aExpireAll) {
+ iter.Remove();
+ }
+ }
+ }
+ if (aExpireAll) {
+ mFileActors.Clear();
+ }
+ } else {
+ MOZ_ASSERT(!mFileActors.Count());
+ }
+}
+
+void IDBDatabase::NoteLiveMutableFile(IDBMutableFile& aMutableFile) {
+ AssertIsOnOwningThread();
+ aMutableFile.AssertIsOnOwningThread();
+ MOZ_ASSERT(!mLiveMutableFiles.Contains(&aMutableFile));
+
+ mLiveMutableFiles.AppendElement(WrapNotNullUnchecked(&aMutableFile));
+}
+
+void IDBDatabase::NoteFinishedMutableFile(IDBMutableFile& aMutableFile) {
+ AssertIsOnOwningThread();
+ aMutableFile.AssertIsOnOwningThread();
+
+ // It's ok if this is called after we cleared the array, so don't assert that
+ // aMutableFile is in the list.
+
+ mLiveMutableFiles.RemoveElement(&aMutableFile);
+}
+
+void IDBDatabase::InvalidateMutableFiles() {
+ AssertIsOnOwningThread();
+
+ if (!mLiveMutableFiles.IsEmpty()) {
+ for (uint32_t count = mLiveMutableFiles.Length(), index = 0; index < count;
+ index++) {
+ mLiveMutableFiles[index]->Invalidate();
+ }
+
+ mLiveMutableFiles.Clear();
+ }
+}
+
+void IDBDatabase::Invalidate() {
+ AssertIsOnOwningThread();
+
+ if (!mInvalidated) {
+ mInvalidated = true;
+
+ InvalidateInternal();
+ }
+}
+
+void IDBDatabase::NoteInactiveTransactionDelayed() {
+ ExpireFileActors(/* aExpireAll */ false);
+
+ MOZ_ASSERT(mFactory);
+ mFactory->UpdateActiveTransactionCount(-1);
+}
+
+void IDBDatabase::LogWarning(const char* aMessageName,
+ const nsAString& aFilename, uint32_t aLineNumber,
+ uint32_t aColumnNumber) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aMessageName);
+
+ ScriptErrorHelper::DumpLocalizedMessage(
+ nsDependentCString(aMessageName), aFilename, aLineNumber, aColumnNumber,
+ nsIScriptError::warningFlag, mFactory->IsChrome(),
+ mFactory->InnerWindowID());
+}
+
+NS_IMPL_ADDREF_INHERITED(IDBDatabase, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(IDBDatabase, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IDBDatabase)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(IDBDatabase)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(IDBDatabase,
+ DOMEventTargetHelper)
+ tmp->AssertIsOnOwningThread();
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFactory)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(IDBDatabase,
+ DOMEventTargetHelper)
+ tmp->AssertIsOnOwningThread();
+
+ // Don't unlink mFactory!
+
+ // We've been unlinked, at the very least we should be able to prevent further
+ // transactions from starting and unblock any other SetVersion callers.
+ tmp->CloseInternal();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+void IDBDatabase::DisconnectFromOwner() {
+ InvalidateInternal();
+ DOMEventTargetHelper::DisconnectFromOwner();
+}
+
+void IDBDatabase::LastRelease() {
+ AssertIsOnOwningThread();
+
+ CloseInternal();
+
+ ExpireFileActors(/* aExpireAll */ true);
+
+ if (mBackgroundActor) {
+ mBackgroundActor->SendDeleteMeInternal();
+ MOZ_ASSERT(!mBackgroundActor, "SendDeleteMeInternal should have cleared!");
+ }
+}
+
+JSObject* IDBDatabase::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return IDBDatabase_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMETHODIMP
+CancelableRunnableWrapper::Run() {
+ const nsCOMPtr<nsIRunnable> runnable = std::move(mRunnable);
+
+ if (runnable) {
+ return runnable->Run();
+ }
+
+ return NS_OK;
+}
+
+nsresult CancelableRunnableWrapper::Cancel() {
+ if (mRunnable) {
+ mRunnable = nullptr;
+ return NS_OK;
+ }
+
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMPL_ISUPPORTS(IDBDatabase::Observer, nsIObserver)
+
+NS_IMETHODIMP
+IDBDatabase::Observer::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aTopic);
+
+ if (!strcmp(aTopic, kWindowObserverTopic)) {
+ if (mWeakDatabase) {
+ nsCOMPtr<nsISupportsPRUint64> supportsInt = do_QueryInterface(aSubject);
+ MOZ_ASSERT(supportsInt);
+
+ uint64_t windowId;
+ MOZ_ALWAYS_SUCCEEDS(supportsInt->GetData(&windowId));
+
+ if (windowId == mWindowId) {
+ RefPtr<IDBDatabase> database = mWeakDatabase;
+ mWeakDatabase = nullptr;
+
+ database->InvalidateInternal();
+ }
+ }
+
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, kCycleCollectionObserverTopic) ||
+ !strcmp(aTopic, kMemoryPressureObserverTopic)) {
+ if (mWeakDatabase) {
+ RefPtr<IDBDatabase> database = mWeakDatabase;
+
+ database->ExpireFileActors(/* aExpireAll */ false);
+ }
+
+ return NS_OK;
+ }
+
+ NS_WARNING("Unknown observer topic!");
+ return NS_OK;
+}
+
+nsresult IDBDatabase::RenameObjectStore(int64_t aObjectStoreId,
+ const nsAString& aName) {
+ MOZ_ASSERT(mSpec);
+
+ nsTArray<ObjectStoreSpec>& objectStores = mSpec->objectStores();
+ ObjectStoreSpec* foundObjectStoreSpec = nullptr;
+
+ // Find the matched object store spec and check if 'aName' is already used by
+ // another object store.
+
+ for (auto& objSpec : objectStores) {
+ const bool idIsCurrent = objSpec.metadata().id() == aObjectStoreId;
+
+ if (idIsCurrent) {
+ MOZ_ASSERT(!foundObjectStoreSpec);
+ foundObjectStoreSpec = &objSpec;
+ }
+
+ if (objSpec.metadata().name() == aName) {
+ if (idIsCurrent) {
+ return NS_OK;
+ }
+ return NS_ERROR_DOM_INDEXEDDB_RENAME_OBJECT_STORE_ERR;
+ }
+ }
+
+ MOZ_ASSERT(foundObjectStoreSpec);
+
+ // Update the name of the matched object store.
+ foundObjectStoreSpec->metadata().name().Assign(aName);
+
+ return NS_OK;
+}
+
+nsresult IDBDatabase::RenameIndex(int64_t aObjectStoreId, int64_t aIndexId,
+ const nsAString& aName) {
+ MOZ_ASSERT(mSpec);
+
+ nsTArray<ObjectStoreSpec>& objectStores = mSpec->objectStores();
+
+ ObjectStoreSpec* foundObjectStoreSpec = nullptr;
+ // Find the matched index metadata and check if 'aName' is already used by
+ // another index.
+ for (uint32_t objCount = objectStores.Length(), objIndex = 0;
+ objIndex < objCount; objIndex++) {
+ const ObjectStoreSpec& objSpec = objectStores[objIndex];
+ if (objSpec.metadata().id() == aObjectStoreId) {
+ foundObjectStoreSpec = &objectStores[objIndex];
+ break;
+ }
+ }
+
+ MOZ_ASSERT(foundObjectStoreSpec);
+
+ nsTArray<IndexMetadata>& indexes = foundObjectStoreSpec->indexes();
+ IndexMetadata* foundIndexMetadata = nullptr;
+ for (uint32_t idxCount = indexes.Length(), idxIndex = 0; idxIndex < idxCount;
+ idxIndex++) {
+ const IndexMetadata& metadata = indexes[idxIndex];
+ if (metadata.id() == aIndexId) {
+ MOZ_ASSERT(!foundIndexMetadata);
+ foundIndexMetadata = &indexes[idxIndex];
+ continue;
+ }
+ if (aName == metadata.name()) {
+ return NS_ERROR_DOM_INDEXEDDB_RENAME_INDEX_ERR;
+ }
+ }
+
+ MOZ_ASSERT(foundIndexMetadata);
+
+ // Update the name of the matched object store.
+ foundIndexMetadata->name() = nsString(aName);
+
+ return NS_OK;
+}
+
+void IDBDatabase::IncreaseActiveDatabaseCount() {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mFactory);
+ MOZ_ASSERT(!mIncreasedActiveDatabaseCount);
+
+ mFactory->UpdateActiveDatabaseCount(1);
+ mIncreasedActiveDatabaseCount = true;
+}
+
+void IDBDatabase::MaybeDecreaseActiveDatabaseCount() {
+ AssertIsOnOwningThread();
+
+ if (mIncreasedActiveDatabaseCount) {
+ // Decrease the number of active databases.
+ MOZ_ASSERT(mFactory);
+ mFactory->UpdateActiveDatabaseCount(-1);
+ mIncreasedActiveDatabaseCount = false;
+ }
+}
+
+} // namespace mozilla::dom