mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-30 21:55:31 +00:00
44deb08f5c
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.
1436 lines
38 KiB
C++
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
|