diff options
Diffstat (limited to 'dom/base/DOMRequest.cpp')
-rw-r--r-- | dom/base/DOMRequest.cpp | 382 |
1 files changed, 382 insertions, 0 deletions
diff --git a/dom/base/DOMRequest.cpp b/dom/base/DOMRequest.cpp new file mode 100644 index 000000000..ce6cd1dcd --- /dev/null +++ b/dom/base/DOMRequest.cpp @@ -0,0 +1,382 @@ +/* -*- 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 "DOMRequest.h" + +#include "DOMError.h" +#include "nsThreadUtils.h" +#include "DOMCursor.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/ScriptSettings.h" +#include "jsfriendapi.h" +#include "nsContentUtils.h" + +using mozilla::dom::AnyCallback; +using mozilla::dom::DOMError; +using mozilla::dom::DOMRequest; +using mozilla::dom::DOMRequestService; +using mozilla::dom::DOMCursor; +using mozilla::dom::Promise; +using mozilla::dom::AutoJSAPI; +using mozilla::dom::RootingCx; + +DOMRequest::DOMRequest(nsPIDOMWindowInner* aWindow) + : DOMEventTargetHelper(aWindow) + , mResult(JS::UndefinedValue()) + , mDone(false) +{ +} + +DOMRequest::DOMRequest(nsIGlobalObject* aGlobal) + : DOMEventTargetHelper(aGlobal) + , mResult(JS::UndefinedValue()) + , mDone(false) +{ +} + +DOMRequest::~DOMRequest() +{ + mResult.setUndefined(); + mozilla::DropJSObjects(this); +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(DOMRequest) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DOMRequest, + DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mError) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DOMRequest, + DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mError) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise) + tmp->mResult.setUndefined(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(DOMRequest, + DOMEventTargetHelper) + // Don't need NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER because + // DOMEventTargetHelper does it for us. + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResult) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DOMRequest) + NS_INTERFACE_MAP_ENTRY(nsIDOMDOMRequest) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +NS_IMPL_ADDREF_INHERITED(DOMRequest, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(DOMRequest, DOMEventTargetHelper) + +/* virtual */ JSObject* +DOMRequest::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return DOMRequestBinding::Wrap(aCx, this, aGivenProto); +} + +NS_IMPL_EVENT_HANDLER(DOMRequest, success) +NS_IMPL_EVENT_HANDLER(DOMRequest, error) + +NS_IMETHODIMP +DOMRequest::GetReadyState(nsAString& aReadyState) +{ + DOMRequestReadyState readyState = ReadyState(); + switch (readyState) { + case DOMRequestReadyState::Pending: + aReadyState.AssignLiteral("pending"); + break; + case DOMRequestReadyState::Done: + aReadyState.AssignLiteral("done"); + break; + default: + MOZ_CRASH("Unrecognized readyState."); + } + + return NS_OK; +} + +NS_IMETHODIMP +DOMRequest::GetResult(JS::MutableHandle<JS::Value> aResult) +{ + GetResult(nullptr, aResult); + return NS_OK; +} + +NS_IMETHODIMP +DOMRequest::GetError(nsISupports** aError) +{ + NS_IF_ADDREF(*aError = GetError()); + return NS_OK; +} + +void +DOMRequest::FireSuccess(JS::Handle<JS::Value> aResult) +{ + NS_ASSERTION(!mDone, "mDone shouldn't have been set to true already!"); + NS_ASSERTION(!mError, "mError shouldn't have been set!"); + NS_ASSERTION(mResult.isUndefined(), "mResult shouldn't have been set!"); + + mDone = true; + if (aResult.isGCThing()) { + RootResultVal(); + } + mResult = aResult; + + FireEvent(NS_LITERAL_STRING("success"), false, false); + + if (mPromise) { + mPromise->MaybeResolve(mResult); + } +} + +void +DOMRequest::FireError(const nsAString& aError) +{ + NS_ASSERTION(!mDone, "mDone shouldn't have been set to true already!"); + NS_ASSERTION(!mError, "mError shouldn't have been set!"); + NS_ASSERTION(mResult.isUndefined(), "mResult shouldn't have been set!"); + + mDone = true; + mError = new DOMError(GetOwner(), aError); + + FireEvent(NS_LITERAL_STRING("error"), true, true); + + if (mPromise) { + mPromise->MaybeRejectBrokenly(mError); + } +} + +void +DOMRequest::FireError(nsresult aError) +{ + NS_ASSERTION(!mDone, "mDone shouldn't have been set to true already!"); + NS_ASSERTION(!mError, "mError shouldn't have been set!"); + NS_ASSERTION(mResult.isUndefined(), "mResult shouldn't have been set!"); + + mDone = true; + mError = new DOMError(GetOwner(), aError); + + FireEvent(NS_LITERAL_STRING("error"), true, true); + + if (mPromise) { + mPromise->MaybeRejectBrokenly(mError); + } +} + +void +DOMRequest::FireDetailedError(DOMError* aError) +{ + NS_ASSERTION(!mDone, "mDone shouldn't have been set to true already!"); + NS_ASSERTION(!mError, "mError shouldn't have been set!"); + NS_ASSERTION(mResult.isUndefined(), "mResult shouldn't have been set!"); + NS_ASSERTION(aError, "No detailed error provided"); + + mDone = true; + mError = aError; + + FireEvent(NS_LITERAL_STRING("error"), true, true); + + if (mPromise) { + mPromise->MaybeRejectBrokenly(mError); + } +} + +void +DOMRequest::FireEvent(const nsAString& aType, bool aBubble, bool aCancelable) +{ + if (NS_FAILED(CheckInnerWindowCorrectness())) { + return; + } + + RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr); + event->InitEvent(aType, aBubble, aCancelable); + event->SetTrusted(true); + + bool dummy; + DispatchEvent(event, &dummy); +} + +void +DOMRequest::RootResultVal() +{ + mozilla::HoldJSObjects(this); +} + +void +DOMRequest::Then(JSContext* aCx, AnyCallback* aResolveCallback, + AnyCallback* aRejectCallback, + JS::MutableHandle<JS::Value> aRetval, + mozilla::ErrorResult& aRv) +{ + if (!mPromise) { + mPromise = Promise::Create(DOMEventTargetHelper::GetParentObject(), aRv); + if (aRv.Failed()) { + return; + } + if (mDone) { + // Since we create mPromise lazily, it's possible that the DOMRequest object + // has already fired its success/error event. In that case we should + // manually resolve/reject mPromise here. mPromise will take care of + // calling the callbacks on |promise| as needed. + if (mError) { + mPromise->MaybeRejectBrokenly(mError); + } else { + mPromise->MaybeResolve(mResult); + } + } + } + + // Just use the global of the Promise itself as the callee global. + JS::Rooted<JSObject*> global(aCx, mPromise->PromiseObj()); + global = js::GetGlobalForObjectCrossCompartment(global); + mPromise->Then(aCx, global, aResolveCallback, aRejectCallback, aRetval, aRv); +} + +NS_IMPL_ISUPPORTS(DOMRequestService, nsIDOMRequestService) + +NS_IMETHODIMP +DOMRequestService::CreateRequest(mozIDOMWindow* aWindow, + nsIDOMDOMRequest** aRequest) +{ + MOZ_ASSERT(NS_IsMainThread()); + NS_ENSURE_STATE(aWindow); + auto* win = nsPIDOMWindowInner::From(aWindow); + NS_ADDREF(*aRequest = new DOMRequest(win)); + + return NS_OK; +} + +NS_IMETHODIMP +DOMRequestService::CreateCursor(mozIDOMWindow* aWindow, + nsICursorContinueCallback* aCallback, + nsIDOMDOMCursor** aCursor) +{ + NS_ENSURE_STATE(aWindow); + auto* win = nsPIDOMWindowInner::From(aWindow); + NS_ADDREF(*aCursor = new DOMCursor(win, aCallback)); + + return NS_OK; +} + +NS_IMETHODIMP +DOMRequestService::FireSuccess(nsIDOMDOMRequest* aRequest, + JS::Handle<JS::Value> aResult) +{ + NS_ENSURE_STATE(aRequest); + static_cast<DOMRequest*>(aRequest)->FireSuccess(aResult); + + return NS_OK; +} + +NS_IMETHODIMP +DOMRequestService::FireError(nsIDOMDOMRequest* aRequest, + const nsAString& aError) +{ + NS_ENSURE_STATE(aRequest); + static_cast<DOMRequest*>(aRequest)->FireError(aError); + + return NS_OK; +} + +NS_IMETHODIMP +DOMRequestService::FireDetailedError(nsIDOMDOMRequest* aRequest, + nsISupports* aError) +{ + NS_ENSURE_STATE(aRequest); + nsCOMPtr<DOMError> err = do_QueryInterface(aError); + NS_ENSURE_STATE(err); + static_cast<DOMRequest*>(aRequest)->FireDetailedError(err); + + return NS_OK; +} + +class FireSuccessAsyncTask : public mozilla::Runnable +{ + + FireSuccessAsyncTask(DOMRequest* aRequest, + const JS::Value& aResult) : + mReq(aRequest), + mResult(RootingCx(), aResult) + { + } + +public: + + // Due to the fact that initialization can fail during shutdown (since we + // can't fetch a js context), set up an initiatization function to make sure + // we can return the failure appropriately + static nsresult + Dispatch(DOMRequest* aRequest, + const JS::Value& aResult) + { + RefPtr<FireSuccessAsyncTask> asyncTask = + new FireSuccessAsyncTask(aRequest, aResult); + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(asyncTask)); + return NS_OK; + } + + NS_IMETHOD + Run() override + { + mReq->FireSuccess(JS::Handle<JS::Value>::fromMarkedLocation(mResult.address())); + return NS_OK; + } + +private: + RefPtr<DOMRequest> mReq; + JS::PersistentRooted<JS::Value> mResult; +}; + +class FireErrorAsyncTask : public mozilla::Runnable +{ +public: + FireErrorAsyncTask(DOMRequest* aRequest, + const nsAString& aError) : + mReq(aRequest), + mError(aError) + { + } + + NS_IMETHOD + Run() override + { + mReq->FireError(mError); + return NS_OK; + } +private: + RefPtr<DOMRequest> mReq; + nsString mError; +}; + +NS_IMETHODIMP +DOMRequestService::FireSuccessAsync(nsIDOMDOMRequest* aRequest, + JS::Handle<JS::Value> aResult) +{ + NS_ENSURE_STATE(aRequest); + return FireSuccessAsyncTask::Dispatch(static_cast<DOMRequest*>(aRequest), aResult); +} + +NS_IMETHODIMP +DOMRequestService::FireErrorAsync(nsIDOMDOMRequest* aRequest, + const nsAString& aError) +{ + NS_ENSURE_STATE(aRequest); + nsCOMPtr<nsIRunnable> asyncTask = + new FireErrorAsyncTask(static_cast<DOMRequest*>(aRequest), aError); + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(asyncTask)); + return NS_OK; +} + +NS_IMETHODIMP +DOMRequestService::FireDone(nsIDOMDOMCursor* aCursor) { + NS_ENSURE_STATE(aCursor); + static_cast<DOMCursor*>(aCursor)->FireDone(); + + return NS_OK; +} |