Source code

Revision control

Other Tools

/* -*- 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 <type_traits>
#include "BackgroundChildImpl.h"
#include "IDBDatabase.h"
#include "IDBEvents.h"
#include "IDBFactory.h"
#include "IDBFileHandle.h"
#include "IDBIndex.h"
#include "IDBMutableFile.h"
#include "IDBObjectStore.h"
#include "IDBRequest.h"
#include "IDBTransaction.h"
#include "IndexedDatabase.h"
#include "IndexedDatabaseInlines.h"
#include "js/Array.h" // JS::NewArrayObject, JS::SetArrayLength
#include "js/Date.h" // JS::NewDateObject, JS::TimeClip
#include <mozIRemoteLazyInputStream.h>
#include "mozilla/ArrayAlgorithm.h"
#include "mozilla/BasicEvents.h"
#include "mozilla/CycleCollectedJSRuntime.h"
#include "mozilla/Maybe.h"
#include "mozilla/SnappyUncompressInputStream.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/PermissionMessageUtils.h"
#include "mozilla/dom/BrowserChild.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseFileChild.h"
#include "mozilla/dom/IPCBlobUtils.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerRunnable.h"
#include "mozilla/Encoding.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/TaskQueue.h"
#include "nsCOMPtr.h"
#include "nsContentUtils.h"
#include "nsIAsyncInputStream.h"
#include "nsIBFCacheEntry.h"
#include "mozilla/dom/Document.h"
#include "nsIEventTarget.h"
#include "nsIFileStreams.h"
#include "nsNetCID.h"
#include "nsPIDOMWindow.h"
#include "nsThreadUtils.h"
#include "nsTraceRefcnt.h"
#include "PermissionRequestBase.h"
#include "ProfilerHelpers.h"
#include "ReportInternalError.h"
#include "ThreadLocal.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 {
namespace indexedDB {
namespace {
/*******************************************************************************
* Constants
******************************************************************************/
const uint32_t kFileCopyBufferSize = 32768;
} // namespace
/*******************************************************************************
* ThreadLocal
******************************************************************************/
ThreadLocal::ThreadLocal(const nsID& aBackgroundChildLoggingId)
: mLoggingInfo(aBackgroundChildLoggingId, 1, -1, 1),
mLoggingIdString(aBackgroundChildLoggingId) {
MOZ_COUNT_CTOR(mozilla::dom::indexedDB::ThreadLocal);
}
ThreadLocal::~ThreadLocal() {
MOZ_COUNT_DTOR(mozilla::dom::indexedDB::ThreadLocal);
}
/*******************************************************************************
* 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;
Maybe<IDBTransaction&> const mTransaction;
Maybe<IDBTransaction&> mPreviousTransaction;
ThreadLocal* mThreadLocal;
public:
AutoSetCurrentTransaction(const AutoSetCurrentTransaction&) = delete;
AutoSetCurrentTransaction(AutoSetCurrentTransaction&&) = delete;
AutoSetCurrentTransaction& operator=(const AutoSetCurrentTransaction&) =
delete;
AutoSetCurrentTransaction& operator=(AutoSetCurrentTransaction&&) = delete;
explicit AutoSetCurrentTransaction(Maybe<IDBTransaction&> aTransaction)
: mTransaction(aTransaction),
mPreviousTransaction(),
mThreadLocal(nullptr) {
if (aTransaction) {
BackgroundChildImpl::ThreadLocal* threadLocal =
BackgroundChildImpl::GetThreadLocalForCurrentThread();
MOZ_ASSERT(threadLocal);
// Hang onto this for resetting later.
mThreadLocal = threadLocal->mIndexedDBThreadLocal.get();
MOZ_ASSERT(mThreadLocal);
// Save the current value.
mPreviousTransaction = mThreadLocal->MaybeCurrentTransactionRef();
// Set the new value.
mThreadLocal->SetCurrentTransaction(aTransaction);
}
}
~AutoSetCurrentTransaction() {
MOZ_ASSERT_IF(mThreadLocal, mTransaction);
MOZ_ASSERT_IF(mThreadLocal,
ReferenceEquals(mThreadLocal->MaybeCurrentTransactionRef(),
mTransaction));
if (mThreadLocal) {
// Reset old value.
mThreadLocal->SetCurrentTransaction(mPreviousTransaction);
}
}
};
template <typename T>
void SetResultAndDispatchSuccessEvent(
const NotNull<RefPtr<IDBRequest>>& aRequest,
const SafeRefPtr<IDBTransaction>& aTransaction, T& aPtr,
RefPtr<Event> aEvent = nullptr);
namespace detail {
void DispatchSuccessEvent(const NotNull<RefPtr<IDBRequest>>& aRequest,
const SafeRefPtr<IDBTransaction>& aTransaction,
const RefPtr<Event>& aEvent);
template <class T>
std::enable_if_t<std::is_same_v<T, IDBDatabase> ||
std::is_same_v<T, IDBCursor> ||
std::is_same_v<T, IDBMutableFile>,
nsresult>
GetResult(JSContext* aCx, T* aDOMObject, JS::MutableHandle<JS::Value> aResult) {
if (!aDOMObject) {
aResult.setNull();
return NS_OK;
}
const 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, const JS::Handle<JS::Value>* aValue,
JS::MutableHandle<JS::Value> aResult) {
aResult.set(*aValue);
return NS_OK;
}
nsresult GetResult(JSContext* aCx, const uint64_t* aValue,
JS::MutableHandle<JS::Value> aResult) {
aResult.set(JS::NumberValue(*aValue));
return NS_OK;
}
nsresult GetResult(JSContext* aCx, StructuredCloneReadInfoChild&& aCloneInfo,
JS::MutableHandle<JS::Value> aResult) {
const bool ok =
IDBObjectStore::DeserializeValue(aCx, std::move(aCloneInfo), aResult);
if (NS_WARN_IF(!ok)) {
return NS_ERROR_DOM_DATA_CLONE_ERR;
}
return NS_OK;
}
nsresult GetResult(JSContext* aCx, StructuredCloneReadInfoChild* aCloneInfo,
JS::MutableHandle<JS::Value> aResult) {
return GetResult(aCx, std::move(*aCloneInfo), aResult);
}
nsresult GetResult(JSContext* aCx,
nsTArray<StructuredCloneReadInfoChild>* 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 = aCloneInfos->ElementAt(index);
JS::Rooted<JS::Value> value(aCx);
const nsresult rv = GetResult(aCx, std::move(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) {
const 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);
const 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;
}
} // namespace detail
class PermissionRequestMainProcessHelper final : public PermissionRequestBase {
BackgroundFactoryRequestChild* mActor;
SafeRefPtr<IDBFactory> mFactory;
public:
PermissionRequestMainProcessHelper(BackgroundFactoryRequestChild* aActor,
SafeRefPtr<IDBFactory> aFactory,
Element* aOwnerElement,
nsIPrincipal* aPrincipal)
: PermissionRequestBase(aOwnerElement, aPrincipal),
mActor(aActor),
mFactory(std::move(aFactory)) {
MOZ_ASSERT(aActor);
MOZ_ASSERT(mFactory);
aActor->AssertIsOnOwningThread();
}
protected:
~PermissionRequestMainProcessHelper() = default;
private:
virtual void OnPromptComplete(PermissionValue aPermissionValue) override;
};
auto DeserializeStructuredCloneFiles(
IDBDatabase* aDatabase,
const nsTArray<SerializedStructuredCloneFile>& aSerializedFiles,
bool aForPreprocess) {
MOZ_ASSERT_IF(aForPreprocess, aSerializedFiles.Length() == 1);
return TransformIntoNewArray(
aSerializedFiles,
[aForPreprocess, &database = *aDatabase](
const auto& serializedFile) -> StructuredCloneFileChild {
MOZ_ASSERT_IF(
aForPreprocess,
serializedFile.type() == StructuredCloneFileBase::eStructuredClone);
const BlobOrMutableFile& blobOrMutableFile = serializedFile.file();
switch (serializedFile.type()) {
case StructuredCloneFileBase::eBlob: {
MOZ_ASSERT(blobOrMutableFile.type() == BlobOrMutableFile::TIPCBlob);
const IPCBlob& ipcBlob = blobOrMutableFile.get_IPCBlob();
const RefPtr<BlobImpl> blobImpl =
IPCBlobUtils::Deserialize(ipcBlob);
MOZ_ASSERT(blobImpl);
RefPtr<Blob> blob =
Blob::Create(database.GetOwnerGlobal(), blobImpl);
MOZ_ASSERT(blob);
return {StructuredCloneFileBase::eBlob, std::move(blob)};
}
case StructuredCloneFileBase::eMutableFile: {
MOZ_ASSERT(blobOrMutableFile.type() == BlobOrMutableFile::Tnull_t ||
blobOrMutableFile.type() ==
BlobOrMutableFile::TPBackgroundMutableFileChild);
switch (blobOrMutableFile.type()) {
case BlobOrMutableFile::Tnull_t:
return StructuredCloneFileChild{
StructuredCloneFileBase::eMutableFile};
case BlobOrMutableFile::TPBackgroundMutableFileChild: {
auto* const actor = static_cast<BackgroundMutableFileChild*>(
blobOrMutableFile.get_PBackgroundMutableFileChild());
MOZ_ASSERT(actor);
actor->EnsureDOMObject();
auto* const mutableFile =
static_cast<IDBMutableFile*>(actor->GetDOMObject());
MOZ_ASSERT(mutableFile);
auto file = StructuredCloneFileChild{mutableFile};
actor->ReleaseDOMObject();
return file;
}
default:
MOZ_CRASH("Should never get here!");
}
}
case StructuredCloneFileBase::eStructuredClone: {
if (aForPreprocess) {
MOZ_ASSERT(blobOrMutableFile.type() ==
BlobOrMutableFile::TIPCBlob);
const IPCBlob& ipcBlob = blobOrMutableFile.get_IPCBlob();
const RefPtr<BlobImpl> blobImpl =
IPCBlobUtils::Deserialize(ipcBlob);
MOZ_ASSERT(blobImpl);
RefPtr<Blob> blob =
Blob::Create(database.GetOwnerGlobal(), blobImpl);
MOZ_ASSERT(blob);
return {StructuredCloneFileBase::eStructuredClone,
std::move(blob)};
}
MOZ_ASSERT(blobOrMutableFile.type() == BlobOrMutableFile::Tnull_t);
return StructuredCloneFileChild{
StructuredCloneFileBase::eStructuredClone};
}
case StructuredCloneFileBase::eWasmBytecode:
case StructuredCloneFileBase::eWasmCompiled: {
MOZ_ASSERT(blobOrMutableFile.type() == BlobOrMutableFile::Tnull_t);
return StructuredCloneFileChild{serializedFile.type()};
// Don't set mBlob, support for storing WebAssembly.Modules has been
// removed in bug 1469395. Support for de-serialization of
// WebAssembly.Modules has been removed in bug 1561876. Full removal
// is tracked in bug 1487479.
}
default:
MOZ_CRASH("Should never get here!");
}
});
}
JSStructuredCloneData PreprocessingNotSupported() {
MOZ_CRASH("Preprocessing not (yet) supported!");
}
template <typename PreprocessInfoAccessor>
StructuredCloneReadInfoChild DeserializeStructuredCloneReadInfo(
SerializedStructuredCloneReadInfo&& aSerialized,
IDBDatabase* const aDatabase,
PreprocessInfoAccessor preprocessInfoAccessor) {
// XXX Make this a class invariant of SerializedStructuredCloneReadInfo.
MOZ_ASSERT_IF(aSerialized.hasPreprocessInfo(),
0 == aSerialized.data().data.Size());
return {aSerialized.hasPreprocessInfo() ? preprocessInfoAccessor()
: std::move(aSerialized.data().data),
DeserializeStructuredCloneFiles(aDatabase, aSerialized.files(),
/* aForPreprocess */ false),
aDatabase};
}
// TODO: Remove duplication between DispatchErrorEvent and DispatchSucessEvent.
void DispatchErrorEvent(
MovingNotNull<RefPtr<IDBRequest>> aRequest, nsresult aErrorCode,
const SafeRefPtr<IDBTransaction>& aTransaction = nullptr,
RefPtr<Event> aEvent = nullptr) {
const RefPtr<IDBRequest> request = std::move(aRequest);
request->AssertIsOnOwningThread();
MOZ_ASSERT(NS_FAILED(aErrorCode));
MOZ_ASSERT(NS_ERROR_GET_MODULE(aErrorCode) == NS_ERROR_MODULE_DOM_INDEXEDDB);
AUTO_PROFILER_LABEL("IndexedDB:DispatchErrorEvent", DOM);
request->SetError(aErrorCode);
if (!aEvent) {
// Make an error event and fire it at the target.
aEvent = CreateGenericEvent(request, nsDependentString(kErrorEventType),
eDoesBubble, eCancelable);
}
MOZ_ASSERT(aEvent);
// XXX This is redundant if we are called from
// DispatchSuccessEvent.
Maybe<AutoSetCurrentTransaction> asct;
if (aTransaction) {
asct.emplace(SomeRef(*aTransaction));
}
if (aTransaction && aTransaction->IsInactive()) {
aTransaction->TransitionToActive();
}
if (aTransaction) {
IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
"Firing %s event with error 0x%x", "%s (0x%" PRIx32 ")",
aTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
IDB_LOG_STRINGIFY(aEvent, kErrorEventType),
static_cast<uint32_t>(aErrorCode));
} else {
IDB_LOG_MARK_CHILD_REQUEST("Firing %s event with error 0x%x",
"%s (0x%" PRIx32 ")",
request->LoggingSerialNumber(),
IDB_LOG_STRINGIFY(aEvent, kErrorEventType),
static_cast<uint32_t>(aErrorCode));
}
IgnoredErrorResult rv;
const bool doDefault =
request->DispatchEvent(*aEvent, CallerType::System, rv);
if (NS_WARN_IF(rv.Failed())) {
return;
}
MOZ_ASSERT(!aTransaction || aTransaction->IsActive() ||
aTransaction->IsAborted() ||
aTransaction->WasExplicitlyCommitted());
if (aTransaction && aTransaction->IsActive()) {
aTransaction->TransitionToInactive();
// Do not abort the transaction here if this request is failed due to the
// abortion of its transaction to ensure that the correct error cause of
// the abort event be set in IDBTransaction::FireCompleteOrAbortEvents()
// later.
if (aErrorCode != NS_ERROR_DOM_INDEXEDDB_ABORT_ERR) {
WidgetEvent* const internalEvent = aEvent->WidgetEventPtr();
MOZ_ASSERT(internalEvent);
if (internalEvent->mFlags.mExceptionWasRaised) {
aTransaction->Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
} else if (doDefault) {
aTransaction->Abort(request);
}
}
}
}
template <typename T>
void SetResultAndDispatchSuccessEvent(
const NotNull<RefPtr<IDBRequest>>& aRequest,
const SafeRefPtr<IDBTransaction>& aTransaction, T& aPtr,
RefPtr<Event> aEvent) {
const auto autoTransaction = AutoSetCurrentTransaction{
aTransaction ? SomeRef(*aTransaction) : Nothing()};
AUTO_PROFILER_LABEL("IndexedDB:SetResultAndDispatchSuccessEvent", DOM);
aRequest->AssertIsOnOwningThread();
if (aTransaction && aTransaction->IsAborted()) {
DispatchErrorEvent(aRequest, aTransaction->AbortCode(), aTransaction);
return;
}
if (!aEvent) {
aEvent =
CreateGenericEvent(aRequest.get(), nsDependentString(kSuccessEventType),
eDoesNotBubble, eNotCancelable);
}
MOZ_ASSERT(aEvent);
aRequest->SetResult(
[&aPtr](JSContext* aCx, JS::MutableHandle<JS::Value> aResult) {
MOZ_ASSERT(aCx);
return detail::GetResult(aCx, &aPtr, aResult);
});
detail::DispatchSuccessEvent(aRequest, aTransaction, aEvent);
}
namespace detail {
void DispatchSuccessEvent(const NotNull<RefPtr<IDBRequest>>& aRequest,
const SafeRefPtr<IDBTransaction>& aTransaction,
const RefPtr<Event>& aEvent) {
if (aTransaction && aTransaction->IsInactive()) {
aTransaction->TransitionToActive();
}
if (aTransaction) {
IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
"Firing %s event", "%s", aTransaction->LoggingSerialNumber(),
aRequest->LoggingSerialNumber(),
IDB_LOG_STRINGIFY(aEvent, kSuccessEventType));
} else {
IDB_LOG_MARK_CHILD_REQUEST("Firing %s event", "%s",
aRequest->LoggingSerialNumber(),
IDB_LOG_STRINGIFY(aEvent, kSuccessEventType));
}
MOZ_ASSERT_IF(aTransaction && !aTransaction->WasExplicitlyCommitted(),
aTransaction->IsActive() && !aTransaction->IsAborted());
IgnoredErrorResult rv;
aRequest->DispatchEvent(*aEvent, rv);
if (NS_WARN_IF(rv.Failed())) {
return;
}
WidgetEvent* const internalEvent = aEvent->WidgetEventPtr();
MOZ_ASSERT(internalEvent);
if (aTransaction && aTransaction->IsActive()) {
aTransaction->TransitionToInactive();
if (internalEvent->mFlags.mExceptionWasRaised) {
aTransaction->Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
} else {
// To handle upgrade transaction.
aTransaction->CommitIfNotStarted();
}
}
}
} // namespace detail
PRFileDesc* GetFileDescriptorFromStream(nsIInputStream* aStream) {
MOZ_ASSERT(aStream);
const nsCOMPtr<nsIFileMetadata> fileMetadata = do_QueryInterface(aStream);
if (NS_WARN_IF(!fileMetadata)) {
return nullptr;
}
PRFileDesc* fileDesc;
const nsresult rv = fileMetadata->GetFileDescriptor(&fileDesc);
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}
MOZ_ASSERT(fileDesc);
return fileDesc;
}
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;
};
class WorkerPermissionChallenge final : public Runnable {
public:
WorkerPermissionChallenge(WorkerPrivate* aWorkerPrivate,
BackgroundFactoryRequestChild* aActor,
SafeRefPtr<IDBFactory> aFactory,
PrincipalInfo&& aPrincipalInfo)
: Runnable("indexedDB::WorkerPermissionChallenge"),
mWorkerPrivate(aWorkerPrivate),
mActor(aActor),
mFactory(std::move(aFactory)),
mPrincipalInfo(std::move(aPrincipalInfo)) {
MOZ_ASSERT(mWorkerPrivate);
MOZ_ASSERT(aActor);
MOZ_ASSERT(mFactory);
mWorkerPrivate->AssertIsOnWorkerThread();
}
bool Dispatch() {
mWorkerPrivate->AssertIsOnWorkerThread();
if (NS_WARN_IF(!mWorkerPrivate->ModifyBusyCountFromWorker(true))) {
return false;
}
if (NS_WARN_IF(NS_FAILED(mWorkerPrivate->DispatchToMainThread(this)))) {
mWorkerPrivate->ModifyBusyCountFromWorker(false);
return false;
}
return true;
}
NS_IMETHOD
Run() override {
const bool completed = RunInternal();
if (completed) {
OperationCompleted();
}
return NS_OK;
}
void OperationCompleted() {
if (NS_IsMainThread()) {
const RefPtr<WorkerPermissionOperationCompleted> runnable =
new WorkerPermissionOperationCompleted(mWorkerPrivate, this);
MOZ_ALWAYS_TRUE(runnable->Dispatch());
return;
}
MOZ_ASSERT(mActor);
mActor->AssertIsOnOwningThread();
MaybeCollectGarbageOnIPCMessage();
const SafeRefPtr<IDBFactory> factory = std::move(mFactory);
Unused << factory; // XXX see Bug 1605075
mActor->SendPermissionRetry();
mActor = nullptr;
mWorkerPrivate->AssertIsOnWorkerThread();
mWorkerPrivate->ModifyBusyCountFromWorker(false);
}
private:
bool RunInternal() {
MOZ_ASSERT(NS_IsMainThread());
// Walk up to our containing page
WorkerPrivate* wp = mWorkerPrivate;
while (wp->GetParent()) {
wp = wp->GetParent();
}
nsPIDOMWindowInner* const window = wp->GetWindow();
if (!window) {
return true;
}
auto principalOrErr =
mozilla::ipc::PrincipalInfoToPrincipal(mPrincipalInfo);
if (NS_WARN_IF(principalOrErr.isErr())) {
return true;
}
const nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
if (XRE_IsParentProcess()) {
const 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;
}
BrowserChild* browserChild = BrowserChild::GetFrom(window);
MOZ_ASSERT(browserChild);
RefPtr<WorkerPermissionChallenge> self(this);
browserChild->SendIndexedDBPermissionRequest(principal)->Then(
GetCurrentSerialEventTarget(), __func__,
[self](const uint32_t& aPermission) { self->OperationCompleted(); },
[](const mozilla::ipc::ResponseRejectReason) {});
return false;
}
private:
WorkerPrivate* const mWorkerPrivate;
BackgroundFactoryRequestChild* mActor;
SafeRefPtr<IDBFactory> mFactory;
const 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;
}
class MOZ_STACK_CLASS AutoSetCurrentFileHandle final {
typedef mozilla::ipc::BackgroundChildImpl BackgroundChildImpl;
IDBFileHandle* const mFileHandle;
IDBFileHandle* mPreviousFileHandle;
IDBFileHandle** mThreadLocalSlot;
public:
explicit AutoSetCurrentFileHandle(IDBFileHandle* aFileHandle)
: mFileHandle(aFileHandle),
mPreviousFileHandle(nullptr),
mThreadLocalSlot(nullptr) {
if (aFileHandle) {
BackgroundChildImpl::ThreadLocal* threadLocal =
BackgroundChildImpl::GetThreadLocalForCurrentThread();
MOZ_ASSERT(threadLocal);
// Hang onto this location for resetting later.
mThreadLocalSlot = &threadLocal->mCurrentFileHandle;
// Save the current value.
mPreviousFileHandle = *mThreadLocalSlot;
// Set the new value.
*mThreadLocalSlot = aFileHandle;
}
}
~AutoSetCurrentFileHandle() {
MOZ_ASSERT_IF(mThreadLocalSlot, mFileHandle);
MOZ_ASSERT_IF(mThreadLocalSlot, *mThreadLocalSlot == mFileHandle);
if (mThreadLocalSlot) {
// Reset old value.
*mThreadLocalSlot = mPreviousFileHandle;
}
}
IDBFileHandle* FileHandle() const { return mFileHandle; }
};
template <typename T>
void SetFileHandleResultAndDispatchSuccessEvent(
const RefPtr<IDBFileRequest>& aFileRequest,
const RefPtr<IDBFileHandle>& aFileHandle, T* aPtr);
namespace detail {
nsresult GetFileHandleResult(const RefPtr<IDBFileRequest>& aFileRequest,
JSContext* aCx, const nsCString* aString,
JS::MutableHandle<JS::Value> aResult) {
const nsCString& data = *aString;
nsresult rv;
if (!aFileRequest->HasEncoding()) {
JS::Rooted<JSObject*> arrayBuffer(aCx);
rv = nsContentUtils::CreateArrayBuffer(aCx, data, arrayBuffer.address());
if (NS_WARN_IF(NS_FAILED(rv))) {
return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
}
aResult.setObject(*arrayBuffer);
return NS_OK;
}
// Try the API argument.
const Encoding* encoding = Encoding::ForLabel(aFileRequest->GetEncoding());
if (!encoding) {
// API argument failed. Since we are dealing with a file system file,
// we don't have a meaningful type attribute for the blob available,
// so proceeding to the next step, which is defaulting to UTF-8.
encoding = UTF_8_ENCODING;
}
nsString tmpString;
Tie(rv, encoding) = encoding->Decode(data, tmpString);
if (NS_WARN_IF(NS_FAILED(rv))) {
return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
}
if (NS_WARN_IF(!xpc::StringToJsval(aCx, tmpString, aResult))) {
return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
}
return NS_OK;
}
nsresult GetFileHandleResult(const RefPtr<IDBFileRequest>& /*aFileRequest*/,
JSContext* aCx,
const FileRequestMetadata* aMetadata,
JS::MutableHandle<JS::Value> aResult) {
JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
if (NS_WARN_IF(!obj)) {
return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
}
const Maybe<uint64_t>& size = aMetadata->size();
if (size.isSome()) {
JS::Rooted<JS::Value> number(aCx, JS_NumberValue(size.value()));
if (NS_WARN_IF(!JS_DefineProperty(aCx, obj, "size", number, 0))) {
return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
}
}
const Maybe<int64_t>& lastModified = aMetadata->lastModified();
if (lastModified.isSome()) {
JS::Rooted<JSObject*> date(
aCx, JS::NewDateObject(aCx, JS::TimeClip(lastModified.value())));
if (NS_WARN_IF(!date)) {
return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
}
if (NS_WARN_IF(!JS_DefineProperty(aCx, obj, "lastModified", date, 0))) {
return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
}
}
aResult.setObject(*obj);
return NS_OK;
}
nsresult GetFileHandleResult(const RefPtr<IDBFileRequest>& /*aFileRequest*/,
JSContext* aCx,
const JS::Handle<JS::Value>* aValue,
JS::MutableHandle<JS::Value> aResult) {
aResult.set(*aValue);
return NS_OK;
}
} // namespace detail
void DispatchFileHandleErrorEvent(IDBFileRequest* aFileRequest,
nsresult aErrorCode,
IDBFileHandle* aFileHandle) {
MOZ_ASSERT(aFileRequest);
aFileRequest->AssertIsOnOwningThread();
MOZ_ASSERT(NS_FAILED(aErrorCode));
MOZ_ASSERT(NS_ERROR_GET_MODULE(aErrorCode) == NS_ERROR_MODULE_DOM_FILEHANDLE);
MOZ_ASSERT(aFileHandle);
const RefPtr<IDBFileRequest> fileRequest = aFileRequest;
const RefPtr<IDBFileHandle> fileHandle = aFileHandle;
AutoSetCurrentFileHandle ascfh(aFileHandle);
fileRequest->FireError(aErrorCode);
MOZ_ASSERT(fileHandle->IsOpen() || fileHandle->IsAborted());
}
template <typename T>
void SetFileHandleResultAndDispatchSuccessEvent(
const RefPtr<IDBFileRequest>& aFileRequest,
const RefPtr<IDBFileHandle>& aFileHandle, T* aPtr) {
MOZ_ASSERT(aFileRequest);
MOZ_ASSERT(aFileHandle);
MOZ_ASSERT(aPtr);
auto autoFileHandle = AutoSetCurrentFileHandle{aFileHandle};
aFileRequest->AssertIsOnOwningThread();
if (aFileHandle->IsAborted()) {
aFileRequest->FireError(NS_ERROR_DOM_FILEHANDLE_ABORT_ERR);
return;
}
MOZ_ASSERT(aFileHandle->IsOpen());
aFileRequest->SetResult(
[aFileRequest, aPtr](JSContext* aCx,
JS::MutableHandle<JS::Value> aResult) {
return detail::GetFileHandleResult(aFileRequest, aCx, aPtr, aResult);
});
MOZ_ASSERT(aFileHandle->IsOpen() || aFileHandle->IsAborted());
}
auto GetKeyOperator(const IDBCursorDirection aDirection) {
switch (aDirection) {
case IDBCursorDirection::Next:
case IDBCursorDirection::Nextunique:
return &Key::operator>=;
case IDBCursorDirection::Prev:
case IDBCursorDirection::Prevunique:
return &Key::operator<=;
default:
MOZ_CRASH("Should never get here.");
}
}
// Does not need to be threadsafe since this only runs on one thread, but
// inheriting from CancelableRunnable is easy.
template <typename T>
class DelayedActionRunnable final : public CancelableRunnable {
using ActionFunc = void (T::*)();
T* mActor;
RefPtr<IDBRequest> mRequest;
ActionFunc mActionFunc;
public:
explicit DelayedActionRunnable(T* aActor, ActionFunc aActionFunc)
: CancelableRunnable("indexedDB::DelayedActionRunnable"),
mActor(aActor),
mRequest(aActor->GetRequest()),
mActionFunc(aActionFunc) {
MOZ_ASSERT(aActor);
aActor->AssertIsOnOwningThread();
MOZ_ASSERT(mRequest);
MOZ_ASSERT(mActionFunc);
}
private:
~DelayedActionRunnable() = default;
NS_DECL_NSIRUNNABLE
nsresult Cancel() override;
};
} // namespace
/*******************************************************************************
* Actor class declarations
******************************************************************************/
// CancelableRunnable is used to make workers happy.
class BackgroundRequestChild::PreprocessHelper final
: public CancelableRunnable,
public nsIInputStreamCallback,
public nsIFileMetadataCallback {
enum class State {
// Just created on the owning thread, dispatched to the thread pool. Next
// step is either Finishing if stream was ready to be read or
// WaitingForStreamReady if the stream is not ready.
Initial,
// Waiting for stream to be ready on a thread pool thread. Next state is
// Finishing.
WaitingForStreamReady,
// Waiting to finish/finishing on the owning thread. Next step is Completed.
Finishing,
// All done.
Completed
};
const nsCOMPtr<nsIEventTarget> mOwningEventTarget;
RefPtr<TaskQueue> mTaskQueue;
nsCOMPtr<nsIInputStream> mStream;
UniquePtr<JSStructuredCloneData> mCloneData;
BackgroundRequestChild* mActor;
const uint32_t mCloneDataIndex;
nsresult mResultCode;
State mState;
public:
PreprocessHelper(uint32_t aCloneDataIndex, BackgroundRequestChild* aActor)
: CancelableRunnable(
"indexedDB::BackgroundRequestChild::PreprocessHelper"),
mOwningEventTarget(aActor->GetActorEventTarget()),
mActor(aActor),
mCloneDataIndex(aCloneDataIndex),
mResultCode(NS_OK),
mState(State::Initial) {
AssertIsOnOwningThread();
MOZ_ASSERT(aActor);
aActor->AssertIsOnOwningThread();
}
bool IsOnOwningThread() const {
MOZ_ASSERT(mOwningEventTarget);
bool current;
return NS_SUCCEEDED(mOwningEventTarget->IsOnCurrentThread(&current)) &&
current;
}
void AssertIsOnOwningThread() const { MOZ_ASSERT(IsOnOwningThread()); }
void ClearActor() {
AssertIsOnOwningThread();
mActor = nullptr;
}
nsresult Init(const StructuredCloneFileChild& aFile);
nsresult Dispatch();
private:
~PreprocessHelper() {
MOZ_ASSERT(mState == State::Initial || mState == State::Completed);
if (mTaskQueue) {
mTaskQueue->BeginShutdown();
}
}
nsresult Start();
nsresult ProcessStream();
void Finish();
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_NSIRUNNABLE
NS_DECL_NSIINPUTSTREAMCALLBACK
NS_DECL_NSIFILEMETADATACALLBACK
};
/*******************************************************************************
* Local class implementations
******************************************************************************/
void PermissionRequestMainProcessHelper::OnPromptComplete(
PermissionValue aPermissionValue) {
MOZ_ASSERT(mActor);
mActor->AssertIsOnOwningThread();
MaybeCollectGarbageOnIPCMessage();
mActor->SendPermissionRetry();
mActor = nullptr;
mFactory = nullptr;
}
/*******************************************************************************
* BackgroundRequestChildBase
******************************************************************************/
BackgroundRequestChildBase::BackgroundRequestChildBase(
MovingNotNull<RefPtr<IDBRequest>> aRequest)
: mRequest(std::move(aRequest)) {
mRequest->AssertIsOnOwningThread();
MOZ_COUNT_CTOR(indexedDB::BackgroundRequestChildBase);
}
BackgroundRequestChildBase::~BackgroundRequestChildBase() {
AssertIsOnOwningThread();
MOZ_COUNT_DTOR(indexedDB::BackgroundRequestChildBase);
}
#ifdef DEBUG
void BackgroundRequestChildBase::AssertIsOnOwningThread() const {
mRequest->AssertIsOnOwningThread();
}
#endif // DEBUG
/*******************************************************************************
* BackgroundFactoryChild
******************************************************************************/
BackgroundFactoryChild::BackgroundFactoryChild(IDBFactory& aFactory)
: mFactory(&aFactory) {
AssertIsOnOwningThread();
mFactory->AssertIsOnOwningThread();
MOZ_COUNT_CTOR(indexedDB::BackgroundFactoryChild);
}
BackgroundFactoryChild::~BackgroundFactoryChild() {
MOZ_COUNT_DTOR(indexedDB::BackgroundFactoryChild);
}
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* const 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;
}
mozilla::ipc::IPCResult
BackgroundFactoryChild::RecvPBackgroundIDBDatabaseConstructor(
PBackgroundIDBDatabaseChild* aActor, const DatabaseSpec& aSpec,
PBackgroundIDBFactoryRequestChild* aRequest) {
AssertIsOnOwningThread();
MOZ_ASSERT(aActor);
MOZ_ASSERT(aActor->GetActorEventTarget(),
"The event target shall be inherited from its manager actor.");
return IPC_OK();
}
/*******************************************************************************
* BackgroundFactoryRequestChild
******************************************************************************/
BackgroundFactoryRequestChild::BackgroundFactoryRequestChild(
SafeRefPtr<IDBFactory> aFactory,
MovingNotNull<RefPtr<IDBOpenDBRequest>> aOpenRequest, bool aIsDeleteOp,
uint64_t aRequestedVersion)
: BackgroundRequestChildBase(std::move(aOpenRequest)),
mFactory(std::move(aFactory)),
mDatabaseActor(nullptr),
mRequestedVersion(aRequestedVersion),
mIsDeleteOp(aIsDeleteOp) {
// Can't assert owning thread here because IPDL has not yet set our manager!
MOZ_ASSERT(mFactory);
mFactory->AssertIsOnOwningThread();
MOZ_COUNT_CTOR(indexedDB::BackgroundFactoryRequestChild);
}
BackgroundFactoryRequestChild::~BackgroundFactoryRequestChild() {
MOZ_COUNT_DTOR(indexedDB::BackgroundFactoryRequestChild);
}
NotNull<IDBOpenDBRequest*> BackgroundFactoryRequestChild::GetOpenDBRequest()
const {
AssertIsOnOwningThread();
// XXX NotNull might provide something to encapsulate this
return WrapNotNullUnchecked(
static_cast<IDBOpenDBRequest*>(mRequest.get().get()));
}
void BackgroundFactoryRequestChild::SetDatabaseActor(
BackgroundDatabaseChild* aActor) {
AssertIsOnOwningThread();
MOZ_ASSERT(!aActor || !mDatabaseActor);
mDatabaseActor = aActor;
}
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);
if (mDatabaseActor) {
mDatabaseActor->ReleaseDOMObject();
MOZ_ASSERT(!mDatabaseActor);
}
return true;
}
bool BackgroundFactoryRequestChild::HandleResponse(
const OpenDatabaseRequestResponse& aResponse) {
AssertIsOnOwningThread();
mRequest->Reset();
auto* databaseActor =
static_cast<BackgroundDatabaseChild*>(aResponse.databaseChild());
MOZ_ASSERT(databaseActor);
NotNull<IDBDatabase*> database = [this, databaseActor] {
IDBDatabase* database = databaseActor->GetDOMObject();
if (!database) {
Unused << this;
databaseActor->EnsureDOMObject();
MOZ_ASSERT(mDatabaseActor);
database = databaseActor->GetDOMObject();
MOZ_ASSERT(database);
MOZ_ASSERT(!database->IsClosed());
}
return WrapNotNullUnchecked(database);
}();
MOZ_ASSERT(mDatabaseActor == databaseActor);
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 {
SetResultAndDispatchSuccessEvent(mRequest, nullptr, *database);
}
databaseActor->ReleaseDOMObject();
MOZ_ASSERT(!mDatabaseActor);
return true;
}
bool BackgroundFactoryRequestChild::HandleResponse(
const DeleteDatabaseRequestResponse& aResponse) {
AssertIsOnOwningThread();
RefPtr<Event> successEvent = IDBVersionChangeEvent::Create(
mRequest.get(), nsDependentString(kSuccessEventType),
aResponse.previousVersion());
MOZ_ASSERT(successEvent);
SetResultAndDispatchSuccessEvent(mRequest, nullptr, JS::UndefinedHandleValue,
std::move(successEvent));
MOZ_ASSERT(!mDatabaseActor);
return true;
}
void BackgroundFactoryRequestChild::ActorDestroy(ActorDestroyReason aWhy) {
AssertIsOnOwningThread();
MaybeCollectGarbageOnIPCMessage();
if (aWhy != Deletion) {
GetOpenDBRequest()->NoteComplete();
}
}
mozilla::ipc::IPCResult BackgroundFactoryRequestChild::Recv__delete__(
const FactoryRequestResponse& aResponse) {
AssertIsOnOwningThread();
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!");
}
auto request = GetOpenDBRequest();
request->NoteComplete();
if (NS_WARN_IF(!result)) {
return IPC_FAIL_NO_REASON(this);
}
return IPC_OK();
}
mozilla::ipc::IPCResult BackgroundFactoryRequestChild::RecvPermissionChallenge(
PrincipalInfo&& aPrincipalInfo) {
AssertIsOnOwningThread();
MaybeCollectGarbageOnIPCMessage();
if (!NS_IsMainThread()) {
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(workerPrivate);
workerPrivate->AssertIsOnWorkerThread();
RefPtr<WorkerPermissionChallenge> challenge = new WorkerPermissionChallenge(
workerPrivate, this, mFactory.clonePtr(), std::move(aPrincipalInfo));
if (!challenge->Dispatch()) {
return IPC_FAIL_NO_REASON(this);
}
return IPC_OK();
}
auto principalOrErr = mozilla::ipc::PrincipalInfoToPrincipal(aPrincipalInfo);
if (NS_WARN_IF(principalOrErr.isErr())) {
return IPC_FAIL_NO_REASON(this);
}
nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
if (XRE_IsParentProcess()) {
nsCOMPtr<nsIGlobalObject> global = mFactory->GetParentObject();
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global);
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.
if (!SendPermissionRetry()) {
return IPC_FAIL_NO_REASON(this);
}
return IPC_OK();
}
RefPtr<PermissionRequestMainProcessHelper> helper =
new PermissionRequestMainProcessHelper(this, mFactory.clonePtr(),
ownerElement, principal);
PermissionRequestBase::PermissionValue permission;
if (NS_WARN_IF(NS_FAILED(helper->PromptIfNeeded(&permission)))) {
return IPC_FAIL_NO_REASON(this);
}
MOZ_ASSERT(permission == PermissionRequestBase::kPermissionAllowed ||
permission == PermissionRequestBase::kPermissionDenied ||
permission == PermissionRequestBase::kPermissionPrompt);
if (permission != PermissionRequestBase::kPermissionPrompt) {
SendPermissionRetry();
}
return IPC_OK();
}
RefPtr<BrowserChild> browserChild = mFactory->GetBrowserChild();
MOZ_ASSERT(browserChild);
browserChild->SendIndexedDBPermissionRequest(principal)->Then(
GetCurrentSerialEventTarget(), __func__,
[this](const uint32_t& aPermission) {
this->AssertIsOnOwningThread();
MaybeCollectGarbageOnIPCMessage();
this->SendPermissionRetry();
},
[](const mozilla::ipc::ResponseRejectReason) {});
return IPC_OK();
}
mozilla::ipc::IPCResult BackgroundFactoryRequestChild::RecvBlocked(
const uint64_t aCurrentVersion) {
AssertIsOnOwningThread();
MaybeCollectGarbageOnIPCMessage();
const nsDependentString type(kBlockedEventType);
RefPtr<Event> blockedEvent;
if (mIsDeleteOp) {
blockedEvent =
IDBVersionChangeEvent::Create(mRequest.get(), type, aCurrentVersion);
MOZ_ASSERT(blockedEvent);
} else {
blockedEvent = IDBVersionChangeEvent::Create(
mRequest.get(), type, aCurrentVersion, mRequestedVersion);
MOZ_ASSERT(blockedEvent);
}
RefPtr<IDBRequest> kungFuDeathGrip = mRequest;
IDB_LOG_MARK_CHILD_REQUEST("Firing \"blocked\" event", "\"blocked\"",
kungFuDeathGrip->LoggingSerialNumber());
IgnoredErrorResult rv;
kungFuDeathGrip->DispatchEvent(*blockedEvent, rv);
if (rv.Failed()) {
NS_WARNING("Failed to dispatch event!");
}
return IPC_OK();
}
/*******************************************************************************
* BackgroundDatabaseChild
******************************************************************************/
BackgroundDatabaseChild::BackgroundDatabaseChild(
const DatabaseSpec& aSpec, BackgroundFactoryRequestChild* aOpenRequestActor)
: mSpec(MakeUnique<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);
}
#ifdef DEBUG
void BackgroundDatabaseChild::AssertIsOnOwningThread() const {
static_cast<BackgroundFactoryChild*>(Manager())->AssertIsOnOwningThread();
}
#endif // DEBUG
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);
MOZ_ASSERT(mDatabase == mTemporaryStrongDatabase);
return;
}
MOZ_ASSERT(mSpec);
const auto request = mOpenRequestActor->GetOpenDBRequest();
auto& factory =
static_cast<BackgroundFactoryChild*>(Manager())->GetDOMObject();
// TODO: This AcquireStrongRefFromRawPtr looks suspicious. This should be
// changed or at least well explained, see also comment on
// BackgroundFactoryChild.
mTemporaryStrongDatabase = IDBDatabase::Create(
request, SafeRefPtr{&factory, AcquireStrongRefFromRawPtr{}}, this,
std::move(mSpec));
MOZ_ASSERT(mTemporaryStrongDatabase);
mTemporaryStrongDatabase->AssertIsOnOwningThread();
mDatabase = mTemporaryStrongDatabase;
mOpenRequestActor->SetDatabaseActor(this);
}
void BackgroundDatabaseChild::ReleaseDOMObject() {
AssertIsOnOwningThread();
MOZ_ASSERT(mTemporaryStrongDatabase);
mTemporaryStrongDatabase->AssertIsOnOwningThread();
MOZ_ASSERT(mOpenRequestActor);
MOZ_ASSERT(mDatabase == mTemporaryStrongDatabase);
mOpenRequestActor->SetDatabaseActor(nullptr);
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;
// XXX Why isn't mDatabase set to nullptr here?
}
void BackgroundDatabaseChild::ActorDestroy(ActorDestroyReason aWhy) {
AssertIsOnOwningThread();
MaybeCollectGarbageOnIPCMessage();
if (mDatabase) {
mDatabase->ClearBackgroundActor();
#ifdef DEBUG
mDatabase = nullptr;
#endif
}
}
PBackgroundIDBDatabaseFileChild*
BackgroundDatabaseChild::AllocPBackgroundIDBDatabaseFileChild(
const IPCBlob& aIPCBlob) {
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;
}
already_AddRefed<PBackgroundIDBVersionChangeTransactionChild>
BackgroundDatabaseChild::AllocPBackgroundIDBVersionChangeTransactionChild(
const uint64_t aCurrentVersion, const uint64_t aRequestedVersion,
const int64_t aNextObjectStoreId, const int64_t aNextIndexId) {
AssertIsOnOwningThread();
return RefPtr{new BackgroundVersionChangeTransactionChild(
mOpenRequestActor->GetOpenDBRequest())}
.forget();
}
mozilla::ipc::IPCResult
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(aActor->GetActorEventTarget(),
"The event target shall be inherited from its manager actor.");
MOZ_ASSERT(mOpenRequestActor);
MaybeCollectGarbageOnIPCMessage();
EnsureDOMObject();
auto* actor = static_cast<BackgroundVersionChangeTransactionChild*>(aActor);
// XXX NotNull might encapsulate this
const auto request =
WrapNotNullUnchecked(RefPtr{mOpenRequestActor->GetOpenDBRequest().get()});
SafeRefPtr<IDBTransaction> transaction = IDBTransaction::CreateVersionChange(
mDatabase, actor, request, aNextObjectStoreId, aNextIndexId);
MOZ_ASSERT(transaction);
transaction->AssertIsOnOwningThread();
actor->SetDOMTransaction(transaction.clonePtr());
const auto database = WrapNotNull(mDatabase);
database->EnterSetVersionTransaction(aRequestedVersion);
request->SetTransaction(transaction.clonePtr());
RefPtr<Event> upgradeNeededEvent = IDBVersionChangeEvent::Create(
request.get(), nsDependentString(kUpgradeNeededEventType),
aCurrentVersion, aRequestedVersion);
MOZ_ASSERT(upgradeNeededEvent);
SetResultAndDispatchSuccessEvent(
WrapNotNullUnchecked<RefPtr<IDBRequest>>(request.get()), transaction,
*database, std::move(upgradeNeededEvent));
return IPC_OK();
}
PBackgroundMutableFileChild*
BackgroundDatabaseChild::AllocPBackgroundMutableFileChild(
const nsString& aName, const nsString& aType) {
AssertIsOnOwningThread();
return new BackgroundMutableFileChild(aName, aType);
}
bool BackgroundDatabaseChild::DeallocPBackgroundMutableFileChild(
PBackgroundMutableFileChild* aActor) {
MOZ_ASSERT(aActor);
delete static_cast<BackgroundMutableFileChild*>(aActor);
return true;
}
mozilla::ipc::IPCResult BackgroundDatabaseChild::RecvVersionChange(
const uint64_t aOldVersion, const Maybe<uint64_t> aNewVersion) {
AssertIsOnOwningThread();
MaybeCollectGarbageOnIPCMessage();
if (!mDatabase || mDatabase->IsClosed()) {
return IPC_OK();
}
RefPtr<IDBDatabase> kungFuDeathGrip = mDatabase;
// Handle bfcache'd windows.
if (nsPIDOMWindowInner* 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<Document> 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 IPC_OK();
}
}
// Otherwise fire a versionchange event.
const nsDependentString type(kVersionChangeEventType);
RefPtr<Event> versionChangeEvent;
if (aNewVersion.isNothing()) {
versionChangeEvent =
IDBVersionChangeEvent::Create(kungFuDeathGrip, type, aOldVersion);
MOZ_ASSERT(versionChangeEvent);
} else {
versionChangeEvent = IDBVersionChangeEvent::Create(
kungFuDeathGrip, type, aOldVersion, aNewVersion.value());
MOZ_ASSERT(versionChangeEvent);
}
IDB_LOG_MARK("Child : Firing \"versionchange\" event",
"C: IDBDatabase \"versionchange\" event", IDB_LOG_ID_STRING());
IgnoredErrorResult rv;
kungFuDeathGrip->DispatchEvent(*versionChangeEvent, rv);
if (rv.Failed()) {
NS_WARNING("Failed to dispatch event!");
}
if (!kungFuDeathGrip->IsClosed()) {
SendBlocked();
}
return IPC_OK();
}
mozilla::ipc::IPCResult BackgroundDatabaseChild::RecvInvalidate() {
AssertIsOnOwningThread();
MaybeCollectGarbageOnIPCMessage();
if (mDatabase) {
mDatabase->Invalidate();
}
return IPC_OK();
}
mozilla::ipc::IPCResult
BackgroundDatabaseChild::RecvCloseAfterInvalidationComplete() {
AssertIsOnOwningThread();
MaybeCollectGarbageOnIPCMessage();
if (mDatabase) {
mDatabase->DispatchTrustedEvent(nsDependentString(kCloseEventType));
}
return IPC_OK();
}
/*******************************************************************************
* BackgroundDatabaseRequestChild
******************************************************************************/
BackgroundDatabaseRequestChild::BackgroundDatabaseRequestChild(
IDBDatabase* aDatabase, MovingNotNull<RefPtr<IDBRequest>> aRequest)
: BackgroundRequestChildBase(std::move(aRequest)), mDatabase(aDatabase) {
// Can't assert owning thread here because IPDL has not yet set our manager!
MOZ_ASSERT(aDatabase);
aDatabase->AssertIsOnOwningThread();
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();
SetResultAndDispatchSuccessEvent(mRequest, nullptr,
*WrapNotNull(static_cast<IDBMutableFile*>(
mutableFileActor->GetDOMObject())));
mutableFileActor->ReleaseDOMObject();
return true;
}
mozilla::ipc::IPCResult BackgroundDatabaseRequestChild::Recv__delete__(
const DatabaseRequestResponse& aResponse) {
AssertIsOnOwningThread();
switch (aResponse.type()) {
case DatabaseRequestResponse::Tnsresult:
if (!HandleResponse(aResponse.get_nsresult())) {
return IPC_FAIL_NO_REASON(this);
}
return IPC_OK();
case DatabaseRequestResponse::TCreateFileRequestResponse:
if (!HandleResponse(aResponse.get_CreateFileRequestResponse())) {
return IPC_FAIL_NO_REASON(this);
}
return IPC_OK();
default:
MOZ_CRASH("Unknown response type!");
}
MOZ_CRASH("Should never get here!");
}
/*******************************************************************************
* BackgroundTransactionBase
******************************************************************************/
BackgroundTransactionBase::