gecko-dev/dom/indexedDB/IDBRequest.cpp

663 строки
16 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 "IDBRequest.h"
#include "BackgroundChildImpl.h"
#include "IDBCursor.h"
#include "IDBDatabase.h"
#include "IDBEvents.h"
#include "IDBFactory.h"
#include "IDBIndex.h"
#include "IDBObjectStore.h"
#include "IDBTransaction.h"
#include "IndexedDatabaseManager.h"
#include "mozilla/ContentEvents.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/Move.h"
#include "mozilla/dom/DOMError.h"
#include "mozilla/dom/ErrorEventBinding.h"
#include "mozilla/dom/IDBOpenDBRequestBinding.h"
#include "mozilla/dom/ScriptSettings.h"
#include "nsCOMPtr.h"
#include "nsContentUtils.h"
#include "nsIScriptContext.h"
#include "nsJSUtils.h"
#include "nsPIDOMWindow.h"
#include "nsString.h"
#include "ReportInternalError.h"
#include "WorkerHolder.h"
#include "WorkerPrivate.h"
// Include this last to avoid path problems on Windows.
#include "ActorsChild.h"
namespace mozilla {
namespace dom {
using namespace mozilla::dom::indexedDB;
using namespace mozilla::dom::workers;
using namespace mozilla::ipc;
namespace {
NS_DEFINE_IID(kIDBRequestIID, PRIVATE_IDBREQUEST_IID);
} // namespace
IDBRequest::IDBRequest(IDBDatabase* aDatabase)
: IDBWrapperCache(aDatabase)
#ifdef DEBUG
, mOwningThread(nullptr)
#endif
, mLoggingSerialNumber(0)
, mLineNo(0)
, mColumn(0)
, mHaveResultOrErrorCode(false)
{
MOZ_ASSERT(aDatabase);
aDatabase->AssertIsOnOwningThread();
InitMembers();
}
IDBRequest::IDBRequest(nsPIDOMWindowInner* aOwner)
: IDBWrapperCache(aOwner)
#ifdef DEBUG
, mOwningThread(nullptr)
#endif
, mLoggingSerialNumber(0)
, mLineNo(0)
, mColumn(0)
, mHaveResultOrErrorCode(false)
{
InitMembers();
}
IDBRequest::~IDBRequest()
{
AssertIsOnOwningThread();
}
#ifdef DEBUG
void
IDBRequest::AssertIsOnOwningThread() const
{
MOZ_ASSERT(mOwningThread);
MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
}
#endif // DEBUG
void
IDBRequest::InitMembers()
{
#ifdef DEBUG
mOwningThread = PR_GetCurrentThread();
#endif
AssertIsOnOwningThread();
mResultVal.setUndefined();
mLoggingSerialNumber = NextSerialNumber();
mErrorCode = NS_OK;
mLineNo = 0;
mColumn = 0;
mHaveResultOrErrorCode = false;
}
// static
already_AddRefed<IDBRequest>
IDBRequest::Create(JSContext* aCx,
IDBDatabase* aDatabase,
IDBTransaction* aTransaction)
{
MOZ_ASSERT(aCx);
MOZ_ASSERT(aDatabase);
aDatabase->AssertIsOnOwningThread();
RefPtr<IDBRequest> request = new IDBRequest(aDatabase);
CaptureCaller(aCx, request->mFilename, &request->mLineNo, &request->mColumn);
request->mTransaction = aTransaction;
request->SetScriptOwner(aDatabase->GetScriptOwner());
return request.forget();
}
// static
already_AddRefed<IDBRequest>
IDBRequest::Create(JSContext* aCx,
IDBObjectStore* aSourceAsObjectStore,
IDBDatabase* aDatabase,
IDBTransaction* aTransaction)
{
MOZ_ASSERT(aSourceAsObjectStore);
aSourceAsObjectStore->AssertIsOnOwningThread();
RefPtr<IDBRequest> request = Create(aCx, aDatabase, aTransaction);
request->mSourceAsObjectStore = aSourceAsObjectStore;
return request.forget();
}
// static
already_AddRefed<IDBRequest>
IDBRequest::Create(JSContext* aCx,
IDBIndex* aSourceAsIndex,
IDBDatabase* aDatabase,
IDBTransaction* aTransaction)
{
MOZ_ASSERT(aSourceAsIndex);
aSourceAsIndex->AssertIsOnOwningThread();
RefPtr<IDBRequest> request = Create(aCx, aDatabase, aTransaction);
request->mSourceAsIndex = aSourceAsIndex;
return request.forget();
}
// static
uint64_t
IDBRequest::NextSerialNumber()
{
BackgroundChildImpl::ThreadLocal* threadLocal =
BackgroundChildImpl::GetThreadLocalForCurrentThread();
MOZ_ASSERT(threadLocal);
ThreadLocal* idbThreadLocal = threadLocal->mIndexedDBThreadLocal;
MOZ_ASSERT(idbThreadLocal);
return idbThreadLocal->NextRequestSN();
}
void
IDBRequest::SetLoggingSerialNumber(uint64_t aLoggingSerialNumber)
{
AssertIsOnOwningThread();
MOZ_ASSERT(aLoggingSerialNumber > mLoggingSerialNumber);
mLoggingSerialNumber = aLoggingSerialNumber;
}
void
IDBRequest::CaptureCaller(JSContext* aCx, nsAString& aFilename,
uint32_t* aLineNo, uint32_t* aColumn)
{
MOZ_ASSERT(aFilename.IsEmpty());
MOZ_ASSERT(aLineNo);
MOZ_ASSERT(aColumn);
nsJSUtils::GetCallingLocation(aCx, aFilename, aLineNo, aColumn);
}
void
IDBRequest::GetSource(
Nullable<OwningIDBObjectStoreOrIDBIndexOrIDBCursor>& aSource) const
{
AssertIsOnOwningThread();
MOZ_ASSERT_IF(mSourceAsObjectStore, !mSourceAsIndex);
MOZ_ASSERT_IF(mSourceAsIndex, !mSourceAsObjectStore);
MOZ_ASSERT_IF(mSourceAsCursor, mSourceAsObjectStore || mSourceAsIndex);
// Always check cursor first since cursor requests hold both the cursor and
// the objectStore or index the cursor came from.
if (mSourceAsCursor) {
aSource.SetValue().SetAsIDBCursor() = mSourceAsCursor;
} else if (mSourceAsObjectStore) {
aSource.SetValue().SetAsIDBObjectStore() = mSourceAsObjectStore;
} else if (mSourceAsIndex) {
aSource.SetValue().SetAsIDBIndex() = mSourceAsIndex;
} else {
aSource.SetNull();
}
}
void
IDBRequest::Reset()
{
AssertIsOnOwningThread();
mResultVal.setUndefined();
mHaveResultOrErrorCode = false;
mError = nullptr;
}
void
IDBRequest::DispatchNonTransactionError(nsresult aErrorCode)
{
AssertIsOnOwningThread();
MOZ_ASSERT(NS_FAILED(aErrorCode));
MOZ_ASSERT(NS_ERROR_GET_MODULE(aErrorCode) == NS_ERROR_MODULE_DOM_INDEXEDDB);
SetError(aErrorCode);
// Make an error event and fire it at the target.
nsCOMPtr<nsIDOMEvent> event =
CreateGenericEvent(this,
nsDependentString(kErrorEventType),
eDoesBubble,
eCancelable);
MOZ_ASSERT(event);
bool ignored;
if (NS_FAILED(DispatchEvent(event, &ignored))) {
NS_WARNING("Failed to dispatch event!");
}
}
void
IDBRequest::SetError(nsresult aRv)
{
AssertIsOnOwningThread();
MOZ_ASSERT(NS_FAILED(aRv));
MOZ_ASSERT(NS_ERROR_GET_MODULE(aRv) == NS_ERROR_MODULE_DOM_INDEXEDDB);
MOZ_ASSERT(!mError);
mHaveResultOrErrorCode = true;
mError = new DOMError(GetOwner(), aRv);
mErrorCode = aRv;
mResultVal.setUndefined();
}
#ifdef DEBUG
nsresult
IDBRequest::GetErrorCode() const
{
AssertIsOnOwningThread();
MOZ_ASSERT(mHaveResultOrErrorCode);
return mErrorCode;
}
DOMError*
IDBRequest::GetErrorAfterResult() const
{
AssertIsOnOwningThread();
MOZ_ASSERT(mHaveResultOrErrorCode);
return mError;
}
#endif // DEBUG
void
IDBRequest::GetCallerLocation(nsAString& aFilename, uint32_t* aLineNo,
uint32_t* aColumn) const
{
AssertIsOnOwningThread();
MOZ_ASSERT(aLineNo);
MOZ_ASSERT(aColumn);
aFilename = mFilename;
*aLineNo = mLineNo;
*aColumn = mColumn;
}
IDBRequestReadyState
IDBRequest::ReadyState() const
{
AssertIsOnOwningThread();
return IsPending() ?
IDBRequestReadyState::Pending :
IDBRequestReadyState::Done;
}
void
IDBRequest::SetSource(IDBCursor* aSource)
{
AssertIsOnOwningThread();
MOZ_ASSERT(aSource);
MOZ_ASSERT(mSourceAsObjectStore || mSourceAsIndex);
MOZ_ASSERT(!mSourceAsCursor);
mSourceAsCursor = aSource;
}
JSObject*
IDBRequest::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return IDBRequestBinding::Wrap(aCx, this, aGivenProto);
}
void
IDBRequest::GetResult(JS::MutableHandle<JS::Value> aResult,
ErrorResult& aRv) const
{
AssertIsOnOwningThread();
if (!mHaveResultOrErrorCode) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
JS::ExposeValueToActiveJS(mResultVal);
aResult.set(mResultVal);
}
void
IDBRequest::SetResultCallback(ResultCallback* aCallback)
{
AssertIsOnOwningThread();
MOZ_ASSERT(aCallback);
MOZ_ASSERT(!mHaveResultOrErrorCode);
MOZ_ASSERT(mResultVal.isUndefined());
MOZ_ASSERT(!mError);
// See if our window is still valid.
if (NS_WARN_IF(NS_FAILED(CheckInnerWindowCorrectness()))) {
SetError(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
return;
}
AutoJSAPI autoJS;
Maybe<JSAutoCompartment> ac;
if (GetScriptOwner()) {
// If we have a script owner we want the SafeJSContext and then to enter the
// script owner's compartment.
autoJS.Init();
ac.emplace(autoJS.cx(), GetScriptOwner());
} else {
// Otherwise our owner is a window and we use that to initialize.
MOZ_ASSERT(GetOwner());
if (!autoJS.Init(GetOwner())) {
IDB_WARNING("Failed to initialize AutoJSAPI!");
SetError(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
return;
}
}
JSContext* cx = autoJS.cx();
AssertIsRooted();
JS::Rooted<JS::Value> result(cx);
nsresult rv = aCallback->GetResult(cx, &result);
if (NS_WARN_IF(NS_FAILED(rv))) {
// This can only fail if the structured clone contains a mutable file
// and the child is not in the main thread and main process.
// In that case CreateAndWrapMutableFile() returns false which shows up
// as NS_ERROR_DOM_DATA_CLONE_ERR here.
MOZ_ASSERT(rv == NS_ERROR_DOM_DATA_CLONE_ERR);
// We are not setting a result or an error object here since we want to
// throw an exception when the 'result' property is being touched.
return;
}
mError = nullptr;
mResultVal = result;
mHaveResultOrErrorCode = true;
}
DOMError*
IDBRequest::GetError(ErrorResult& aRv)
{
AssertIsOnOwningThread();
if (!mHaveResultOrErrorCode) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr;
}
return mError;
}
NS_IMPL_CYCLE_COLLECTION_CLASS(IDBRequest)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(IDBRequest, IDBWrapperCache)
// Don't need NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS because
// DOMEventTargetHelper does it for us.
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourceAsObjectStore)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourceAsIndex)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourceAsCursor)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTransaction)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mError)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(IDBRequest, IDBWrapperCache)
tmp->mResultVal.setUndefined();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourceAsObjectStore)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourceAsIndex)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourceAsCursor)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mTransaction)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mError)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(IDBRequest, IDBWrapperCache)
// Don't need NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER because
// DOMEventTargetHelper does it for us.
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultVal)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(IDBRequest)
if (aIID.Equals(kIDBRequestIID)) {
foundInterface = this;
} else
NS_INTERFACE_MAP_END_INHERITING(IDBWrapperCache)
NS_IMPL_ADDREF_INHERITED(IDBRequest, IDBWrapperCache)
NS_IMPL_RELEASE_INHERITED(IDBRequest, IDBWrapperCache)
nsresult
IDBRequest::PreHandleEvent(EventChainPreVisitor& aVisitor)
{
AssertIsOnOwningThread();
aVisitor.mCanHandle = true;
aVisitor.mParentTarget = mTransaction;
return NS_OK;
}
class IDBOpenDBRequest::WorkerHolder final
: public mozilla::dom::workers::WorkerHolder
{
WorkerPrivate* mWorkerPrivate;
#ifdef DEBUG
// This is only here so that assertions work in the destructor even if
// NoteAddWorkerHolderFailed was called.
WorkerPrivate* mWorkerPrivateDEBUG;
#endif
public:
explicit
WorkerHolder(WorkerPrivate* aWorkerPrivate)
: mWorkerPrivate(aWorkerPrivate)
#ifdef DEBUG
, mWorkerPrivateDEBUG(aWorkerPrivate)
#endif
{
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
MOZ_COUNT_CTOR(IDBOpenDBRequest::WorkerHolder);
}
~WorkerHolder()
{
#ifdef DEBUG
mWorkerPrivateDEBUG->AssertIsOnWorkerThread();
#endif
MOZ_COUNT_DTOR(IDBOpenDBRequest::WorkerHolder);
}
void
NoteAddWorkerHolderFailed()
{
MOZ_ASSERT(mWorkerPrivate);
mWorkerPrivate->AssertIsOnWorkerThread();
mWorkerPrivate = nullptr;
}
private:
virtual bool
Notify(Status aStatus) override;
};
IDBOpenDBRequest::IDBOpenDBRequest(IDBFactory* aFactory,
nsPIDOMWindowInner* aOwner,
bool aFileHandleDisabled)
: IDBRequest(aOwner)
, mFactory(aFactory)
, mFileHandleDisabled(aFileHandleDisabled)
{
AssertIsOnOwningThread();
MOZ_ASSERT(aFactory);
// aOwner may be null.
}
IDBOpenDBRequest::~IDBOpenDBRequest()
{
AssertIsOnOwningThread();
}
// static
already_AddRefed<IDBOpenDBRequest>
IDBOpenDBRequest::CreateForWindow(JSContext* aCx,
IDBFactory* aFactory,
nsPIDOMWindowInner* aOwner,
JS::Handle<JSObject*> aScriptOwner)
{
MOZ_ASSERT(aFactory);
aFactory->AssertIsOnOwningThread();
MOZ_ASSERT(aOwner);
MOZ_ASSERT(aScriptOwner);
bool fileHandleDisabled = !IndexedDatabaseManager::IsFileHandleEnabled();
RefPtr<IDBOpenDBRequest> request =
new IDBOpenDBRequest(aFactory, aOwner, fileHandleDisabled);
CaptureCaller(aCx, request->mFilename, &request->mLineNo, &request->mColumn);
request->SetScriptOwner(aScriptOwner);
return request.forget();
}
// static
already_AddRefed<IDBOpenDBRequest>
IDBOpenDBRequest::CreateForJS(JSContext* aCx,
IDBFactory* aFactory,
JS::Handle<JSObject*> aScriptOwner)
{
MOZ_ASSERT(aFactory);
aFactory->AssertIsOnOwningThread();
MOZ_ASSERT(aScriptOwner);
bool fileHandleDisabled = !IndexedDatabaseManager::IsFileHandleEnabled();
RefPtr<IDBOpenDBRequest> request =
new IDBOpenDBRequest(aFactory, nullptr, fileHandleDisabled);
CaptureCaller(aCx, request->mFilename, &request->mLineNo, &request->mColumn);
request->SetScriptOwner(aScriptOwner);
if (!NS_IsMainThread()) {
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(workerPrivate);
workerPrivate->AssertIsOnWorkerThread();
nsAutoPtr<WorkerHolder> workerHolder(new WorkerHolder(workerPrivate));
if (NS_WARN_IF(!workerHolder->HoldWorker(workerPrivate))) {
workerHolder->NoteAddWorkerHolderFailed();
return nullptr;
}
request->mWorkerHolder = Move(workerHolder);
}
return request.forget();
}
void
IDBOpenDBRequest::SetTransaction(IDBTransaction* aTransaction)
{
AssertIsOnOwningThread();
MOZ_ASSERT(!aTransaction || !mTransaction);
mTransaction = aTransaction;
}
void
IDBOpenDBRequest::NoteComplete()
{
AssertIsOnOwningThread();
MOZ_ASSERT_IF(!NS_IsMainThread(), mWorkerHolder);
// If we have a WorkerHolder installed on the worker then nulling this out
// will uninstall it from the worker.
mWorkerHolder = nullptr;
}
NS_IMPL_CYCLE_COLLECTION_CLASS(IDBOpenDBRequest)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(IDBOpenDBRequest,
IDBRequest)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFactory)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(IDBOpenDBRequest,
IDBRequest)
// Don't unlink mFactory!
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(IDBOpenDBRequest)
NS_INTERFACE_MAP_END_INHERITING(IDBRequest)
NS_IMPL_ADDREF_INHERITED(IDBOpenDBRequest, IDBRequest)
NS_IMPL_RELEASE_INHERITED(IDBOpenDBRequest, IDBRequest)
nsresult
IDBOpenDBRequest::PostHandleEvent(EventChainPostVisitor& aVisitor)
{
nsresult rv =
IndexedDatabaseManager::CommonPostHandleEvent(aVisitor, mFactory);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
JSObject*
IDBOpenDBRequest::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
AssertIsOnOwningThread();
return IDBOpenDBRequestBinding::Wrap(aCx, this, aGivenProto);
}
bool
IDBOpenDBRequest::
WorkerHolder::Notify(Status aStatus)
{
MOZ_ASSERT(mWorkerPrivate);
mWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(aStatus > Running);
// There's nothing we can really do here at the moment...
NS_WARNING("Worker closing but IndexedDB is waiting to open a database!");
return true;
}
} // namespace dom
} // namespace mozilla