gecko-dev/dom/indexedDB/IDBDatabase.cpp
Bevis Tseng 44deb08f5c Bug 1151017 - Support the 'close' Event on Databases. r=khuey
Outline of this patch:

1. Define a new ipdl message called |CloseAfterInvalidationComplete| to trigger the close event after all transactions are complete only if the database is invalidated by the user agent.
2. Make sure the following event sequence is consistent during invalidation according to the steps in |5.2. Closing a database| by the following 2 solutions:
     IDBRequest.onerror -> IDBTransaction.onerror -> IDBTransaction.onabort -> IDBDatabase.onclose.
   2.1. In parent process, do not force to abort the transactions after invalidation but wait for all the transactions in its child process are complete.
   2.2. In child process, make sure that each IDBTransaction will notify its completion to the parent after all its pending IDBRequests are finished.
3. Add test_database_onclose.js to test the close event especially when read/write operation is ongoing.
4. Add test_database_close_without_onclose.js as a XPCShell test because setTimeout() is not preferred in Mochitest to ensure that the IDBDatabase.onclose event won't be sent after closed normally.
2016-05-31 18:08:20 +08:00

1436 lines
38 KiB
C++

/* -*- 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 "FileInfo.h"
#include "IDBEvents.h"
#include "IDBFactory.h"
#include "IDBIndex.h"
#include "IDBMutableFile.h"
#include "IDBObjectStore.h"
#include "IDBRequest.h"
#include "IDBTransaction.h"
#include "IDBFactory.h"
#include "IndexedDatabaseManager.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/EventDispatcher.h"
#include "MainThreadUtils.h"
#include "mozilla/Services.h"
#include "mozilla/storage.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/DOMStringList.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/ipc/BackgroundChild.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/dom/ipc/BlobChild.h"
#include "mozilla/dom/ipc/nsIRemoteBlob.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/ipc/FileDescriptor.h"
#include "mozilla/ipc/InputStreamParams.h"
#include "mozilla/ipc/InputStreamUtils.h"
#include "nsAutoPtr.h"
#include "nsCOMPtr.h"
#include "nsIDocument.h"
#include "nsIObserver.h"
#include "nsIObserverService.h"
#include "nsIScriptError.h"
#include "nsISupportsPrimitives.h"
#include "nsThreadUtils.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 {
namespace dom {
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(nsIRunnable* aRunnable)
: mRunnable(aRunnable)
{
MOZ_ASSERT(aRunnable);
}
private:
~CancelableRunnableWrapper()
{ }
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,
IDBFactory* aFactory,
BackgroundDatabaseChild* aActor,
DatabaseSpec* aSpec)
: IDBWrapperCache(aRequest)
, mFactory(aFactory)
, mSpec(aSpec)
, mBackgroundActor(aActor)
, mFileHandleDisabled(aRequest->IsFileHandleDisabled())
, mClosed(false)
, mInvalidated(false)
, mQuotaExceeded(false)
{
MOZ_ASSERT(aRequest);
MOZ_ASSERT(aFactory);
aFactory->AssertIsOnOwningThread();
MOZ_ASSERT(aActor);
MOZ_ASSERT(aSpec);
}
IDBDatabase::~IDBDatabase()
{
AssertIsOnOwningThread();
MOZ_ASSERT(!mBackgroundActor);
}
// static
already_AddRefed<IDBDatabase>
IDBDatabase::Create(IDBOpenDBRequest* aRequest,
IDBFactory* aFactory,
BackgroundDatabaseChild* aActor,
DatabaseSpec* aSpec)
{
MOZ_ASSERT(aRequest);
MOZ_ASSERT(aFactory);
aFactory->AssertIsOnOwningThread();
MOZ_ASSERT(aActor);
MOZ_ASSERT(aSpec);
RefPtr<IDBDatabase> db =
new IDBDatabase(aRequest, aFactory, aActor, aSpec);
db->SetScriptOwner(aRequest->GetScriptOwner());
if (NS_IsMainThread()) {
if (nsPIDOMWindowInner* window = aFactory->GetParentObject()) {
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.
if (NS_WARN_IF(NS_FAILED(
obsSvc->AddObserver(observer, kWindowObserverTopic, false)))) {
observer->Revoke();
return nullptr;
}
// These topics are not crucial.
if (NS_FAILED(obsSvc->AddObserver(observer,
kCycleCollectionObserverTopic,
false)) ||
NS_FAILED(obsSvc->AddObserver(observer,
kMemoryPressureObserverTopic,
false))) {
NS_WARNING("Failed to add additional memory observers!");
}
db->mObserver.swap(observer);
}
}
return db.forget();
}
#ifdef DEBUG
void
IDBDatabase::AssertIsOnOwningThread() const
{
MOZ_ASSERT(mFactory);
mFactory->AssertIsOnOwningThread();
}
PRThread*
IDBDatabase::OwningThread() const
{
MOZ_ASSERT(mFactory);
return mFactory->OwningThread();
}
#endif // DEBUG
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();
}
}
}
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 = new 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!
nsAutoPtr<DatabaseSpec> currentSpec(mSpec.forget());
mSpec = mPreviousSpec.forget();
RefreshSpec(/* aMayDelete */ true);
}
void
IDBDatabase::RefreshSpec(bool aMayDelete)
{
AssertIsOnOwningThread();
for (auto iter = mTransactions.Iter(); !iter.Done(); iter.Next()) {
RefPtr<IDBTransaction> transaction = iter.Get()->GetKey();
MOZ_ASSERT(transaction);
transaction->AssertIsOnOwningThread();
transaction->RefreshSpec(aMayDelete);
}
}
nsPIDOMWindowInner*
IDBDatabase::GetParentObject() const
{
return mFactory->GetParentObject();
}
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();
}
already_AddRefed<DOMStringList>
IDBDatabase::ObjectStoreNames() const
{
AssertIsOnOwningThread();
MOZ_ASSERT(mSpec);
const nsTArray<ObjectStoreSpec>& objectStores = mSpec->objectStores();
RefPtr<DOMStringList> list = new DOMStringList();
if (!objectStores.IsEmpty()) {
nsTArray<nsString>& listNames = list->StringArray();
listNames.SetCapacity(objectStores.Length());
for (uint32_t index = 0; index < objectStores.Length(); index++) {
listNames.InsertElementSorted(objectStores[index].metadata().name());
}
}
return list.forget();
}
already_AddRefed<nsIDocument>
IDBDatabase::GetOwnerDocument() const
{
if (nsPIDOMWindowInner* window = GetOwner()) {
nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
return doc.forget();
}
return nullptr;
}
already_AddRefed<IDBObjectStore>
IDBDatabase::CreateObjectStore(
const nsAString& aName,
const IDBObjectStoreParameters& aOptionalParameters,
ErrorResult& aRv)
{
AssertIsOnOwningThread();
IDBTransaction* transaction = IDBTransaction::GetCurrent();
if (!transaction ||
transaction->Database() != this ||
transaction->GetMode() != IDBTransaction::VERSION_CHANGE) {
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
return nullptr;
}
if (!transaction->IsOpen()) {
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
return nullptr;
}
KeyPath keyPath(0);
if (NS_FAILED(KeyPath::Parse(aOptionalParameters.mKeyPath, &keyPath))) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return nullptr;
}
nsTArray<ObjectStoreSpec>& objectStores = mSpec->objectStores();
for (uint32_t count = objectStores.Length(), index = 0;
index < count;
index++) {
if (aName == objectStores[index].metadata().name()) {
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR);
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);
}
RefPtr<IDBObjectStore> 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("IndexedDB %s: Child Transaction[%lld] Request[%llu]: "
"database(%s).transaction(%s).createObjectStore(%s)",
"IndexedDB %s: C T[%lld] R[%llu]: "
"IDBDatabase.createObjectStore()",
IDB_LOG_ID_STRING(),
transaction->LoggingSerialNumber(),
requestSerialNumber,
IDB_LOG_STRINGIFY(this),
IDB_LOG_STRINGIFY(transaction),
IDB_LOG_STRINGIFY(objectStore));
return objectStore.forget();
}
void
IDBDatabase::DeleteObjectStore(const nsAString& aName, ErrorResult& aRv)
{
AssertIsOnOwningThread();
IDBTransaction* transaction = IDBTransaction::GetCurrent();
if (!transaction ||
transaction->Database() != this ||
transaction->GetMode() != IDBTransaction::VERSION_CHANGE) {
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
return;
}
if (!transaction->IsOpen()) {
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
return;
}
nsTArray<ObjectStoreSpec>& specArray = mSpec->objectStores();
int64_t objectStoreId = 0;
for (uint32_t specCount = specArray.Length(), specIndex = 0;
specIndex < specCount;
specIndex++) {
const ObjectStoreMetadata& metadata = specArray[specIndex].metadata();
MOZ_ASSERT(metadata.id());
if (aName == metadata.name()) {
objectStoreId = metadata.id();
// Must do this before altering the metadata array!
transaction->DeleteObjectStore(objectStoreId);
specArray.RemoveElementAt(specIndex);
RefreshSpec(/* aMayDelete */ false);
break;
}
}
if (!objectStoreId) {
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR);
return;
}
// 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("IndexedDB %s: Child Transaction[%lld] Request[%llu]: "
"database(%s).transaction(%s).deleteObjectStore(\"%s\")",
"IndexedDB %s: C T[%lld] R[%llu]: "
"IDBDatabase.deleteObjectStore()",
IDB_LOG_ID_STRING(),
transaction->LoggingSerialNumber(),
requestSerialNumber,
IDB_LOG_STRINGIFY(this),
IDB_LOG_STRINGIFY(transaction),
NS_ConvertUTF16toUTF8(aName).get());
}
already_AddRefed<IDBTransaction>
IDBDatabase::Transaction(JSContext* aCx,
const StringOrStringSequence& aStoreNames,
IDBTransactionMode aMode,
ErrorResult& aRv)
{
AssertIsOnOwningThread();
if ((aMode == IDBTransactionMode::Readwriteflush ||
aMode == IDBTransactionMode::Cleanup) &&
!IndexedDatabaseManager::ExperimentalFeaturesEnabled()) {
// 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>(
NS_LITERAL_STRING("Argument 2 of IDBDatabase.transaction"),
NS_LITERAL_STRING("readwriteflush"),
NS_LITERAL_STRING("IDBTransactionMode"));
return nullptr;
}
RefPtr<IDBTransaction> transaction;
aRv = Transaction(aCx, aStoreNames, aMode, getter_AddRefs(transaction));
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
return transaction.forget();
}
nsresult
IDBDatabase::Transaction(JSContext* aCx,
const StringOrStringSequence& aStoreNames,
IDBTransactionMode aMode,
IDBTransaction** aTransaction)
{
AssertIsOnOwningThread();
if (NS_WARN_IF((aMode == IDBTransactionMode::Readwriteflush ||
aMode == IDBTransactionMode::Cleanup) &&
!IndexedDatabaseManager::ExperimentalFeaturesEnabled())) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
if (QuotaManager::IsShuttingDown()) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
if (mClosed || RunningVersionChangeTransaction()) {
return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
}
AutoTArray<nsString, 1> stackSequence;
if (aStoreNames.IsString()) {
stackSequence.AppendElement(aStoreNames.GetAsString());
} else {
MOZ_ASSERT(aStoreNames.IsStringSequence());
if (aStoreNames.GetAsStringSequence().IsEmpty()) {
return NS_ERROR_DOM_INVALID_ACCESS_ERR;
}
}
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);
// Check to make sure the object store names we collected actually exist.
for (uint32_t nameIndex = 0; nameIndex < nameCount; nameIndex++) {
const nsString& name = storeNames[nameIndex];
bool found = false;
for (uint32_t objCount = objectStores.Length(), objIndex = 0;
objIndex < objCount;
objIndex++) {
if (objectStores[objIndex].metadata().name() == name) {
found = true;
break;
}
}
if (!found) {
return NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR;
}
sortedStoreNames.InsertElementSorted(name);
}
// Remove any duplicates.
for (uint32_t nameIndex = nameCount - 1; nameIndex > 0; nameIndex--) {
if (sortedStoreNames[nameIndex] == sortedStoreNames[nameIndex - 1]) {
sortedStoreNames.RemoveElementAt(nameIndex);
}
}
IDBTransaction::Mode mode;
switch (aMode) {
case IDBTransactionMode::Readonly:
mode = IDBTransaction::READ_ONLY;
break;
case IDBTransactionMode::Readwrite:
if (mQuotaExceeded) {
mode = IDBTransaction::CLEANUP;
mQuotaExceeded = false;
} else {
mode = IDBTransaction::READ_WRITE;
}
break;
case IDBTransactionMode::Readwriteflush:
mode = IDBTransaction::READ_WRITE_FLUSH;
break;
case IDBTransactionMode::Cleanup:
mode = IDBTransaction::CLEANUP;
mQuotaExceeded = false;
break;
case IDBTransactionMode::Versionchange:
return NS_ERROR_DOM_TYPE_ERR;
default:
MOZ_CRASH("Unknown mode!");
}
RefPtr<IDBTransaction> transaction =
IDBTransaction::Create(aCx, this, sortedStoreNames, mode);
if (NS_WARN_IF(!transaction)) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
BackgroundTransactionChild* actor =
new BackgroundTransactionChild(transaction);
IDB_LOG_MARK("IndexedDB %s: Child Transaction[%lld]: "
"database(%s).transaction(%s)",
"IndexedDB %s: C T[%lld]: IDBDatabase.transaction()",
IDB_LOG_ID_STRING(),
transaction->LoggingSerialNumber(),
IDB_LOG_STRINGIFY(this),
IDB_LOG_STRINGIFY(transaction));
MOZ_ALWAYS_TRUE(
mBackgroundActor->SendPBackgroundIDBTransactionConstructor(actor,
sortedStoreNames,
mode));
transaction->SetBackgroundActor(actor);
if (mode == IDBTransaction::CLEANUP) {
ExpireFileActors(/* aExpireAll */ true);
}
transaction.forget(aTransaction);
return NS_OK;
}
StorageType
IDBDatabase::Storage() const
{
AssertIsOnOwningThread();
MOZ_ASSERT(mSpec);
return PersistenceTypeToStorage(mSpec->metadata().persistenceType());
}
already_AddRefed<IDBRequest>
IDBDatabase::CreateMutableFile(JSContext* aCx,
const nsAString& aName,
const Optional<nsAString>& aType,
ErrorResult& aRv)
{
AssertIsOnOwningThread();
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);
RefPtr<IDBRequest> request = IDBRequest::Create(aCx, this, nullptr);
MOZ_ASSERT(request);
BackgroundDatabaseRequestChild* actor =
new BackgroundDatabaseRequestChild(this, request);
IDB_LOG_MARK("IndexedDB %s: Child Request[%llu]: "
"database(%s).createMutableFile(%s)",
"IndexedDB %s: C R[%llu]: IDBDatabase.createMutableFile()",
IDB_LOG_ID_STRING(),
request->LoggingSerialNumber(),
IDB_LOG_STRINGIFY(this),
NS_ConvertUTF16toUTF8(aName).get());
mBackgroundActor->SendPBackgroundIDBDatabaseRequestConstructor(actor, params);
return request.forget();
}
void
IDBDatabase::RegisterTransaction(IDBTransaction* aTransaction)
{
AssertIsOnOwningThread();
MOZ_ASSERT(aTransaction);
aTransaction->AssertIsOnOwningThread();
MOZ_ASSERT(!mTransactions.Contains(aTransaction));
mTransactions.PutEntry(aTransaction);
}
void
IDBDatabase::UnregisterTransaction(IDBTransaction* aTransaction)
{
AssertIsOnOwningThread();
MOZ_ASSERT(aTransaction);
aTransaction->AssertIsOnOwningThread();
MOZ_ASSERT(mTransactions.Contains(aTransaction));
mTransactions.RemoveEntry(aTransaction);
}
void
IDBDatabase::AbortTransactions(bool aShouldWarn)
{
AssertIsOnOwningThread();
class MOZ_STACK_CLASS Helper final
{
typedef AutoTArray<RefPtr<IDBTransaction>, 20> StrongTransactionArray;
typedef AutoTArray<IDBTransaction*, 20> WeakTransactionArray;
public:
static void
AbortTransactions(IDBDatabase* aDatabase, const bool aShouldWarn)
{
MOZ_ASSERT(aDatabase);
aDatabase->AssertIsOnOwningThread();
nsTHashtable<nsPtrHashKey<IDBTransaction>>& transactionTable =
aDatabase->mTransactions;
if (!transactionTable.Count()) {
return;
}
StrongTransactionArray transactionsToAbort;
transactionsToAbort.SetCapacity(transactionTable.Count());
for (auto iter = transactionTable.Iter(); !iter.Done(); iter.Next()) {
IDBTransaction* transaction = iter.Get()->GetKey();
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->IsDone()) {
transactionsToAbort.AppendElement(transaction);
}
}
MOZ_ASSERT(transactionsToAbort.Length() <= transactionTable.Count());
if (transactionsToAbort.IsEmpty()) {
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 (RefPtr<IDBTransaction>& transaction : transactionsToAbort) {
MOZ_ASSERT(transaction);
MOZ_ASSERT(!transaction->IsDone());
if (aShouldWarn) {
switch (transaction->GetMode()) {
// We ignore transactions that could not have written any data.
case IDBTransaction::READ_ONLY:
break;
// We warn for any transactions that could have written data.
case IDBTransaction::READ_WRITE:
case IDBTransaction::READ_WRITE_FLUSH:
case IDBTransaction::CLEANUP:
case IDBTransaction::VERSION_CHANGE:
transactionsThatNeedWarning.AppendElement(transaction);
break;
default:
MOZ_CRASH("Unknown mode!");
}
}
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);
aDatabase->LogWarning(kWarningMessage, filename, lineNo, column);
}
}
};
Helper::AbortTransactions(this, aShouldWarn);
}
PBackgroundIDBDatabaseFileChild*
IDBDatabase::GetOrCreateFileActorForBlob(Blob* aBlob)
{
AssertIsOnOwningThread();
MOZ_ASSERT(aBlob);
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.
nsCOMPtr<nsIDOMBlob> blob = aBlob;
nsCOMPtr<nsIWeakReference> weakRef = do_GetWeakReference(blob);
MOZ_ASSERT(weakRef);
PBackgroundIDBDatabaseFileChild* actor = nullptr;
if (!mFileActors.Get(weakRef, &actor)) {
BlobImpl* blobImpl = aBlob->Impl();
MOZ_ASSERT(blobImpl);
if (mReceivedBlobs.GetEntry(weakRef)) {
// This blob was previously retrieved from the database.
nsCOMPtr<nsIRemoteBlob> remoteBlob = do_QueryObject(blobImpl);
MOZ_ASSERT(remoteBlob);
BlobChild* blobChild = remoteBlob->GetBlobChild();
MOZ_ASSERT(blobChild);
#ifdef DEBUG
{
PBackgroundChild* backgroundManager = blobChild->GetBackgroundManager();
MOZ_ASSERT(backgroundManager);
PBackgroundChild* thisManager = mBackgroundActor->Manager()->Manager();
MOZ_ASSERT(thisManager);
MOZ_ASSERT(thisManager == backgroundManager);
}
#endif
auto* dbFile = new DatabaseFile(this);
actor =
mBackgroundActor->SendPBackgroundIDBDatabaseFileConstructor(dbFile,
blobChild);
if (NS_WARN_IF(!actor)) {
return nullptr;
}
} else {
// Make sure that the input stream we get here is one that can actually be
// serialized to PBackground.
PBackgroundChild* backgroundManager =
mBackgroundActor->Manager()->Manager();
MOZ_ASSERT(backgroundManager);
auto* blobChild =
static_cast<BlobChild*>(
BackgroundChild::GetOrCreateActorForBlob(backgroundManager, aBlob));
MOZ_ASSERT(blobChild);
auto* dbFile = new DatabaseFile(this);
actor =
mBackgroundActor->SendPBackgroundIDBDatabaseFileConstructor(dbFile,
blobChild);
if (NS_WARN_IF(!actor)) {
return nullptr;
}
}
MOZ_ASSERT(actor);
mFileActors.Put(weakRef, actor);
}
MOZ_ASSERT(actor);
return actor;
}
void
IDBDatabase::NoteFinishedFileActor(PBackgroundIDBDatabaseFileChild* aFileActor)
{
AssertIsOnOwningThread();
MOZ_ASSERT(aFileActor);
for (auto iter = mFileActors.Iter(); !iter.Done(); iter.Next()) {
MOZ_ASSERT(iter.Key());
PBackgroundIDBDatabaseFileChild* actor = iter.Data();
MOZ_ASSERT(actor);
if (actor == aFileActor) {
iter.Remove();
}
}
}
void
IDBDatabase::NoteReceivedBlob(Blob* aBlob)
{
AssertIsOnOwningThread();
MOZ_ASSERT(aBlob);
MOZ_ASSERT(mBackgroundActor);
#ifdef DEBUG
{
RefPtr<BlobImpl> blobImpl = aBlob->Impl();
MOZ_ASSERT(blobImpl);
nsCOMPtr<nsIRemoteBlob> remoteBlob = do_QueryObject(blobImpl);
MOZ_ASSERT(remoteBlob);
BlobChild* blobChild = remoteBlob->GetBlobChild();
MOZ_ASSERT(blobChild);
PBackgroundChild* backgroundManager = blobChild->GetBackgroundManager();
MOZ_ASSERT(backgroundManager);
PBackgroundChild* thisManager = mBackgroundActor->Manager()->Manager();
MOZ_ASSERT(thisManager);
MOZ_ASSERT(thisManager == backgroundManager);
}
#endif
nsCOMPtr<nsIDOMBlob> blob = aBlob;
nsCOMPtr<nsIWeakReference> weakRef = do_GetWeakReference(blob);
MOZ_ASSERT(weakRef);
// It's ok if this entry already exists in the table.
mReceivedBlobs.PutEntry(weakRef);
}
void
IDBDatabase::DelayedMaybeExpireFileActors()
{
AssertIsOnOwningThread();
if (!mBackgroundActor || !mFileActors.Count()) {
return;
}
nsCOMPtr<nsIRunnable> runnable =
NewRunnableMethod<bool>(this,
&IDBDatabase::ExpireFileActors,
/* aExpireAll */ false);
MOZ_ASSERT(runnable);
if (!NS_IsMainThread()) {
// Wrap as a nsICancelableRunnable to make workers happy.
nsCOMPtr<nsIRunnable> cancelable = new CancelableRunnableWrapper(runnable);
cancelable.swap(runnable);
}
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(runnable));
}
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:
QuotaManager::GetInfoForChrome(nullptr, nullptr, &aOrigin, nullptr);
return NS_OK;
case PrincipalInfo::TContentPrincipalInfo: {
nsresult rv;
nsCOMPtr<nsIPrincipal> principal =
PrincipalInfoToPrincipal(*principalInfo, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = QuotaManager::GetInfoFromPrincipal(principal,
nullptr,
nullptr,
&aOrigin,
nullptr);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
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) {
nsCOMPtr<nsIWeakReference> 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());
}
if (mReceivedBlobs.Count()) {
if (aExpireAll) {
mReceivedBlobs.Clear();
} else {
for (auto iter = mReceivedBlobs.Iter(); !iter.Done(); iter.Next()) {
nsISupports* key = iter.Get()->GetKey();
MOZ_ASSERT(key);
nsCOMPtr<nsIWeakReference> weakRef = do_QueryInterface(key);
MOZ_ASSERT(weakRef);
nsCOMPtr<nsISupports> referent = do_QueryReferent(weakRef);
if (!referent) {
iter.Remove();
}
}
}
}
}
void
IDBDatabase::NoteLiveMutableFile(IDBMutableFile* aMutableFile)
{
AssertIsOnOwningThread();
MOZ_ASSERT(aMutableFile);
aMutableFile->AssertIsOnOwningThread();
MOZ_ASSERT(!mLiveMutableFiles.Contains(aMutableFile));
mLiveMutableFiles.AppendElement(aMutableFile);
}
void
IDBDatabase::NoteFinishedMutableFile(IDBMutableFile* aMutableFile)
{
AssertIsOnOwningThread();
MOZ_ASSERT(aMutableFile);
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::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, IDBWrapperCache)
NS_IMPL_RELEASE_INHERITED(IDBDatabase, IDBWrapperCache)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(IDBDatabase)
NS_INTERFACE_MAP_END_INHERITING(IDBWrapperCache)
NS_IMPL_CYCLE_COLLECTION_CLASS(IDBDatabase)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(IDBDatabase, IDBWrapperCache)
tmp->AssertIsOnOwningThread();
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFactory)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(IDBDatabase, IDBWrapperCache)
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::LastRelease()
{
AssertIsOnOwningThread();
CloseInternal();
if (mBackgroundActor) {
mBackgroundActor->SendDeleteMeInternal();
MOZ_ASSERT(!mBackgroundActor, "SendDeleteMeInternal should have cleared!");
}
}
nsresult
IDBDatabase::PostHandleEvent(EventChainPostVisitor& aVisitor)
{
nsresult rv =
IndexedDatabaseManager::CommonPostHandleEvent(aVisitor, mFactory);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
JSObject*
IDBDatabase::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return IDBDatabaseBinding::Wrap(aCx, this, aGivenProto);
}
NS_IMETHODIMP
CancelableRunnableWrapper::Run()
{
nsCOMPtr<nsIRunnable> runnable;
mRunnable.swap(runnable);
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 (uint32_t objCount = objectStores.Length(), objIndex = 0;
objIndex < objCount;
objIndex++) {
const ObjectStoreSpec& objSpec = objectStores[objIndex];
if (objSpec.metadata().id() == aObjectStoreId) {
MOZ_ASSERT(!foundObjectStoreSpec);
foundObjectStoreSpec = &objectStores[objIndex];
continue;
}
if (aName == objSpec.metadata().name()) {
return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
}
}
MOZ_ASSERT(foundObjectStoreSpec);
// Update the name of the matched object store.
foundObjectStoreSpec->metadata().name() = nsString(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_CONSTRAINT_ERR;
}
}
MOZ_ASSERT(foundIndexMetadata);
// Update the name of the matched object store.
foundIndexMetadata->name() = nsString(aName);
return NS_OK;
}
} // namespace dom
} // namespace mozilla