tenfourfox/dom/indexedDB/ActorsChild.cpp

3122 lines
81 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 "ActorsChild.h"
#include "BackgroundChildImpl.h"
#include "IDBDatabase.h"
#include "IDBEvents.h"
#include "IDBFactory.h"
#include "IDBIndex.h"
#include "IDBMutableFile.h"
#include "IDBObjectStore.h"
#include "IDBMutableFile.h"
#include "IDBRequest.h"
#include "IDBTransaction.h"
#include "IndexedDatabase.h"
#include "IndexedDatabaseInlines.h"
#include "mozilla/BasicEvents.h"
#include "mozilla/Maybe.h"
#include "mozilla/TypeTraits.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/PermissionMessageUtils.h"
#include "mozilla/dom/TabChild.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseFileChild.h"
#include "mozilla/dom/indexedDB/PIndexedDBPermissionRequestChild.h"
#include "mozilla/dom/ipc/BlobChild.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "nsCOMPtr.h"
#include "nsContentUtils.h"
#include "nsIBFCacheEntry.h"
#include "nsIDocument.h"
#include "nsIDOMEvent.h"
#include "nsIEventTarget.h"
#include "nsPIDOMWindow.h"
#include "nsThreadUtils.h"
#include "nsTraceRefcnt.h"
#include "PermissionRequestBase.h"
#include "ProfilerHelpers.h"
#include "ReportInternalError.h"
#include "WorkerPrivate.h"
#include "WorkerRunnable.h"
#ifdef DEBUG
#include "IndexedDatabaseManager.h"
#endif
#define GC_ON_IPC_MESSAGES 0
#if defined(DEBUG) || GC_ON_IPC_MESSAGES
#include "js/GCAPI.h"
#include "nsJSEnvironment.h"
#define BUILD_GC_ON_IPC_MESSAGES
#endif // DEBUG || GC_ON_IPC_MESSAGES
namespace mozilla {
using ipc::PrincipalInfo;
namespace dom {
using namespace workers;
namespace indexedDB {
/*******************************************************************************
* ThreadLocal
******************************************************************************/
ThreadLocal::ThreadLocal(const nsID& aBackgroundChildLoggingId)
: mLoggingInfo(aBackgroundChildLoggingId, 1, -1, 1)
, mCurrentTransaction(0)
#ifdef DEBUG
, mOwningThread(PR_GetCurrentThread())
#endif
{
MOZ_ASSERT(mOwningThread);
MOZ_COUNT_CTOR(mozilla::dom::indexedDB::ThreadLocal);
// NSID_LENGTH counts the null terminator, SetLength() does not.
mLoggingIdString.SetLength(NSID_LENGTH - 1);
aBackgroundChildLoggingId.ToProvidedString(
*reinterpret_cast<char(*)[NSID_LENGTH]>(mLoggingIdString.BeginWriting()));
}
ThreadLocal::~ThreadLocal()
{
MOZ_COUNT_DTOR(mozilla::dom::indexedDB::ThreadLocal);
}
#ifdef DEBUG
void
ThreadLocal::AssertIsOnOwningThread() const
{
MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
}
#endif // DEBUG
/*******************************************************************************
* Helpers
******************************************************************************/
namespace {
void
MaybeCollectGarbageOnIPCMessage()
{
#ifdef BUILD_GC_ON_IPC_MESSAGES
static const bool kCollectGarbageOnIPCMessages =
#if GC_ON_IPC_MESSAGES
true;
#else
false;
#endif // GC_ON_IPC_MESSAGES
if (!kCollectGarbageOnIPCMessages) {
return;
}
static bool haveWarnedAboutGC = false;
static bool haveWarnedAboutNonMainThread = false;
if (!haveWarnedAboutGC) {
haveWarnedAboutGC = true;
NS_WARNING("IndexedDB child actor GC debugging enabled!");
}
if (!NS_IsMainThread()) {
if (!haveWarnedAboutNonMainThread) {
haveWarnedAboutNonMainThread = true;
NS_WARNING("Don't know how to GC on a non-main thread yet.");
}
return;
}
nsJSContext::GarbageCollectNow(JS::gcreason::DOM_IPC);
nsJSContext::CycleCollectNow();
#endif // BUILD_GC_ON_IPC_MESSAGES
}
class MOZ_STACK_CLASS AutoSetCurrentTransaction final
{
typedef mozilla::ipc::BackgroundChildImpl BackgroundChildImpl;
IDBTransaction* const mTransaction;
IDBTransaction* mPreviousTransaction;
ThreadLocal* mThreadLocal;
public:
explicit AutoSetCurrentTransaction(IDBTransaction* aTransaction)
: mTransaction(aTransaction)
, mPreviousTransaction(nullptr)
, mThreadLocal(nullptr)
{
if (aTransaction) {
BackgroundChildImpl::ThreadLocal* threadLocal =
BackgroundChildImpl::GetThreadLocalForCurrentThread();
MOZ_ASSERT(threadLocal);
// Hang onto this for resetting later.
mThreadLocal = threadLocal->mIndexedDBThreadLocal;
MOZ_ASSERT(mThreadLocal);
// Save the current value.
mPreviousTransaction = mThreadLocal->GetCurrentTransaction();
// Set the new value.
mThreadLocal->SetCurrentTransaction(aTransaction);
}
}
~AutoSetCurrentTransaction()
{
MOZ_ASSERT_IF(mThreadLocal, mTransaction);
MOZ_ASSERT_IF(mThreadLocal,
mThreadLocal->GetCurrentTransaction() == mTransaction);
if (mThreadLocal) {
// Reset old value.
mThreadLocal->SetCurrentTransaction(mPreviousTransaction);
}
}
IDBTransaction*
Transaction() const
{
return mTransaction;
}
};
class MOZ_STACK_CLASS ResultHelper final
: public IDBRequest::ResultCallback
{
IDBRequest* mRequest;
AutoSetCurrentTransaction mAutoTransaction;
union
{
IDBDatabase* mDatabase;
IDBCursor* mCursor;
IDBMutableFile* mMutableFile;
StructuredCloneReadInfo* mStructuredClone;
const nsTArray<StructuredCloneReadInfo>* mStructuredCloneArray;
const Key* mKey;
const nsTArray<Key>* mKeyArray;
const JS::Value* mJSVal;
const JS::Handle<JS::Value>* mJSValHandle;
} mResult;
enum
{
ResultTypeDatabase,
ResultTypeCursor,
ResultTypeMutableFile,
ResultTypeStructuredClone,
ResultTypeStructuredCloneArray,
ResultTypeKey,
ResultTypeKeyArray,
ResultTypeJSVal,
ResultTypeJSValHandle,
} mResultType;
public:
ResultHelper(IDBRequest* aRequest,
IDBTransaction* aTransaction,
IDBDatabase* aResult)
: mRequest(aRequest)
, mAutoTransaction(aTransaction)
, mResultType(ResultTypeDatabase)
{
MOZ_ASSERT(aRequest);
MOZ_ASSERT(aResult);
mResult.mDatabase = aResult;
}
ResultHelper(IDBRequest* aRequest,
IDBTransaction* aTransaction,
IDBCursor* aResult)
: mRequest(aRequest)
, mAutoTransaction(aTransaction)
, mResultType(ResultTypeCursor)
{
MOZ_ASSERT(aRequest);
mResult.mCursor = aResult;
}
ResultHelper(IDBRequest* aRequest,
IDBTransaction* aTransaction,
IDBMutableFile* aResult)
: mRequest(aRequest)
, mAutoTransaction(aTransaction)
, mResultType(ResultTypeMutableFile)
{
MOZ_ASSERT(aRequest);
mResult.mMutableFile = aResult;
}
ResultHelper(IDBRequest* aRequest,
IDBTransaction* aTransaction,
StructuredCloneReadInfo* aResult)
: mRequest(aRequest)
, mAutoTransaction(aTransaction)
, mResultType(ResultTypeStructuredClone)
{
MOZ_ASSERT(aRequest);
MOZ_ASSERT(aResult);
mResult.mStructuredClone = aResult;
}
ResultHelper(IDBRequest* aRequest,
IDBTransaction* aTransaction,
const nsTArray<StructuredCloneReadInfo>* aResult)
: mRequest(aRequest)
, mAutoTransaction(aTransaction)
, mResultType(ResultTypeStructuredCloneArray)
{
MOZ_ASSERT(aRequest);
MOZ_ASSERT(aResult);
mResult.mStructuredCloneArray = aResult;
}
ResultHelper(IDBRequest* aRequest,
IDBTransaction* aTransaction,
const Key* aResult)
: mRequest(aRequest)
, mAutoTransaction(aTransaction)
, mResultType(ResultTypeKey)
{
MOZ_ASSERT(aRequest);
MOZ_ASSERT(aResult);
mResult.mKey = aResult;
}
ResultHelper(IDBRequest* aRequest,
IDBTransaction* aTransaction,
const nsTArray<Key>* aResult)
: mRequest(aRequest)
, mAutoTransaction(aTransaction)
, mResultType(ResultTypeKeyArray)
{
MOZ_ASSERT(aRequest);
MOZ_ASSERT(aResult);
mResult.mKeyArray = aResult;
}
ResultHelper(IDBRequest* aRequest,
IDBTransaction* aTransaction,
const JS::Value* aResult)
: mRequest(aRequest)
, mAutoTransaction(aTransaction)
, mResultType(ResultTypeJSVal)
{
MOZ_ASSERT(aRequest);
MOZ_ASSERT(!aResult->isGCThing());
mResult.mJSVal = aResult;
}
ResultHelper(IDBRequest* aRequest,
IDBTransaction* aTransaction,
const JS::Handle<JS::Value>* aResult)
: mRequest(aRequest)
, mAutoTransaction(aTransaction)
, mResultType(ResultTypeJSValHandle)
{
MOZ_ASSERT(aRequest);
mResult.mJSValHandle = aResult;
}
IDBRequest*
Request() const
{
return mRequest;
}
IDBTransaction*
Transaction() const
{
return mAutoTransaction.Transaction();
}
virtual nsresult
GetResult(JSContext* aCx, JS::MutableHandle<JS::Value> aResult) override
{
MOZ_ASSERT(aCx);
MOZ_ASSERT(mRequest);
switch (mResultType) {
case ResultTypeDatabase:
return GetResult(aCx, mResult.mDatabase, aResult);
case ResultTypeCursor:
return GetResult(aCx, mResult.mCursor, aResult);
case ResultTypeMutableFile:
return GetResult(aCx, mResult.mMutableFile, aResult);
case ResultTypeStructuredClone:
return GetResult(aCx, mResult.mStructuredClone, aResult);
case ResultTypeStructuredCloneArray:
return GetResult(aCx, mResult.mStructuredCloneArray, aResult);
case ResultTypeKey:
return GetResult(aCx, mResult.mKey, aResult);
case ResultTypeKeyArray:
return GetResult(aCx, mResult.mKeyArray, aResult);
case ResultTypeJSVal:
aResult.set(*mResult.mJSVal);
return NS_OK;
case ResultTypeJSValHandle:
aResult.set(*mResult.mJSValHandle);
return NS_OK;
default:
MOZ_CRASH("Unknown result type!");
}
MOZ_CRASH("Should never get here!");
}
private:
template <class T>
typename EnableIf<IsSame<T, IDBDatabase>::value ||
IsSame<T, IDBCursor>::value ||
IsSame<T, IDBMutableFile>::value,
nsresult>::Type
GetResult(JSContext* aCx,
T* aDOMObject,
JS::MutableHandle<JS::Value> aResult)
{
if (!aDOMObject) {
aResult.setNull();
return NS_OK;
}
bool ok = GetOrCreateDOMReflector(aCx, aDOMObject, aResult);
if (NS_WARN_IF(!ok)) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
return NS_OK;
}
nsresult
GetResult(JSContext* aCx,
StructuredCloneReadInfo* aCloneInfo,
JS::MutableHandle<JS::Value> aResult)
{
bool ok = IDBObjectStore::DeserializeValue(aCx, *aCloneInfo, aResult);
aCloneInfo->mCloneBuffer.clear();
if (NS_WARN_IF(!ok)) {
return NS_ERROR_DOM_DATA_CLONE_ERR;
}
return NS_OK;
}
nsresult
GetResult(JSContext* aCx,
const nsTArray<StructuredCloneReadInfo>* aCloneInfos,
JS::MutableHandle<JS::Value> aResult)
{
JS::Rooted<JSObject*> array(aCx, JS_NewArrayObject(aCx, 0));
if (NS_WARN_IF(!array)) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
if (!aCloneInfos->IsEmpty()) {
const uint32_t count = aCloneInfos->Length();
if (NS_WARN_IF(!JS_SetArrayLength(aCx, array, count))) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
for (uint32_t index = 0; index < count; index++) {
auto& cloneInfo =
const_cast<StructuredCloneReadInfo&>(aCloneInfos->ElementAt(index));
JS::Rooted<JS::Value> value(aCx);
nsresult rv = GetResult(aCx, &cloneInfo, &value);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (NS_WARN_IF(!JS_DefineElement(aCx, array, index, value,
JSPROP_ENUMERATE))) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
}
}
aResult.setObject(*array);
return NS_OK;
}
nsresult
GetResult(JSContext* aCx,
const Key* aKey,
JS::MutableHandle<JS::Value> aResult)
{
nsresult rv = aKey->ToJSVal(aCx, aResult);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
nsresult
GetResult(JSContext* aCx,
const nsTArray<Key>* aKeys,
JS::MutableHandle<JS::Value> aResult)
{
JS::Rooted<JSObject*> array(aCx, JS_NewArrayObject(aCx, 0));
if (NS_WARN_IF(!array)) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
if (!aKeys->IsEmpty()) {
const uint32_t count = aKeys->Length();
if (NS_WARN_IF(!JS_SetArrayLength(aCx, array, count))) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
for (uint32_t index = 0; index < count; index++) {
const Key& key = aKeys->ElementAt(index);
MOZ_ASSERT(!key.IsUnset());
JS::Rooted<JS::Value> value(aCx);
nsresult rv = GetResult(aCx, &key, &value);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (NS_WARN_IF(!JS_DefineElement(aCx, array, index, value,
JSPROP_ENUMERATE))) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
}
}
aResult.setObject(*array);
return NS_OK;
}
};
class PermissionRequestMainProcessHelper final
: public PermissionRequestBase
{
BackgroundFactoryRequestChild* mActor;
RefPtr<IDBFactory> mFactory;
public:
PermissionRequestMainProcessHelper(BackgroundFactoryRequestChild* aActor,
IDBFactory* aFactory,
Element* aOwnerElement,
nsIPrincipal* aPrincipal)
: PermissionRequestBase(aOwnerElement, aPrincipal)
, mActor(aActor)
, mFactory(aFactory)
{
MOZ_ASSERT(aActor);
MOZ_ASSERT(aFactory);
aActor->AssertIsOnOwningThread();
}
protected:
~PermissionRequestMainProcessHelper()
{ }
private:
virtual void
OnPromptComplete(PermissionValue aPermissionValue) override;
};
class PermissionRequestChildProcessActor final
: public PIndexedDBPermissionRequestChild
{
BackgroundFactoryRequestChild* mActor;
RefPtr<IDBFactory> mFactory;
public:
PermissionRequestChildProcessActor(BackgroundFactoryRequestChild* aActor,
IDBFactory* aFactory)
: mActor(aActor)
, mFactory(aFactory)
{
MOZ_ASSERT(aActor);
MOZ_ASSERT(aFactory);
aActor->AssertIsOnOwningThread();
}
protected:
~PermissionRequestChildProcessActor()
{ }
virtual bool
Recv__delete__(const uint32_t& aPermission) override;
};
void
ConvertActorsToBlobs(IDBDatabase* aDatabase,
const SerializedStructuredCloneReadInfo& aCloneReadInfo,
nsTArray<StructuredCloneFile>& aFiles)
{
MOZ_ASSERT(aFiles.IsEmpty());
const nsTArray<BlobOrMutableFile>& blobs = aCloneReadInfo.blobs();
if (!blobs.IsEmpty()) {
const uint32_t count = blobs.Length();
aFiles.SetCapacity(count);
for (uint32_t index = 0; index < count; index++) {
const BlobOrMutableFile& blobOrMutableFile = blobs[index];
switch (blobOrMutableFile.type()) {
case BlobOrMutableFile::TPBlobChild: {
auto* actor =
static_cast<BlobChild*>(blobOrMutableFile.get_PBlobChild());
RefPtr<BlobImpl> blobImpl = actor->GetBlobImpl();
MOZ_ASSERT(blobImpl);
RefPtr<Blob> blob = Blob::Create(aDatabase->GetOwner(), blobImpl);
aDatabase->NoteReceivedBlob(blob);
StructuredCloneFile* file = aFiles.AppendElement();
MOZ_ASSERT(file);
file->mMutable = false;
file->mBlob.swap(blob);
break;
}
case BlobOrMutableFile::TNullableMutableFile: {
const NullableMutableFile& nullableMutableFile =
blobOrMutableFile.get_NullableMutableFile();
switch (nullableMutableFile.type()) {
case NullableMutableFile::Tnull_t: {
StructuredCloneFile* file = aFiles.AppendElement();
MOZ_ASSERT(file);
file->mMutable = true;
break;
}
case NullableMutableFile::TPBackgroundMutableFileChild: {
auto* actor =
static_cast<BackgroundMutableFileChild*>(
nullableMutableFile.get_PBackgroundMutableFileChild());
MOZ_ASSERT(actor);
actor->EnsureDOMObject();
auto* mutableFile =
static_cast<IDBMutableFile*>(actor->GetDOMObject());
MOZ_ASSERT(mutableFile);
StructuredCloneFile* file = aFiles.AppendElement();
MOZ_ASSERT(file);
file->mMutable = true;
file->mMutableFile = mutableFile;
actor->ReleaseDOMObject();
break;
}
default:
MOZ_CRASH("Should never get here!");
}
break;
}
default:
MOZ_CRASH("Should never get here!");
}
}
}
}
void
DispatchErrorEvent(IDBRequest* aRequest,
nsresult aErrorCode,
IDBTransaction* aTransaction = nullptr,
nsIDOMEvent* aEvent = nullptr)
{
MOZ_ASSERT(aRequest);
aRequest->AssertIsOnOwningThread();
MOZ_ASSERT(NS_FAILED(aErrorCode));
MOZ_ASSERT(NS_ERROR_GET_MODULE(aErrorCode) == NS_ERROR_MODULE_DOM_INDEXEDDB);
PROFILER_LABEL("IndexedDB",
"DispatchErrorEvent",
js::ProfileEntry::Category::STORAGE);
RefPtr<IDBRequest> request = aRequest;
RefPtr<IDBTransaction> transaction = aTransaction;
request->SetError(aErrorCode);
nsCOMPtr<nsIDOMEvent> errorEvent;
if (!aEvent) {
// Make an error event and fire it at the target.
errorEvent = CreateGenericEvent(request,
nsDependentString(kErrorEventType),
eDoesBubble,
eCancelable);
MOZ_ASSERT(errorEvent);
aEvent = errorEvent;
}
Maybe<AutoSetCurrentTransaction> asct;
if (aTransaction) {
asct.emplace(aTransaction);
}
if (transaction) {
IDB_LOG_MARK("IndexedDB %s: Child Transaction[%lld] Request[%llu]: "
"Firing %s event with error 0x%x",
"IndexedDB %s: C T[%lld] R[%llu]: %s (0x%x)",
IDB_LOG_ID_STRING(),
transaction->LoggingSerialNumber(),
request->LoggingSerialNumber(),
IDB_LOG_STRINGIFY(aEvent, kErrorEventType),
aErrorCode);
} else {
IDB_LOG_MARK("IndexedDB %s: Child Request[%llu]: "
"Firing %s event with error 0x%x",
"IndexedDB %s: C R[%llu]: %s (0x%x)",
IDB_LOG_ID_STRING(),
request->LoggingSerialNumber(),
IDB_LOG_STRINGIFY(aEvent, kErrorEventType),
aErrorCode);
}
bool doDefault;
nsresult rv = request->DispatchEvent(aEvent, &doDefault);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
MOZ_ASSERT(!transaction || transaction->IsOpen() || transaction->IsAborted());
if (transaction && transaction->IsOpen()) {
WidgetEvent* internalEvent = aEvent->GetInternalNSEvent();
MOZ_ASSERT(internalEvent);
if (internalEvent->mFlags.mExceptionHasBeenRisen) {
transaction->Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
} else if (doDefault) {
transaction->Abort(request);
}
}
}
void
DispatchSuccessEvent(ResultHelper* aResultHelper,
nsIDOMEvent* aEvent = nullptr)
{
MOZ_ASSERT(aResultHelper);
PROFILER_LABEL("IndexedDB",
"DispatchSuccessEvent",
js::ProfileEntry::Category::STORAGE);
RefPtr<IDBRequest> request = aResultHelper->Request();
MOZ_ASSERT(request);
request->AssertIsOnOwningThread();
RefPtr<IDBTransaction> transaction = aResultHelper->Transaction();
if (transaction && transaction->IsAborted()) {
DispatchErrorEvent(request, transaction->AbortCode(), transaction);
return;
}
nsCOMPtr<nsIDOMEvent> successEvent;
if (!aEvent) {
successEvent = CreateGenericEvent(request,
nsDependentString(kSuccessEventType),
eDoesNotBubble,
eNotCancelable);
MOZ_ASSERT(successEvent);
aEvent = successEvent;
}
request->SetResultCallback(aResultHelper);
MOZ_ASSERT(aEvent);
MOZ_ASSERT_IF(transaction, transaction->IsOpen());
if (transaction) {
IDB_LOG_MARK("IndexedDB %s: Child Transaction[%lld] Request[%llu]: "
"Firing %s event",
"IndexedDB %s: C T[%lld] R[%llu]: %s",
IDB_LOG_ID_STRING(),
transaction->LoggingSerialNumber(),
request->LoggingSerialNumber(),
IDB_LOG_STRINGIFY(aEvent, kSuccessEventType));
} else {
IDB_LOG_MARK("IndexedDB %s: Child Request[%llu]: Firing %s event",
"IndexedDB %s: C R[%llu]: %s",
IDB_LOG_ID_STRING(),
request->LoggingSerialNumber(),
IDB_LOG_STRINGIFY(aEvent, kSuccessEventType));
}
bool dummy;
nsresult rv = request->DispatchEvent(aEvent, &dummy);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
MOZ_ASSERT_IF(transaction,
transaction->IsOpen() || transaction->IsAborted());
WidgetEvent* internalEvent = aEvent->GetInternalNSEvent();
MOZ_ASSERT(internalEvent);
if (transaction &&
transaction->IsOpen() &&
internalEvent->mFlags.mExceptionHasBeenRisen) {
transaction->Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
}
}
class WorkerPermissionChallenge;
// This class calles WorkerPermissionChallenge::OperationCompleted() in the
// worker thread.
class WorkerPermissionOperationCompleted final : public WorkerControlRunnable
{
RefPtr<WorkerPermissionChallenge> mChallenge;
public:
WorkerPermissionOperationCompleted(WorkerPrivate* aWorkerPrivate,
WorkerPermissionChallenge* aChallenge)
: WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
, mChallenge(aChallenge)
{
MOZ_ASSERT(NS_IsMainThread());
}
virtual bool
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
};
// This class used to do prompting in the main thread and main process.
class WorkerPermissionRequest final : public PermissionRequestBase
{
RefPtr<WorkerPermissionChallenge> mChallenge;
public:
WorkerPermissionRequest(Element* aElement,
nsIPrincipal* aPrincipal,
WorkerPermissionChallenge* aChallenge)
: PermissionRequestBase(aElement, aPrincipal)
, mChallenge(aChallenge)
{
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aChallenge);
}
private:
~WorkerPermissionRequest()
{
MOZ_ASSERT(NS_IsMainThread());
}
virtual void
OnPromptComplete(PermissionValue aPermissionValue) override;
};
// This class is used in the main thread of all child processes.
class WorkerPermissionRequestChildProcessActor final
: public PIndexedDBPermissionRequestChild
{
RefPtr<WorkerPermissionChallenge> mChallenge;
public:
explicit WorkerPermissionRequestChildProcessActor(
WorkerPermissionChallenge* aChallenge)
: mChallenge(aChallenge)
{
MOZ_ASSERT(!XRE_IsParentProcess());
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aChallenge);
}
protected:
~WorkerPermissionRequestChildProcessActor()
{}
virtual bool
Recv__delete__(const uint32_t& aPermission) override;
};
class WorkerPermissionChallenge final : public nsRunnable
, public WorkerFeature
{
public:
WorkerPermissionChallenge(WorkerPrivate* aWorkerPrivate,
BackgroundFactoryRequestChild* aActor,
IDBFactory* aFactory,
const PrincipalInfo& aPrincipalInfo)
: mWorkerPrivate(aWorkerPrivate)
, mActor(aActor)
, mFactory(aFactory)
, mPrincipalInfo(aPrincipalInfo)
{
MOZ_ASSERT(mWorkerPrivate);
MOZ_ASSERT(aActor);
MOZ_ASSERT(aFactory);
mWorkerPrivate->AssertIsOnWorkerThread();
}
NS_IMETHOD
Run() override
{
bool completed = RunInternal();
if (completed) {
OperationCompleted();
}
return NS_OK;
}
virtual bool
Notify(JSContext* aCx, workers::Status aStatus) override
{
// We don't care about the notification. We just want to keep the
// mWorkerPrivate alive.
return true;
}
void
OperationCompleted()
{
if (NS_IsMainThread()) {
RefPtr<WorkerPermissionOperationCompleted> runnable =
new WorkerPermissionOperationCompleted(mWorkerPrivate, this);
MOZ_ALWAYS_TRUE(runnable->Dispatch(nullptr));
return;
}
MOZ_ASSERT(mActor);
mActor->AssertIsOnOwningThread();
MaybeCollectGarbageOnIPCMessage();
RefPtr<IDBFactory> factory;
mFactory.swap(factory);
mActor->SendPermissionRetry();
mActor = nullptr;
mWorkerPrivate->AssertIsOnWorkerThread();
JSContext* cx = mWorkerPrivate->GetJSContext();
mWorkerPrivate->RemoveFeature(cx, this);
}
private:
bool
RunInternal()
{
MOZ_ASSERT(NS_IsMainThread());
// Walk up to our containing page
WorkerPrivate* wp = mWorkerPrivate;
while (wp->GetParent()) {
wp = wp->GetParent();
}
nsPIDOMWindow* window = wp->GetWindow();
if (!window) {
return true;
}
nsresult rv;
nsCOMPtr<nsIPrincipal> principal =
mozilla::ipc::PrincipalInfoToPrincipal(mPrincipalInfo, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return true;
}
if (XRE_IsParentProcess()) {
nsCOMPtr<Element> ownerElement =
do_QueryInterface(window->GetChromeEventHandler());
if (NS_WARN_IF(!ownerElement)) {
return true;
}
RefPtr<WorkerPermissionRequest> helper =
new WorkerPermissionRequest(ownerElement, principal, this);
PermissionRequestBase::PermissionValue permission;
if (NS_WARN_IF(NS_FAILED(helper->PromptIfNeeded(&permission)))) {
return true;
}
MOZ_ASSERT(permission == PermissionRequestBase::kPermissionAllowed ||
permission == PermissionRequestBase::kPermissionDenied ||
permission == PermissionRequestBase::kPermissionPrompt);
return permission != PermissionRequestBase::kPermissionPrompt;
}
TabChild* tabChild = TabChild::GetFrom(window);
MOZ_ASSERT(tabChild);
IPC::Principal ipcPrincipal(principal);
auto* actor = new WorkerPermissionRequestChildProcessActor(this);
tabChild->SendPIndexedDBPermissionRequestConstructor(actor, ipcPrincipal);
return false;
}
private:
WorkerPrivate* mWorkerPrivate;
BackgroundFactoryRequestChild* mActor;
RefPtr<IDBFactory> mFactory;
PrincipalInfo mPrincipalInfo;
};
void
WorkerPermissionRequest::OnPromptComplete(PermissionValue aPermissionValue)
{
MOZ_ASSERT(NS_IsMainThread());
mChallenge->OperationCompleted();
}
bool
WorkerPermissionOperationCompleted::WorkerRun(JSContext* aCx,
WorkerPrivate* aWorkerPrivate)
{
aWorkerPrivate->AssertIsOnWorkerThread();
mChallenge->OperationCompleted();
return true;
}
bool
WorkerPermissionRequestChildProcessActor::Recv__delete__(
const uint32_t& /* aPermission */)
{
MOZ_ASSERT(NS_IsMainThread());
mChallenge->OperationCompleted();
return true;
}
} // namespace
/*******************************************************************************
* Local class implementations
******************************************************************************/
void
PermissionRequestMainProcessHelper::OnPromptComplete(
PermissionValue aPermissionValue)
{
MOZ_ASSERT(mActor);
mActor->AssertIsOnOwningThread();
MaybeCollectGarbageOnIPCMessage();
mActor->SendPermissionRetry();
mActor = nullptr;
mFactory = nullptr;
}
bool
PermissionRequestChildProcessActor::Recv__delete__(
const uint32_t& /* aPermission */)
{
MOZ_ASSERT(mActor);
mActor->AssertIsOnOwningThread();
MOZ_ASSERT(mFactory);
MaybeCollectGarbageOnIPCMessage();
RefPtr<IDBFactory> factory;
mFactory.swap(factory);
mActor->SendPermissionRetry();
mActor = nullptr;
return true;
}
/*******************************************************************************
* BackgroundRequestChildBase
******************************************************************************/
BackgroundRequestChildBase::BackgroundRequestChildBase(IDBRequest* aRequest)
: mRequest(aRequest)
{
MOZ_ASSERT(aRequest);
aRequest->AssertIsOnOwningThread();
MOZ_COUNT_CTOR(indexedDB::BackgroundRequestChildBase);
}
BackgroundRequestChildBase::~BackgroundRequestChildBase()
{
AssertIsOnOwningThread();
MOZ_COUNT_DTOR(indexedDB::BackgroundRequestChildBase);
}
#ifdef DEBUG
void
BackgroundRequestChildBase::AssertIsOnOwningThread() const
{
MOZ_ASSERT(mRequest);
mRequest->AssertIsOnOwningThread();
}
#endif // DEBUG
/*******************************************************************************
* BackgroundFactoryChild
******************************************************************************/
BackgroundFactoryChild::BackgroundFactoryChild(IDBFactory* aFactory)
: mFactory(aFactory)
#ifdef DEBUG
, mOwningThread(NS_GetCurrentThread())
#endif
{
AssertIsOnOwningThread();
MOZ_ASSERT(aFactory);
aFactory->AssertIsOnOwningThread();
MOZ_COUNT_CTOR(indexedDB::BackgroundFactoryChild);
}
BackgroundFactoryChild::~BackgroundFactoryChild()
{
MOZ_COUNT_DTOR(indexedDB::BackgroundFactoryChild);
}
#ifdef DEBUG
void
BackgroundFactoryChild::AssertIsOnOwningThread() const
{
MOZ_ASSERT(mOwningThread);
bool current;
MOZ_ASSERT(NS_SUCCEEDED(mOwningThread->IsOnCurrentThread(&current)));
MOZ_ASSERT(current);
}
nsIEventTarget*
BackgroundFactoryChild::OwningThread() const
{
MOZ_ASSERT(mOwningThread);
return mOwningThread;
}
#endif // DEBUG
void
BackgroundFactoryChild::SendDeleteMeInternal()
{
AssertIsOnOwningThread();
if (mFactory) {
mFactory->ClearBackgroundActor();
mFactory = nullptr;
MOZ_ALWAYS_TRUE(PBackgroundIDBFactoryChild::SendDeleteMe());
}
}
void
BackgroundFactoryChild::ActorDestroy(ActorDestroyReason aWhy)
{
AssertIsOnOwningThread();
MaybeCollectGarbageOnIPCMessage();
if (mFactory) {
mFactory->ClearBackgroundActor();
#ifdef DEBUG
mFactory = nullptr;
#endif
}
}
PBackgroundIDBFactoryRequestChild*
BackgroundFactoryChild::AllocPBackgroundIDBFactoryRequestChild(
const FactoryRequestParams& aParams)
{
MOZ_CRASH("PBackgroundIDBFactoryRequestChild actors should be manually "
"constructed!");
}
bool
BackgroundFactoryChild::DeallocPBackgroundIDBFactoryRequestChild(
PBackgroundIDBFactoryRequestChild* aActor)
{
MOZ_ASSERT(aActor);
delete static_cast<BackgroundFactoryRequestChild*>(aActor);
return true;
}
PBackgroundIDBDatabaseChild*
BackgroundFactoryChild::AllocPBackgroundIDBDatabaseChild(
const DatabaseSpec& aSpec,
PBackgroundIDBFactoryRequestChild* aRequest)
{
AssertIsOnOwningThread();
auto request = static_cast<BackgroundFactoryRequestChild*>(aRequest);
MOZ_ASSERT(request);
return new BackgroundDatabaseChild(aSpec, request);
}
bool
BackgroundFactoryChild::DeallocPBackgroundIDBDatabaseChild(
PBackgroundIDBDatabaseChild* aActor)
{
MOZ_ASSERT(aActor);
delete static_cast<BackgroundDatabaseChild*>(aActor);
return true;
}
/*******************************************************************************
* BackgroundFactoryRequestChild
******************************************************************************/
BackgroundFactoryRequestChild::BackgroundFactoryRequestChild(
IDBFactory* aFactory,
IDBOpenDBRequest* aOpenRequest,
bool aIsDeleteOp,
uint64_t aRequestedVersion)
: BackgroundRequestChildBase(aOpenRequest)
, mFactory(aFactory)
, mRequestedVersion(aRequestedVersion)
, mIsDeleteOp(aIsDeleteOp)
{
// Can't assert owning thread here because IPDL has not yet set our manager!
MOZ_ASSERT(aFactory);
aFactory->AssertIsOnOwningThread();
MOZ_ASSERT(aOpenRequest);
MOZ_COUNT_CTOR(indexedDB::BackgroundFactoryRequestChild);
}
BackgroundFactoryRequestChild::~BackgroundFactoryRequestChild()
{
MOZ_COUNT_DTOR(indexedDB::BackgroundFactoryRequestChild);
}
IDBOpenDBRequest*
BackgroundFactoryRequestChild::GetOpenDBRequest() const
{
AssertIsOnOwningThread();
return static_cast<IDBOpenDBRequest*>(mRequest.get());
}
bool
BackgroundFactoryRequestChild::HandleResponse(nsresult aResponse)
{
AssertIsOnOwningThread();
MOZ_ASSERT(NS_FAILED(aResponse));
MOZ_ASSERT(NS_ERROR_GET_MODULE(aResponse) == NS_ERROR_MODULE_DOM_INDEXEDDB);
mRequest->Reset();
DispatchErrorEvent(mRequest, aResponse);
return true;
}
bool
BackgroundFactoryRequestChild::HandleResponse(
const OpenDatabaseRequestResponse& aResponse)
{
AssertIsOnOwningThread();
mRequest->Reset();
auto databaseActor =
static_cast<BackgroundDatabaseChild*>(aResponse.databaseChild());
MOZ_ASSERT(databaseActor);
IDBDatabase* database = databaseActor->GetDOMObject();
if (!database) {
databaseActor->EnsureDOMObject();
database = databaseActor->GetDOMObject();
MOZ_ASSERT(database);
MOZ_ASSERT(!database->IsClosed());
}
if (database->IsClosed()) {
// If the database was closed already, which is only possible if we fired an
// "upgradeneeded" event, then we shouldn't fire a "success" event here.
// Instead we fire an error event with AbortErr.
DispatchErrorEvent(mRequest, NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
} else {
ResultHelper helper(mRequest, nullptr, database);
DispatchSuccessEvent(&helper);
}
databaseActor->ReleaseDOMObject();
return true;
}
bool
BackgroundFactoryRequestChild::HandleResponse(
const DeleteDatabaseRequestResponse& aResponse)
{
AssertIsOnOwningThread();
ResultHelper helper(mRequest, nullptr, &JS::UndefinedHandleValue);
nsCOMPtr<nsIDOMEvent> successEvent =
IDBVersionChangeEvent::Create(mRequest,
nsDependentString(kSuccessEventType),
aResponse.previousVersion());
MOZ_ASSERT(successEvent);
DispatchSuccessEvent(&helper, successEvent);
return true;
}
void
BackgroundFactoryRequestChild::ActorDestroy(ActorDestroyReason aWhy)
{
AssertIsOnOwningThread();
MaybeCollectGarbageOnIPCMessage();
if (aWhy != Deletion) {
IDBOpenDBRequest* openRequest = GetOpenDBRequest();
if (openRequest) {
openRequest->NoteComplete();
}
}
}
bool
BackgroundFactoryRequestChild::Recv__delete__(
const FactoryRequestResponse& aResponse)
{
AssertIsOnOwningThread();
MOZ_ASSERT(mRequest);
MaybeCollectGarbageOnIPCMessage();
bool result;
switch (aResponse.type()) {
case FactoryRequestResponse::Tnsresult:
result = HandleResponse(aResponse.get_nsresult());
break;
case FactoryRequestResponse::TOpenDatabaseRequestResponse:
result = HandleResponse(aResponse.get_OpenDatabaseRequestResponse());
break;
case FactoryRequestResponse::TDeleteDatabaseRequestResponse:
result = HandleResponse(aResponse.get_DeleteDatabaseRequestResponse());
break;
default:
MOZ_CRASH("Unknown response type!");
}
IDBOpenDBRequest* request = GetOpenDBRequest();
MOZ_ASSERT(request);
request->NoteComplete();
if (NS_WARN_IF(!result)) {
return false;
}
return true;
}
bool
BackgroundFactoryRequestChild::RecvPermissionChallenge(
const PrincipalInfo& aPrincipalInfo)
{
AssertIsOnOwningThread();
MaybeCollectGarbageOnIPCMessage();
if (!NS_IsMainThread()) {
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(workerPrivate);
workerPrivate->AssertIsOnWorkerThread();
RefPtr<WorkerPermissionChallenge> challenge =
new WorkerPermissionChallenge(workerPrivate, this, mFactory,
aPrincipalInfo);
JSContext* cx = workerPrivate->GetJSContext();
MOZ_ASSERT(cx);
if (NS_WARN_IF(!workerPrivate->AddFeature(cx, challenge))) {
return false;
}
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(challenge)));
return true;
}
nsresult rv;
nsCOMPtr<nsIPrincipal> principal =
mozilla::ipc::PrincipalInfoToPrincipal(aPrincipalInfo, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
if (XRE_IsParentProcess()) {
nsCOMPtr<nsPIDOMWindow> window = mFactory->GetParentObject();
MOZ_ASSERT(window);
nsCOMPtr<Element> ownerElement =
do_QueryInterface(window->GetChromeEventHandler());
if (NS_WARN_IF(!ownerElement)) {
// If this fails, the page was navigated. Fail the permission check by
// forcing an immediate retry.
return SendPermissionRetry();
}
RefPtr<PermissionRequestMainProcessHelper> helper =
new PermissionRequestMainProcessHelper(this, mFactory, ownerElement, principal);
PermissionRequestBase::PermissionValue permission;
if (NS_WARN_IF(NS_FAILED(helper->PromptIfNeeded(&permission)))) {
return false;
}
MOZ_ASSERT(permission == PermissionRequestBase::kPermissionAllowed ||
permission == PermissionRequestBase::kPermissionDenied ||
permission == PermissionRequestBase::kPermissionPrompt);
if (permission != PermissionRequestBase::kPermissionPrompt) {
SendPermissionRetry();
}
return true;
}
RefPtr<TabChild> tabChild = mFactory->GetTabChild();
MOZ_ASSERT(tabChild);
IPC::Principal ipcPrincipal(principal);
auto* actor = new PermissionRequestChildProcessActor(this, mFactory);
tabChild->SendPIndexedDBPermissionRequestConstructor(actor, ipcPrincipal);
return true;
}
bool
BackgroundFactoryRequestChild::RecvBlocked(const uint64_t& aCurrentVersion)
{
AssertIsOnOwningThread();
MOZ_ASSERT(mRequest);
MaybeCollectGarbageOnIPCMessage();
const nsDependentString type(kBlockedEventType);
nsCOMPtr<nsIDOMEvent> blockedEvent;
if (mIsDeleteOp) {
blockedEvent =
IDBVersionChangeEvent::Create(mRequest, type, aCurrentVersion);
MOZ_ASSERT(blockedEvent);
} else {
blockedEvent =
IDBVersionChangeEvent::Create(mRequest,
type,
aCurrentVersion,
mRequestedVersion);
MOZ_ASSERT(blockedEvent);
}
RefPtr<IDBRequest> kungFuDeathGrip = mRequest;
IDB_LOG_MARK("IndexedDB %s: Child Request[%llu]: Firing \"blocked\" event",
"IndexedDB %s: C R[%llu]: \"blocked\"",
IDB_LOG_ID_STRING(),
kungFuDeathGrip->LoggingSerialNumber());
bool dummy;
if (NS_FAILED(kungFuDeathGrip->DispatchEvent(blockedEvent, &dummy))) {
NS_WARNING("Failed to dispatch event!");
}
return true;
}
/*******************************************************************************
* BackgroundDatabaseChild
******************************************************************************/
BackgroundDatabaseChild::BackgroundDatabaseChild(
const DatabaseSpec& aSpec,
BackgroundFactoryRequestChild* aOpenRequestActor)
: mSpec(new DatabaseSpec(aSpec))
, mOpenRequestActor(aOpenRequestActor)
, mDatabase(nullptr)
{
// Can't assert owning thread here because IPDL has not yet set our manager!
MOZ_ASSERT(aOpenRequestActor);
MOZ_COUNT_CTOR(indexedDB::BackgroundDatabaseChild);
}
BackgroundDatabaseChild::~BackgroundDatabaseChild()
{
MOZ_COUNT_DTOR(indexedDB::BackgroundDatabaseChild);
}
void
BackgroundDatabaseChild::SendDeleteMeInternal()
{
AssertIsOnOwningThread();
MOZ_ASSERT(!mTemporaryStrongDatabase);
MOZ_ASSERT(!mOpenRequestActor);
if (mDatabase) {
mDatabase->ClearBackgroundActor();
mDatabase = nullptr;
MOZ_ALWAYS_TRUE(PBackgroundIDBDatabaseChild::SendDeleteMe());
}
}
void
BackgroundDatabaseChild::EnsureDOMObject()
{
AssertIsOnOwningThread();
MOZ_ASSERT(mOpenRequestActor);
if (mTemporaryStrongDatabase) {
MOZ_ASSERT(!mSpec);
return;
}
MOZ_ASSERT(mSpec);
auto request = mOpenRequestActor->GetOpenDBRequest();
MOZ_ASSERT(request);
auto factory =
static_cast<BackgroundFactoryChild*>(Manager())->GetDOMObject();
MOZ_ASSERT(factory);
mTemporaryStrongDatabase =
IDBDatabase::Create(request, factory, this, mSpec);
MOZ_ASSERT(mTemporaryStrongDatabase);
mTemporaryStrongDatabase->AssertIsOnOwningThread();
mDatabase = mTemporaryStrongDatabase;
mSpec.forget();
}
void
BackgroundDatabaseChild::ReleaseDOMObject()
{
AssertIsOnOwningThread();
MOZ_ASSERT(mTemporaryStrongDatabase);
mTemporaryStrongDatabase->AssertIsOnOwningThread();
MOZ_ASSERT(mOpenRequestActor);
MOZ_ASSERT(mDatabase == mTemporaryStrongDatabase);
mOpenRequestActor = nullptr;
// This may be the final reference to the IDBDatabase object so we may end up
// calling SendDeleteMeInternal() here. Make sure everything is cleaned up
// properly before proceeding.
mTemporaryStrongDatabase = nullptr;
}
void
BackgroundDatabaseChild::ActorDestroy(ActorDestroyReason aWhy)
{
AssertIsOnOwningThread();
MaybeCollectGarbageOnIPCMessage();
if (mDatabase) {
mDatabase->ClearBackgroundActor();
#ifdef DEBUG
mDatabase = nullptr;
#endif
}
}
PBackgroundIDBDatabaseFileChild*
BackgroundDatabaseChild::AllocPBackgroundIDBDatabaseFileChild(
PBlobChild* aBlobChild)
{
MOZ_CRASH("PBackgroundIDBFileChild actors should be manually constructed!");
}
bool
BackgroundDatabaseChild::DeallocPBackgroundIDBDatabaseFileChild(
PBackgroundIDBDatabaseFileChild* aActor)
{
AssertIsOnOwningThread();
MOZ_ASSERT(aActor);
delete aActor;
return true;
}
PBackgroundIDBDatabaseRequestChild*
BackgroundDatabaseChild::AllocPBackgroundIDBDatabaseRequestChild(
const DatabaseRequestParams& aParams)
{
MOZ_CRASH("PBackgroundIDBDatabaseRequestChild actors should be manually "
"constructed!");
}
bool
BackgroundDatabaseChild::DeallocPBackgroundIDBDatabaseRequestChild(
PBackgroundIDBDatabaseRequestChild* aActor)
{
MOZ_ASSERT(aActor);
delete static_cast<BackgroundDatabaseRequestChild*>(aActor);
return true;
}
PBackgroundIDBTransactionChild*
BackgroundDatabaseChild::AllocPBackgroundIDBTransactionChild(
const nsTArray<nsString>& aObjectStoreNames,
const Mode& aMode)
{
MOZ_CRASH("PBackgroundIDBTransactionChild actors should be manually "
"constructed!");
}
bool
BackgroundDatabaseChild::DeallocPBackgroundIDBTransactionChild(
PBackgroundIDBTransactionChild* aActor)
{
MOZ_ASSERT(aActor);
delete static_cast<BackgroundTransactionChild*>(aActor);
return true;
}
PBackgroundIDBVersionChangeTransactionChild*
BackgroundDatabaseChild::AllocPBackgroundIDBVersionChangeTransactionChild(
const uint64_t& aCurrentVersion,
const uint64_t& aRequestedVersion,
const int64_t& aNextObjectStoreId,
const int64_t& aNextIndexId)
{
AssertIsOnOwningThread();
IDBOpenDBRequest* request = mOpenRequestActor->GetOpenDBRequest();
MOZ_ASSERT(request);
return new BackgroundVersionChangeTransactionChild(request);
}
bool
BackgroundDatabaseChild::RecvPBackgroundIDBVersionChangeTransactionConstructor(
PBackgroundIDBVersionChangeTransactionChild* aActor,
const uint64_t& aCurrentVersion,
const uint64_t& aRequestedVersion,
const int64_t& aNextObjectStoreId,
const int64_t& aNextIndexId)
{
AssertIsOnOwningThread();
MOZ_ASSERT(aActor);
MOZ_ASSERT(mOpenRequestActor);
MaybeCollectGarbageOnIPCMessage();
EnsureDOMObject();
auto* actor = static_cast<BackgroundVersionChangeTransactionChild*>(aActor);
RefPtr<IDBOpenDBRequest> request = mOpenRequestActor->GetOpenDBRequest();
MOZ_ASSERT(request);
RefPtr<IDBTransaction> transaction =
IDBTransaction::CreateVersionChange(mDatabase,
actor,
request,
aNextObjectStoreId,
aNextIndexId);
if (NS_WARN_IF(!transaction)) {
// This can happen if we receive events after a worker has begun its
// shutdown process.
MOZ_ASSERT(!NS_IsMainThread());
// Report this to the console.
IDB_REPORT_INTERNAL_ERR();
MOZ_ALWAYS_TRUE(aActor->SendDeleteMe());
return true;
}
transaction->AssertIsOnOwningThread();
actor->SetDOMTransaction(transaction);
mDatabase->EnterSetVersionTransaction(aRequestedVersion);
request->SetTransaction(transaction);
nsCOMPtr<nsIDOMEvent> upgradeNeededEvent =
IDBVersionChangeEvent::Create(request,
nsDependentString(kUpgradeNeededEventType),
aCurrentVersion,
aRequestedVersion);
MOZ_ASSERT(upgradeNeededEvent);
ResultHelper helper(request, transaction, mDatabase);
DispatchSuccessEvent(&helper, upgradeNeededEvent);
return true;
}
bool
BackgroundDatabaseChild::DeallocPBackgroundIDBVersionChangeTransactionChild(
PBackgroundIDBVersionChangeTransactionChild* aActor)
{
MOZ_ASSERT(aActor);
delete static_cast<BackgroundVersionChangeTransactionChild*>(aActor);
return true;
}
PBackgroundMutableFileChild*
BackgroundDatabaseChild::AllocPBackgroundMutableFileChild(const nsString& aName,
const nsString& aType)
{
AssertIsOnOwningThread();
#ifdef DEBUG
nsCOMPtr<nsIThread> owningThread = do_QueryInterface(OwningThread());
PRThread* owningPRThread;
owningThread->GetPRThread(&owningPRThread);
#endif
return new BackgroundMutableFileChild(DEBUGONLY(owningPRThread,)
aName,
aType);
}
bool
BackgroundDatabaseChild::DeallocPBackgroundMutableFileChild(
PBackgroundMutableFileChild* aActor)
{
MOZ_ASSERT(aActor);
delete static_cast<BackgroundMutableFileChild*>(aActor);
return true;
}
bool
BackgroundDatabaseChild::RecvVersionChange(const uint64_t& aOldVersion,
const NullableVersion& aNewVersion)
{
AssertIsOnOwningThread();
MaybeCollectGarbageOnIPCMessage();
if (!mDatabase || mDatabase->IsClosed()) {
return true;
}
RefPtr<IDBDatabase> kungFuDeathGrip = mDatabase;
// Handle bfcache'd windows.
if (nsPIDOMWindow* owner = kungFuDeathGrip->GetOwner()) {
// The database must be closed if the window is already frozen.
bool shouldAbortAndClose = owner->IsFrozen();
// Anything in the bfcache has to be evicted and then we have to close the
// database also.
if (nsCOMPtr<nsIDocument> doc = owner->GetExtantDoc()) {
if (nsCOMPtr<nsIBFCacheEntry> bfCacheEntry = doc->GetBFCacheEntry()) {
bfCacheEntry->RemoveFromBFCacheSync();
shouldAbortAndClose = true;
}
}
if (shouldAbortAndClose) {
// Invalidate() doesn't close the database in the parent, so we have
// to call Close() and AbortTransactions() manually.
kungFuDeathGrip->AbortTransactions(/* aShouldWarn */ false);
kungFuDeathGrip->Close();
return true;
}
}
// Otherwise fire a versionchange event.
const nsDependentString type(kVersionChangeEventType);
nsCOMPtr<nsIDOMEvent> versionChangeEvent;
switch (aNewVersion.type()) {
case NullableVersion::Tnull_t:
versionChangeEvent =
IDBVersionChangeEvent::Create(mDatabase, type, aOldVersion);
MOZ_ASSERT(versionChangeEvent);
break;
case NullableVersion::Tuint64_t:
versionChangeEvent =
IDBVersionChangeEvent::Create(mDatabase,
type,
aOldVersion,
aNewVersion.get_uint64_t());
MOZ_ASSERT(versionChangeEvent);
break;
default:
MOZ_CRASH("Should never get here!");
}
IDB_LOG_MARK("IndexedDB %s: Child : Firing \"versionchange\" event",
"IndexedDB %s: C: IDBDatabase \"versionchange\" event",
IDB_LOG_ID_STRING());
bool dummy;
if (NS_FAILED(mDatabase->DispatchEvent(versionChangeEvent, &dummy))) {
NS_WARNING("Failed to dispatch event!");
}
if (!mDatabase->IsClosed()) {
SendBlocked();
}
return true;
}
bool
BackgroundDatabaseChild::RecvInvalidate()
{
AssertIsOnOwningThread();
MaybeCollectGarbageOnIPCMessage();
if (mDatabase) {
mDatabase->Invalidate();
}
return true;
}
/*******************************************************************************
* BackgroundDatabaseRequestChild
******************************************************************************/
BackgroundDatabaseRequestChild::BackgroundDatabaseRequestChild(
IDBDatabase* aDatabase,
IDBRequest* aRequest)
: BackgroundRequestChildBase(aRequest)
, mDatabase(aDatabase)
{
// Can't assert owning thread here because IPDL has not yet set our manager!
MOZ_ASSERT(aDatabase);
aDatabase->AssertIsOnOwningThread();
MOZ_ASSERT(aRequest);
MOZ_COUNT_CTOR(indexedDB::BackgroundDatabaseRequestChild);
}
BackgroundDatabaseRequestChild::~BackgroundDatabaseRequestChild()
{
MOZ_COUNT_DTOR(indexedDB::BackgroundDatabaseRequestChild);
}
bool
BackgroundDatabaseRequestChild::HandleResponse(nsresult aResponse)
{
AssertIsOnOwningThread();
MOZ_ASSERT(NS_FAILED(aResponse));
MOZ_ASSERT(NS_ERROR_GET_MODULE(aResponse) == NS_ERROR_MODULE_DOM_INDEXEDDB);
mRequest->Reset();
DispatchErrorEvent(mRequest, aResponse);
return true;
}
bool
BackgroundDatabaseRequestChild::HandleResponse(
const CreateFileRequestResponse& aResponse)
{
AssertIsOnOwningThread();
mRequest->Reset();
auto mutableFileActor =
static_cast<BackgroundMutableFileChild*>(aResponse.mutableFileChild());
MOZ_ASSERT(mutableFileActor);
mutableFileActor->EnsureDOMObject();
auto mutableFile =
static_cast<IDBMutableFile*>(mutableFileActor->GetDOMObject());
MOZ_ASSERT(mutableFile);
ResultHelper helper(mRequest, nullptr, mutableFile);
DispatchSuccessEvent(&helper);
mutableFileActor->ReleaseDOMObject();
return true;
}
bool
BackgroundDatabaseRequestChild::Recv__delete__(
const DatabaseRequestResponse& aResponse)
{
AssertIsOnOwningThread();
MOZ_ASSERT(mRequest);
switch (aResponse.type()) {
case DatabaseRequestResponse::Tnsresult:
return HandleResponse(aResponse.get_nsresult());
case DatabaseRequestResponse::TCreateFileRequestResponse:
return HandleResponse(aResponse.get_CreateFileRequestResponse());
default:
MOZ_CRASH("Unknown response type!");
}
MOZ_CRASH("Should never get here!");
}
/*******************************************************************************
* BackgroundTransactionBase
******************************************************************************/
BackgroundTransactionBase::BackgroundTransactionBase()
: mTransaction(nullptr)
{
MOZ_COUNT_CTOR(indexedDB::BackgroundTransactionBase);
}
BackgroundTransactionBase::BackgroundTransactionBase(
IDBTransaction* aTransaction)
: mTemporaryStrongTransaction(aTransaction)
, mTransaction(aTransaction)
{
MOZ_ASSERT(aTransaction);
aTransaction->AssertIsOnOwningThread();
MOZ_COUNT_CTOR(indexedDB::BackgroundTransactionBase);
}
BackgroundTransactionBase::~BackgroundTransactionBase()
{
MOZ_COUNT_DTOR(indexedDB::BackgroundTransactionBase);
}
#ifdef DEBUG
void
BackgroundTransactionBase::AssertIsOnOwningThread() const
{
MOZ_ASSERT(mTransaction);
mTransaction->AssertIsOnOwningThread();
}
#endif // DEBUG
void
BackgroundTransactionBase::NoteActorDestroyed()
{
AssertIsOnOwningThread();
MOZ_ASSERT_IF(mTemporaryStrongTransaction, mTransaction);
if (mTransaction) {
mTransaction->ClearBackgroundActor();
// Normally this would be DEBUG-only but NoteActorDestroyed is also called
// from SendDeleteMeInternal. In that case we're going to receive an actual
// ActorDestroy call later and we don't want to touch a dead object.
mTemporaryStrongTransaction = nullptr;
mTransaction = nullptr;
}
}
void
BackgroundTransactionBase::SetDOMTransaction(IDBTransaction* aTransaction)
{
AssertIsOnOwningThread();
MOZ_ASSERT(aTransaction);
aTransaction->AssertIsOnOwningThread();
MOZ_ASSERT(!mTemporaryStrongTransaction);
MOZ_ASSERT(!mTransaction);
mTemporaryStrongTransaction = aTransaction;
mTransaction = aTransaction;
}
void
BackgroundTransactionBase::NoteComplete()
{
AssertIsOnOwningThread();
MOZ_ASSERT_IF(mTransaction, mTemporaryStrongTransaction);
mTemporaryStrongTransaction = nullptr;
}
/*******************************************************************************
* BackgroundTransactionChild
******************************************************************************/
BackgroundTransactionChild::BackgroundTransactionChild(
IDBTransaction* aTransaction)
: BackgroundTransactionBase(aTransaction)
{
MOZ_ASSERT(aTransaction);
aTransaction->AssertIsOnOwningThread();
MOZ_COUNT_CTOR(indexedDB::BackgroundTransactionChild);
}
BackgroundTransactionChild::~BackgroundTransactionChild()
{
MOZ_COUNT_DTOR(indexedDB::BackgroundTransactionChild);
}
#ifdef DEBUG
void
BackgroundTransactionChild::AssertIsOnOwningThread() const
{
static_cast<BackgroundDatabaseChild*>(Manager())->AssertIsOnOwningThread();
}
#endif // DEBUG
void
BackgroundTransactionChild::SendDeleteMeInternal()
{
AssertIsOnOwningThread();
if (mTransaction) {
NoteActorDestroyed();
MOZ_ALWAYS_TRUE(PBackgroundIDBTransactionChild::SendDeleteMe());
}
}
void
BackgroundTransactionChild::ActorDestroy(ActorDestroyReason aWhy)
{
AssertIsOnOwningThread();
MaybeCollectGarbageOnIPCMessage();
NoteActorDestroyed();
}
bool
BackgroundTransactionChild::RecvComplete(const nsresult& aResult)
{
AssertIsOnOwningThread();
MOZ_ASSERT(mTransaction);
MaybeCollectGarbageOnIPCMessage();
mTransaction->FireCompleteOrAbortEvents(aResult);
NoteComplete();
return true;
}
PBackgroundIDBRequestChild*
BackgroundTransactionChild::AllocPBackgroundIDBRequestChild(
const RequestParams& aParams)
{
MOZ_CRASH("PBackgroundIDBRequestChild actors should be manually "
"constructed!");
}
bool
BackgroundTransactionChild::DeallocPBackgroundIDBRequestChild(
PBackgroundIDBRequestChild* aActor)
{
MOZ_ASSERT(aActor);
delete static_cast<BackgroundRequestChild*>(aActor);
return true;
}
PBackgroundIDBCursorChild*
BackgroundTransactionChild::AllocPBackgroundIDBCursorChild(
const OpenCursorParams& aParams)
{
AssertIsOnOwningThread();
MOZ_CRASH("PBackgroundIDBCursorChild actors should be manually constructed!");
}
bool
BackgroundTransactionChild::DeallocPBackgroundIDBCursorChild(
PBackgroundIDBCursorChild* aActor)
{
MOZ_ASSERT(aActor);
delete static_cast<BackgroundCursorChild*>(aActor);
return true;
}
/*******************************************************************************
* BackgroundVersionChangeTransactionChild
******************************************************************************/
BackgroundVersionChangeTransactionChild::
BackgroundVersionChangeTransactionChild(IDBOpenDBRequest* aOpenDBRequest)
: mOpenDBRequest(aOpenDBRequest)
{
MOZ_ASSERT(aOpenDBRequest);
aOpenDBRequest->AssertIsOnOwningThread();
MOZ_COUNT_CTOR(indexedDB::BackgroundVersionChangeTransactionChild);
}
BackgroundVersionChangeTransactionChild::
~BackgroundVersionChangeTransactionChild()
{
AssertIsOnOwningThread();
MOZ_COUNT_DTOR(indexedDB::BackgroundVersionChangeTransactionChild);
}
#ifdef DEBUG
void
BackgroundVersionChangeTransactionChild::AssertIsOnOwningThread() const
{
static_cast<BackgroundDatabaseChild*>(Manager())->AssertIsOnOwningThread();
}
#endif // DEBUG
void
BackgroundVersionChangeTransactionChild::SendDeleteMeInternal(
bool aFailedConstructor)
{
AssertIsOnOwningThread();
if (mTransaction || aFailedConstructor) {
NoteActorDestroyed();
MOZ_ALWAYS_TRUE(PBackgroundIDBVersionChangeTransactionChild::
SendDeleteMe());
}
}
void
BackgroundVersionChangeTransactionChild::ActorDestroy(ActorDestroyReason aWhy)
{
AssertIsOnOwningThread();
MaybeCollectGarbageOnIPCMessage();
mOpenDBRequest = nullptr;
NoteActorDestroyed();
}
bool
BackgroundVersionChangeTransactionChild::RecvComplete(const nsresult& aResult)
{
AssertIsOnOwningThread();
MaybeCollectGarbageOnIPCMessage();
if (!mTransaction) {
return true;
}
MOZ_ASSERT(mOpenDBRequest);
IDBDatabase* database = mTransaction->Database();
MOZ_ASSERT(database);
database->ExitSetVersionTransaction();
if (NS_FAILED(aResult)) {
database->Close();
}
RefPtr<IDBOpenDBRequest> request = mOpenDBRequest;
MOZ_ASSERT(request);
mTransaction->FireCompleteOrAbortEvents(aResult);
request->SetTransaction(nullptr);
request = nullptr;
mOpenDBRequest = nullptr;
NoteComplete();
return true;
}
PBackgroundIDBRequestChild*
BackgroundVersionChangeTransactionChild::AllocPBackgroundIDBRequestChild(
const RequestParams& aParams)
{
MOZ_CRASH("PBackgroundIDBRequestChild actors should be manually "
"constructed!");
}
bool
BackgroundVersionChangeTransactionChild::DeallocPBackgroundIDBRequestChild(
PBackgroundIDBRequestChild* aActor)
{
MOZ_ASSERT(aActor);
delete static_cast<BackgroundRequestChild*>(aActor);
return true;
}
PBackgroundIDBCursorChild*
BackgroundVersionChangeTransactionChild::AllocPBackgroundIDBCursorChild(
const OpenCursorParams& aParams)
{
AssertIsOnOwningThread();
MOZ_CRASH("PBackgroundIDBCursorChild actors should be manually constructed!");
}
bool
BackgroundVersionChangeTransactionChild::DeallocPBackgroundIDBCursorChild(
PBackgroundIDBCursorChild* aActor)
{
MOZ_ASSERT(aActor);
delete static_cast<BackgroundCursorChild*>(aActor);
return true;
}
/*******************************************************************************
* BackgroundMutableFileChild
******************************************************************************/
BackgroundMutableFileChild::BackgroundMutableFileChild(
DEBUGONLY(PRThread* aOwningThread,)
const nsAString& aName,
const nsAString& aType)
: BackgroundMutableFileChildBase(DEBUGONLY(aOwningThread))
, mName(aName)
, mType(aType)
{
// Can't assert owning thread here because IPDL has not yet set our manager!
MOZ_COUNT_CTOR(indexedDB::BackgroundMutableFileChild);
}
BackgroundMutableFileChild::~BackgroundMutableFileChild()
{
MOZ_COUNT_DTOR(indexedDB::BackgroundMutableFileChild);
}
already_AddRefed<MutableFileBase>
BackgroundMutableFileChild::CreateMutableFile()
{
auto database =
static_cast<BackgroundDatabaseChild*>(Manager())->GetDOMObject();
MOZ_ASSERT(database);
RefPtr<IDBMutableFile> mutableFile =
new IDBMutableFile(database, this, mName, mType);
return mutableFile.forget();
}
/*******************************************************************************
* BackgroundRequestChild
******************************************************************************/
BackgroundRequestChild::BackgroundRequestChild(IDBRequest* aRequest)
: BackgroundRequestChildBase(aRequest)
, mTransaction(aRequest->GetTransaction())
{
MOZ_ASSERT(mTransaction);
mTransaction->AssertIsOnOwningThread();
MOZ_COUNT_CTOR(indexedDB::BackgroundRequestChild);
}
BackgroundRequestChild::~BackgroundRequestChild()
{
AssertIsOnOwningThread();
MOZ_ASSERT(!mTransaction);
MOZ_COUNT_DTOR(indexedDB::BackgroundRequestChild);
}
void
BackgroundRequestChild::HandleResponse(nsresult aResponse)
{
AssertIsOnOwningThread();
MOZ_ASSERT(NS_FAILED(aResponse));
MOZ_ASSERT(NS_ERROR_GET_MODULE(aResponse) == NS_ERROR_MODULE_DOM_INDEXEDDB);
MOZ_ASSERT(mTransaction);
DispatchErrorEvent(mRequest, aResponse, mTransaction);
}
void
BackgroundRequestChild::HandleResponse(const Key& aResponse)
{
AssertIsOnOwningThread();
ResultHelper helper(mRequest, mTransaction, &aResponse);
DispatchSuccessEvent(&helper);
}
void
BackgroundRequestChild::HandleResponse(const nsTArray<Key>& aResponse)
{
AssertIsOnOwningThread();
ResultHelper helper(mRequest, mTransaction, &aResponse);
DispatchSuccessEvent(&helper);
}
void
BackgroundRequestChild::HandleResponse(
const SerializedStructuredCloneReadInfo& aResponse)
{
AssertIsOnOwningThread();
// XXX Fix this somehow...
auto& serializedCloneInfo =
const_cast<SerializedStructuredCloneReadInfo&>(aResponse);
StructuredCloneReadInfo cloneReadInfo(Move(serializedCloneInfo));
ConvertActorsToBlobs(mTransaction->Database(),
aResponse,
cloneReadInfo.mFiles);
ResultHelper helper(mRequest, mTransaction, &cloneReadInfo);
DispatchSuccessEvent(&helper);
}
void
BackgroundRequestChild::HandleResponse(
const nsTArray<SerializedStructuredCloneReadInfo>& aResponse)
{
AssertIsOnOwningThread();
nsTArray<StructuredCloneReadInfo> cloneReadInfos;
if (!aResponse.IsEmpty()) {
const uint32_t count = aResponse.Length();
cloneReadInfos.SetCapacity(count);
IDBDatabase* database = mTransaction->Database();
for (uint32_t index = 0; index < count; index++) {
// XXX Fix this somehow...
auto& serializedCloneInfo =
const_cast<SerializedStructuredCloneReadInfo&>(aResponse[index]);
StructuredCloneReadInfo* cloneReadInfo = cloneReadInfos.AppendElement();
// Get the files
nsTArray<StructuredCloneFile> files;
ConvertActorsToBlobs(database, serializedCloneInfo, files);
// Move relevant data into the cloneReadInfo
*cloneReadInfo = Move(serializedCloneInfo);
cloneReadInfo->mFiles = Move(files);
}
}
ResultHelper helper(mRequest, mTransaction, &cloneReadInfos);
DispatchSuccessEvent(&helper);
}
void
BackgroundRequestChild::HandleResponse(JS::Handle<JS::Value> aResponse)
{
AssertIsOnOwningThread();
ResultHelper helper(mRequest, mTransaction, &aResponse);
DispatchSuccessEvent(&helper);
}
void
BackgroundRequestChild::HandleResponse(uint64_t aResponse)
{
AssertIsOnOwningThread();
JS::Value response(JS::NumberValue(aResponse));
ResultHelper helper(mRequest, mTransaction, &response);
DispatchSuccessEvent(&helper);
}
void
BackgroundRequestChild::ActorDestroy(ActorDestroyReason aWhy)
{
AssertIsOnOwningThread();
MaybeCollectGarbageOnIPCMessage();
if (mTransaction) {
mTransaction->AssertIsOnOwningThread();
mTransaction->OnRequestFinished(/* aActorDestroyedNormally */
aWhy == Deletion);
#ifdef DEBUG
mTransaction = nullptr;
#endif
}
}
bool
BackgroundRequestChild::Recv__delete__(const RequestResponse& aResponse)
{
AssertIsOnOwningThread();
MOZ_ASSERT(mRequest);
MOZ_ASSERT(mTransaction);
MaybeCollectGarbageOnIPCMessage();
if (mTransaction->IsAborted()) {
// Always fire an "error" event with ABORT_ERR if the transaction was
// aborted, even if the request succeeded or failed with another error.
HandleResponse(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
} else {
switch (aResponse.type()) {
case RequestResponse::Tnsresult:
HandleResponse(aResponse.get_nsresult());
break;
case RequestResponse::TObjectStoreAddResponse:
HandleResponse(aResponse.get_ObjectStoreAddResponse().key());
break;
case RequestResponse::TObjectStorePutResponse:
HandleResponse(aResponse.get_ObjectStorePutResponse().key());
break;
case RequestResponse::TObjectStoreGetResponse:
HandleResponse(aResponse.get_ObjectStoreGetResponse().cloneInfo());
break;
case RequestResponse::TObjectStoreGetAllResponse:
HandleResponse(aResponse.get_ObjectStoreGetAllResponse().cloneInfos());
break;
case RequestResponse::TObjectStoreGetAllKeysResponse:
HandleResponse(aResponse.get_ObjectStoreGetAllKeysResponse().keys());
break;
case RequestResponse::TObjectStoreDeleteResponse:
HandleResponse(JS::UndefinedHandleValue);
break;
case RequestResponse::TObjectStoreClearResponse:
HandleResponse(JS::UndefinedHandleValue);
break;
case RequestResponse::TObjectStoreCountResponse:
HandleResponse(aResponse.get_ObjectStoreCountResponse().count());
break;
case RequestResponse::TIndexGetResponse:
HandleResponse(aResponse.get_IndexGetResponse().cloneInfo());
break;
case RequestResponse::TIndexGetKeyResponse:
HandleResponse(aResponse.get_IndexGetKeyResponse().key());
break;
case RequestResponse::TIndexGetAllResponse:
HandleResponse(aResponse.get_IndexGetAllResponse().cloneInfos());
break;
case RequestResponse::TIndexGetAllKeysResponse:
HandleResponse(aResponse.get_IndexGetAllKeysResponse().keys());
break;
case RequestResponse::TIndexCountResponse:
HandleResponse(aResponse.get_IndexCountResponse().count());
break;
default:
MOZ_CRASH("Unknown response type!");
}
}
mTransaction->OnRequestFinished(/* aActorDestroyedNormally */ true);
// Null this out so that we don't try to call OnRequestFinished() again in
// ActorDestroy.
mTransaction = nullptr;
return true;
}
/*******************************************************************************
* BackgroundCursorChild
******************************************************************************/
class BackgroundCursorChild::DelayedActionRunnable final
: public nsICancelableRunnable
{
using ActionFunc = void (BackgroundCursorChild::*)();
BackgroundCursorChild* mActor;
RefPtr<IDBRequest> mRequest;
ActionFunc mActionFunc;
public:
explicit
DelayedActionRunnable(BackgroundCursorChild* aActor, ActionFunc aActionFunc)
: mActor(aActor)
, mRequest(aActor->mRequest)
, mActionFunc(aActionFunc)
{
MOZ_ASSERT(aActor);
aActor->AssertIsOnOwningThread();
MOZ_ASSERT(mRequest);
MOZ_ASSERT(mActionFunc);
}
// Does not need to be threadsafe since this only runs on one thread.
NS_DECL_ISUPPORTS
private:
~DelayedActionRunnable()
{ }
NS_DECL_NSIRUNNABLE
NS_DECL_NSICANCELABLERUNNABLE
};
BackgroundCursorChild::BackgroundCursorChild(IDBRequest* aRequest,
IDBObjectStore* aObjectStore,
Direction aDirection)
: mRequest(aRequest)
, mTransaction(aRequest->GetTransaction())
, mObjectStore(aObjectStore)
, mIndex(nullptr)
, mCursor(nullptr)
, mStrongRequest(aRequest)
, mDirection(aDirection)
{
MOZ_ASSERT(aObjectStore);
aObjectStore->AssertIsOnOwningThread();
MOZ_ASSERT(mTransaction);
MOZ_COUNT_CTOR(indexedDB::BackgroundCursorChild);
#ifdef DEBUG
mOwningThread = PR_GetCurrentThread();
MOZ_ASSERT(mOwningThread);
#endif
}
BackgroundCursorChild::BackgroundCursorChild(IDBRequest* aRequest,
IDBIndex* aIndex,
Direction aDirection)
: mRequest(aRequest)
, mTransaction(aRequest->GetTransaction())
, mObjectStore(nullptr)
, mIndex(aIndex)
, mCursor(nullptr)
, mStrongRequest(aRequest)
, mDirection(aDirection)
{
MOZ_ASSERT(aIndex);
aIndex->AssertIsOnOwningThread();
MOZ_ASSERT(mTransaction);
MOZ_COUNT_CTOR(indexedDB::BackgroundCursorChild);
#ifdef DEBUG
mOwningThread = PR_GetCurrentThread();
MOZ_ASSERT(mOwningThread);
#endif
}
BackgroundCursorChild::~BackgroundCursorChild()
{
MOZ_COUNT_DTOR(indexedDB::BackgroundCursorChild);
}
#ifdef DEBUG
void
BackgroundCursorChild::AssertIsOnOwningThread() const
{
MOZ_ASSERT(mOwningThread == PR_GetCurrentThread());
}
#endif // DEBUG
void
BackgroundCursorChild::SendContinueInternal(const CursorRequestParams& aParams,
const Key& aKey)
{
AssertIsOnOwningThread();
MOZ_ASSERT(mRequest);
MOZ_ASSERT(mTransaction);
MOZ_ASSERT(mCursor);
MOZ_ASSERT(!mStrongRequest);
MOZ_ASSERT(!mStrongCursor);
// Make sure all our DOM objects stay alive.
mStrongCursor = mCursor;
MOZ_ASSERT(mRequest->ReadyState() == IDBRequestReadyState::Done);
mRequest->Reset();
mTransaction->OnNewRequest();
CursorRequestParams params = aParams;
Key key = aKey;
switch (params.type()) {
case CursorRequestParams::TContinueParams: {
if (key.IsUnset()) {
break;
}
while (!mCachedResponses.IsEmpty()) {
if (mCachedResponses[0].mKey == key) {
break;
}
mCachedResponses.RemoveElementAt(0);
}
break;
}
case CursorRequestParams::TAdvanceParams: {
uint32_t& advanceCount = params.get_AdvanceParams().count();
while (advanceCount > 1 && !mCachedResponses.IsEmpty()) {
key = mCachedResponses[0].mKey;
mCachedResponses.RemoveElementAt(0);
--advanceCount;
}
break;
}
default:
MOZ_CRASH("Should never get here!");
}
if (!mCachedResponses.IsEmpty()) {
nsCOMPtr<nsIRunnable> continueRunnable = new DelayedActionRunnable(
this, &BackgroundCursorChild::SendDelayedContinueInternal);
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToCurrentThread(continueRunnable)));
} else {
MOZ_ALWAYS_TRUE(PBackgroundIDBCursorChild::SendContinue(params, key));
}
}
void
BackgroundCursorChild::SendDelayedContinueInternal()
{
AssertIsOnOwningThread();
MOZ_ASSERT(mTransaction);
MOZ_ASSERT(mCursor);
MOZ_ASSERT(mStrongCursor);
MOZ_ASSERT(!mCachedResponses.IsEmpty());
RefPtr<IDBCursor> cursor;
mStrongCursor.swap(cursor);
auto& item = mCachedResponses[0];
mCursor->Reset(Move(item.mKey), Move(item.mCloneInfo));
mCachedResponses.RemoveElementAt(0);
ResultHelper helper(mRequest, mTransaction, mCursor);
DispatchSuccessEvent(&helper);
mTransaction->OnRequestFinished(/* aActorDestroyedNormally */ true);
}
void
BackgroundCursorChild::SendDeleteMeInternal()
{
AssertIsOnOwningThread();
MOZ_ASSERT(!mStrongRequest);
MOZ_ASSERT(!mStrongCursor);
mRequest = nullptr;
mTransaction = nullptr;
mObjectStore = nullptr;
mIndex = nullptr;
if (mCursor) {
mCursor->ClearBackgroundActor();
mCursor = nullptr;
MOZ_ALWAYS_TRUE(PBackgroundIDBCursorChild::SendDeleteMe());
}
}
void
BackgroundCursorChild::InvalidateCachedResponses()
{
AssertIsOnOwningThread();
mCachedResponses.Clear();
}
void
BackgroundCursorChild::HandleResponse(nsresult aResponse)
{
AssertIsOnOwningThread();
MOZ_ASSERT(NS_FAILED(aResponse));
MOZ_ASSERT(NS_ERROR_GET_MODULE(aResponse) == NS_ERROR_MODULE_DOM_INDEXEDDB);
MOZ_ASSERT(mRequest);
MOZ_ASSERT(mTransaction);
MOZ_ASSERT(!mStrongRequest);
MOZ_ASSERT(!mStrongCursor);
DispatchErrorEvent(mRequest, aResponse, mTransaction);
}
void
BackgroundCursorChild::HandleResponse(const void_t& aResponse)
{
AssertIsOnOwningThread();
MOZ_ASSERT(mRequest);
MOZ_ASSERT(mTransaction);
MOZ_ASSERT(!mStrongRequest);
MOZ_ASSERT(!mStrongCursor);
if (mCursor) {
mCursor->Reset();
}
ResultHelper helper(mRequest, mTransaction, &JS::NullHandleValue);
DispatchSuccessEvent(&helper);
if (!mCursor) {
nsCOMPtr<nsIRunnable> deleteRunnable = new DelayedActionRunnable(
this, &BackgroundCursorChild::SendDeleteMeInternal);
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToCurrentThread(deleteRunnable)));
}
}
void
BackgroundCursorChild::HandleResponse(
const nsTArray<ObjectStoreCursorResponse>& aResponses)
{
AssertIsOnOwningThread();
MOZ_ASSERT(mRequest);
MOZ_ASSERT(mTransaction);
MOZ_ASSERT(mObjectStore);
MOZ_ASSERT(!mStrongRequest);
MOZ_ASSERT(!mStrongCursor);
MOZ_ASSERT(aResponses.Length() == 1);
// XXX Fix this somehow...
auto& responses =
const_cast<nsTArray<ObjectStoreCursorResponse>&>(aResponses);
// If a new cursor is created, we need to keep a reference to it until the
// ResultHelper creates a DOM Binding.
RefPtr<IDBCursor> newCursor;
for (ObjectStoreCursorResponse& response : responses) {
StructuredCloneReadInfo cloneReadInfo(Move(response.cloneInfo()));
cloneReadInfo.mDatabase = mTransaction->Database();
ConvertActorsToBlobs(mTransaction->Database(),
response.cloneInfo(),
cloneReadInfo.mFiles);
if (mCursor) {
if (mCursor->IsContinueCalled()) {
mCursor->Reset(Move(response.key()), Move(cloneReadInfo));
} else {
CachedResponse cachedResponse;
cachedResponse.mKey = Move(response.key());
cachedResponse.mCloneInfo = Move(cloneReadInfo);
mCachedResponses.AppendElement(Move(cachedResponse));
}
} else {
newCursor = IDBCursor::Create(this,
Move(response.key()),
Move(cloneReadInfo));
mCursor = newCursor;
}
}
ResultHelper helper(mRequest, mTransaction, mCursor);
DispatchSuccessEvent(&helper);
}
void
BackgroundCursorChild::HandleResponse(
const ObjectStoreKeyCursorResponse& aResponse)
{
AssertIsOnOwningThread();
MOZ_ASSERT(mRequest);
MOZ_ASSERT(mTransaction);
MOZ_ASSERT(mObjectStore);
MOZ_ASSERT(!mStrongRequest);
MOZ_ASSERT(!mStrongCursor);
// XXX Fix this somehow...
auto& response = const_cast<ObjectStoreKeyCursorResponse&>(aResponse);
RefPtr<IDBCursor> newCursor;
if (mCursor) {
mCursor->Reset(Move(response.key()));
} else {
newCursor = IDBCursor::Create(this, Move(response.key()));
mCursor = newCursor;
}
ResultHelper helper(mRequest, mTransaction, mCursor);
DispatchSuccessEvent(&helper);
}
void
BackgroundCursorChild::HandleResponse(const IndexCursorResponse& aResponse)
{
AssertIsOnOwningThread();
MOZ_ASSERT(mRequest);
MOZ_ASSERT(mTransaction);
MOZ_ASSERT(mIndex);
MOZ_ASSERT(!mStrongRequest);
MOZ_ASSERT(!mStrongCursor);
// XXX Fix this somehow...
auto& response = const_cast<IndexCursorResponse&>(aResponse);
StructuredCloneReadInfo cloneReadInfo(Move(response.cloneInfo()));
cloneReadInfo.mDatabase = mTransaction->Database();
ConvertActorsToBlobs(mTransaction->Database(),
aResponse.cloneInfo(),
cloneReadInfo.mFiles);
RefPtr<IDBCursor> newCursor;
if (mCursor) {
mCursor->Reset(Move(response.key()),
Move(response.sortKey()),
Move(response.objectKey()),
Move(cloneReadInfo));
} else {
newCursor = IDBCursor::Create(this,
Move(response.key()),
Move(response.sortKey()),
Move(response.objectKey()),
Move(cloneReadInfo));
mCursor = newCursor;
}
ResultHelper helper(mRequest, mTransaction, mCursor);
DispatchSuccessEvent(&helper);
}
void
BackgroundCursorChild::HandleResponse(const IndexKeyCursorResponse& aResponse)
{
AssertIsOnOwningThread();
MOZ_ASSERT(mRequest);
MOZ_ASSERT(mTransaction);
MOZ_ASSERT(mIndex);
MOZ_ASSERT(!mStrongRequest);
MOZ_ASSERT(!mStrongCursor);
// XXX Fix this somehow...
auto& response = const_cast<IndexKeyCursorResponse&>(aResponse);
RefPtr<IDBCursor> newCursor;
if (mCursor) {
mCursor->Reset(Move(response.key()),
Move(response.sortKey()),
Move(response.objectKey()));
} else {
newCursor = IDBCursor::Create(this,
Move(response.key()),
Move(response.sortKey()),
Move(response.objectKey()));
mCursor = newCursor;
}
ResultHelper helper(mRequest, mTransaction, mCursor);
DispatchSuccessEvent(&helper);
}
void
BackgroundCursorChild::ActorDestroy(ActorDestroyReason aWhy)
{
AssertIsOnOwningThread();
MOZ_ASSERT_IF(aWhy == Deletion, !mStrongRequest);
MOZ_ASSERT_IF(aWhy == Deletion, !mStrongCursor);
MaybeCollectGarbageOnIPCMessage();
if (mStrongRequest && !mStrongCursor && mTransaction) {
mTransaction->OnRequestFinished(/* aActorDestroyedNormally */
aWhy == Deletion);
}
if (mCursor) {
mCursor->ClearBackgroundActor();
#ifdef DEBUG
mCursor = nullptr;
#endif
}
#ifdef DEBUG
mRequest = nullptr;
mTransaction = nullptr;
mObjectStore = nullptr;
mIndex = nullptr;
#endif
}
bool
BackgroundCursorChild::RecvResponse(const CursorResponse& aResponse)
{
AssertIsOnOwningThread();
MOZ_ASSERT(aResponse.type() != CursorResponse::T__None);
MOZ_ASSERT(mRequest);
MOZ_ASSERT(mTransaction);
MOZ_ASSERT_IF(mCursor, mStrongCursor);
MOZ_ASSERT_IF(!mCursor, mStrongRequest);
MaybeCollectGarbageOnIPCMessage();
RefPtr<IDBRequest> request;
mStrongRequest.swap(request);
RefPtr<IDBCursor> cursor;
mStrongCursor.swap(cursor);
RefPtr<IDBTransaction> transaction = mTransaction;
switch (aResponse.type()) {
case CursorResponse::Tnsresult:
HandleResponse(aResponse.get_nsresult());
break;
case CursorResponse::Tvoid_t:
HandleResponse(aResponse.get_void_t());
break;
case CursorResponse::TArrayOfObjectStoreCursorResponse:
HandleResponse(aResponse.get_ArrayOfObjectStoreCursorResponse());
break;
case CursorResponse::TObjectStoreKeyCursorResponse:
HandleResponse(aResponse.get_ObjectStoreKeyCursorResponse());
break;
case CursorResponse::TIndexCursorResponse:
HandleResponse(aResponse.get_IndexCursorResponse());
break;
case CursorResponse::TIndexKeyCursorResponse:
HandleResponse(aResponse.get_IndexKeyCursorResponse());
break;
default:
MOZ_CRASH("Should never get here!");
}
transaction->OnRequestFinished(/* aActorDestroyedNormally */ true);
return true;
}
NS_IMPL_ISUPPORTS(BackgroundCursorChild::DelayedActionRunnable,
nsIRunnable,
nsICancelableRunnable)
NS_IMETHODIMP
BackgroundCursorChild::
DelayedActionRunnable::Run()
{
MOZ_ASSERT(mActor);
mActor->AssertIsOnOwningThread();
MOZ_ASSERT(mRequest);
MOZ_ASSERT(mActionFunc);
(mActor->*mActionFunc)();
mActor = nullptr;
mRequest = nullptr;
return NS_OK;
}
NS_IMETHODIMP
BackgroundCursorChild::
DelayedActionRunnable::Cancel()
{
if (NS_WARN_IF(!mActor)) {
return NS_ERROR_UNEXPECTED;
}
// This must always run to clean up our state.
Run();
return NS_OK;
}
BackgroundCursorChild::CachedResponse::CachedResponse()
{
}
BackgroundCursorChild::CachedResponse::CachedResponse(CachedResponse&& aOther)
: mKey(Move(aOther.mKey))
{
mCloneInfo = Move(aOther.mCloneInfo);
}
/*******************************************************************************
* BackgroundUtilsChild
******************************************************************************/
BackgroundUtilsChild::BackgroundUtilsChild(IndexedDatabaseManager* aManager)
: mManager(aManager)
#ifdef DEBUG
, mOwningThread(NS_GetCurrentThread())
#endif
{
AssertIsOnOwningThread();
MOZ_ASSERT(aManager);
MOZ_COUNT_CTOR(indexedDB::BackgroundUtilsChild);
}
BackgroundUtilsChild::~BackgroundUtilsChild()
{
MOZ_COUNT_DTOR(indexedDB::BackgroundUtilsChild);
}
#ifdef DEBUG
void
BackgroundUtilsChild::AssertIsOnOwningThread() const
{
MOZ_ASSERT(mOwningThread);
bool current;
MOZ_ASSERT(NS_SUCCEEDED(mOwningThread->IsOnCurrentThread(&current)));
MOZ_ASSERT(current);
}
#endif // DEBUG
void
BackgroundUtilsChild::SendDeleteMeInternal()
{
AssertIsOnOwningThread();
if (mManager) {
mManager->ClearBackgroundActor();
mManager = nullptr;
MOZ_ALWAYS_TRUE(PBackgroundIndexedDBUtilsChild::SendDeleteMe());
}
}
void
BackgroundUtilsChild::ActorDestroy(ActorDestroyReason aWhy)
{
AssertIsOnOwningThread();
if (mManager) {
mManager->ClearBackgroundActor();
#ifdef DEBUG
mManager = nullptr;
#endif
}
}
} // namespace indexedDB
} // namespace dom
} // namespace mozilla