diff options
Diffstat (limited to 'toolkit/components/extensions/webidl-api/ExtensionEventListener.cpp')
-rw-r--r-- | toolkit/components/extensions/webidl-api/ExtensionEventListener.cpp | 677 |
1 files changed, 677 insertions, 0 deletions
diff --git a/toolkit/components/extensions/webidl-api/ExtensionEventListener.cpp b/toolkit/components/extensions/webidl-api/ExtensionEventListener.cpp new file mode 100644 index 0000000000..5ad9f2dfd8 --- /dev/null +++ b/toolkit/components/extensions/webidl-api/ExtensionEventListener.cpp @@ -0,0 +1,677 @@ +/* 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 "ExtensionEventListener.h" +#include "ExtensionAPIRequestForwarder.h" +#include "ExtensionPort.h" + +#include "mozilla/dom/ClonedErrorHolder.h" +#include "mozilla/dom/FunctionBinding.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/Promise-inl.h" +#include "nsThreadManager.h" // NS_IsMainThread + +namespace mozilla::extensions { + +namespace { + +class SendResponseCallback final : public nsISupports { + public: + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(SendResponseCallback) + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + + static RefPtr<SendResponseCallback> Create( + nsIGlobalObject* aGlobalObject, const RefPtr<dom::Promise>& aPromise, + JS::Handle<JS::Value> aValue, ErrorResult& aRv) { + MOZ_ASSERT(dom::IsCurrentThreadRunningWorker()); + + RefPtr<SendResponseCallback> responseCallback = + new SendResponseCallback(aPromise, aValue); + + // Create a promise monitor that invalidates the sendResponse + // callback if the promise has been already resolved or rejected. + aPromise->AddCallbacksWithCycleCollectedArgs( + [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv, + SendResponseCallback* aCallback) { aCallback->Cleanup(); }, + [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv, + SendResponseCallback* aCallback) { aCallback->Cleanup(); }, + responseCallback); + + auto cleanupCb = [responseCallback]() { responseCallback->Cleanup(); }; + + // Create a StrongWorkerRef to the worker thread, the cleanup callback + // associated to the StrongWorkerRef will release the reference and resolve + // the promise returned to the ExtensionEventListener caller with undefined + // if the worker global is being destroyed. + auto* workerPrivate = dom::GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + workerPrivate->AssertIsOnWorkerThread(); + + RefPtr<dom::StrongWorkerRef> workerRef = dom::StrongWorkerRef::Create( + workerPrivate, "SendResponseCallback", cleanupCb); + if (NS_WARN_IF(!workerRef)) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + responseCallback->mWorkerRef = workerRef; + + return responseCallback; + } + + SendResponseCallback(const RefPtr<dom::Promise>& aPromise, + JS::Handle<JS::Value> aValue) + : mPromise(aPromise), mValue(aValue) { + MOZ_ASSERT(mPromise); + mozilla::HoldJSObjects(this); + } + + void Cleanup(bool aIsDestroying = false) { + // Return earlier if the instance has already been cleaned up. + if (!mPromise) { + return; + } + + NS_WARNING("SendResponseCallback::Cleanup"); + + mPromise->MaybeResolveWithUndefined(); + mPromise = nullptr; + + // Skipped if called from the destructor. + if (!aIsDestroying && mValue.isObject()) { + // Release the reference to the SendResponseCallback. + js::SetFunctionNativeReserved(&mValue.toObject(), + SLOT_SEND_RESPONSE_CALLBACK_INSTANCE, + JS::PrivateValue(nullptr)); + } + + if (mWorkerRef) { + mWorkerRef = nullptr; + } + } + + static bool Call(JSContext* aCx, unsigned aArgc, JS::Value* aVp) { + JS::CallArgs args = CallArgsFromVp(aArgc, aVp); + JS::Rooted<JSObject*> callee(aCx, &args.callee()); + + JS::Value v = js::GetFunctionNativeReserved( + callee, SLOT_SEND_RESPONSE_CALLBACK_INSTANCE); + + SendResponseCallback* sendResponse = + reinterpret_cast<SendResponseCallback*>(v.toPrivate()); + if (!sendResponse || !sendResponse->mPromise || + !sendResponse->mPromise->PromiseObj()) { + NS_WARNING("SendResponseCallback called after being invalidated"); + return true; + } + + sendResponse->mPromise->MaybeResolve(args.get(0)); + sendResponse->Cleanup(); + + return true; + } + + private: + ~SendResponseCallback() { + mozilla::DropJSObjects(this); + this->Cleanup(true); + }; + + RefPtr<dom::Promise> mPromise; + JS::Heap<JS::Value> mValue; + RefPtr<dom::StrongWorkerRef> mWorkerRef; +}; + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SendResponseCallback) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION_CLASS(SendResponseCallback) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(SendResponseCallback) +NS_IMPL_CYCLE_COLLECTING_RELEASE(SendResponseCallback) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SendResponseCallback) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(SendResponseCallback) + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mValue) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SendResponseCallback) + tmp->mValue.setUndefined(); + // Resolve the promise with undefined (as "unhandled") before unlinking it. + if (tmp->mPromise) { + tmp->mPromise->MaybeResolveWithUndefined(); + } + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +} // anonymous namespace + +// ExtensionEventListener + +NS_IMPL_ISUPPORTS(ExtensionEventListener, mozIExtensionEventListener) + +// static +already_AddRefed<ExtensionEventListener> ExtensionEventListener::Create( + nsIGlobalObject* aGlobal, ExtensionBrowser* aExtensionBrowser, + dom::Function* aCallback, CleanupCallback&& aCleanupCallback, + ErrorResult& aRv) { + MOZ_ASSERT(dom::IsCurrentThreadRunningWorker()); + RefPtr<ExtensionEventListener> extCb = + new ExtensionEventListener(aGlobal, aExtensionBrowser, aCallback); + + auto* workerPrivate = dom::GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + workerPrivate->AssertIsOnWorkerThread(); + RefPtr<dom::StrongWorkerRef> workerRef = dom::StrongWorkerRef::Create( + workerPrivate, "ExtensionEventListener", std::move(aCleanupCallback)); + if (!workerRef) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + extCb->mWorkerRef = new dom::ThreadSafeWorkerRef(workerRef); + + return extCb.forget(); +} + +// static +UniquePtr<dom::StructuredCloneHolder> +ExtensionEventListener::SerializeCallArguments(const nsTArray<JS::Value>& aArgs, + JSContext* aCx, + ErrorResult& aRv) { + JS::Rooted<JS::Value> jsval(aCx); + if (NS_WARN_IF(!dom::ToJSValue(aCx, aArgs, &jsval))) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + UniquePtr<dom::StructuredCloneHolder> argsHolder = + MakeUnique<dom::StructuredCloneHolder>( + dom::StructuredCloneHolder::CloningSupported, + dom::StructuredCloneHolder::TransferringNotSupported, + JS::StructuredCloneScope::SameProcess); + + argsHolder->Write(aCx, jsval, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + return argsHolder; +} + +NS_IMETHODIMP ExtensionEventListener::CallListener( + const nsTArray<JS::Value>& aArgs, ListenerCallOptions* aCallOptions, + JSContext* aCx, dom::Promise** aPromiseResult) { + MOZ_ASSERT(NS_IsMainThread()); + NS_ENSURE_ARG_POINTER(aPromiseResult); + + // Process and validate call options. + APIObjectType apiObjectType = APIObjectType::NONE; + JS::Rooted<JS::Value> apiObjectDescriptor(aCx); + if (aCallOptions) { + aCallOptions->GetApiObjectType(&apiObjectType); + aCallOptions->GetApiObjectDescriptor(&apiObjectDescriptor); + + // Explicitly check that the APIObjectType is one of expected ones, + // raise to the caller an explicit error if it is not. + // + // This is using a switch to also get a warning if a new value is added to + // the APIObjectType enum and it is not yet handled. + switch (apiObjectType) { + case APIObjectType::NONE: + if (NS_WARN_IF(!apiObjectDescriptor.isNullOrUndefined())) { + JS_ReportErrorASCII( + aCx, + "Unexpected non-null apiObjectDescriptor on apiObjectType=NONE"); + return NS_ERROR_UNEXPECTED; + } + break; + case APIObjectType::RUNTIME_PORT: + if (NS_WARN_IF(apiObjectDescriptor.isNullOrUndefined())) { + JS_ReportErrorASCII(aCx, + "Unexpected null apiObjectDescriptor on " + "apiObjectType=RUNTIME_PORT"); + return NS_ERROR_UNEXPECTED; + } + break; + default: + MOZ_CRASH("Unexpected APIObjectType"); + return NS_ERROR_UNEXPECTED; + } + } + + // Create promise to be returned. + IgnoredErrorResult rv; + RefPtr<dom::Promise> retPromise; + + nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx); + if (NS_WARN_IF(!global)) { + return NS_ERROR_FAILURE; + } + retPromise = dom::Promise::Create(global, rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + + // Convert args into a non-const sequence. + dom::Sequence<JS::Value> args; + if (!args.AppendElements(aArgs, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // Execute the listener call. + + MutexAutoLock lock(mMutex); + + if (NS_WARN_IF(!mWorkerRef)) { + return NS_ERROR_ABORT; + } + + if (apiObjectType != APIObjectType::NONE) { + bool prependArgument = false; + aCallOptions->GetApiObjectPrepended(&prependArgument); + // Prepend or append the apiObjectDescriptor data to the call arguments, + // the worker runnable will convert that into an API object + // instance on the worker thread. + if (prependArgument) { + if (!args.InsertElementAt(0, std::move(apiObjectDescriptor), fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } else { + if (!args.AppendElement(std::move(apiObjectDescriptor), fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + } + + UniquePtr<dom::StructuredCloneHolder> argsHolder = + SerializeCallArguments(args, aCx, rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + + RefPtr<ExtensionListenerCallWorkerRunnable> runnable = + new ExtensionListenerCallWorkerRunnable(this, std::move(argsHolder), + aCallOptions, retPromise); + runnable->Dispatch(); + retPromise.forget(aPromiseResult); + + return NS_OK; +} + +dom::WorkerPrivate* ExtensionEventListener::GetWorkerPrivate() const { + MOZ_ASSERT(mWorkerRef); + return mWorkerRef->Private(); +} + +// ExtensionListenerCallWorkerRunnable + +void ExtensionListenerCallWorkerRunnable::DeserializeCallArguments( + JSContext* aCx, dom::Sequence<JS::Value>& aArgs, ErrorResult& aRv) { + JS::Rooted<JS::Value> jsvalue(aCx); + + mArgsHolder->Read(xpc::CurrentNativeGlobal(aCx), aCx, &jsvalue, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + nsresult rv2 = + ExtensionAPIRequestForwarder::JSArrayToSequence(aCx, jsvalue, aArgs); + if (NS_FAILED(rv2)) { + aRv.Throw(rv2); + } +} + +bool ExtensionListenerCallWorkerRunnable::WorkerRun( + JSContext* aCx, dom::WorkerPrivate* aWorkerPrivate) { + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + MOZ_ASSERT(aWorkerPrivate == mWorkerPrivate); + auto global = mListener->GetGlobalObject(); + if (NS_WARN_IF(!global)) { + return true; + } + + RefPtr<ExtensionBrowser> extensionBrowser = mListener->GetExtensionBrowser(); + if (NS_WARN_IF(!extensionBrowser)) { + return true; + } + + auto fn = mListener->GetCallback(); + if (NS_WARN_IF(!fn)) { + return true; + } + + IgnoredErrorResult rv; + dom::Sequence<JS::Value> argsSequence; + dom::SequenceRooter<JS::Value> arguments(aCx, &argsSequence); + + DeserializeCallArguments(aCx, argsSequence, rv); + if (NS_WARN_IF(rv.Failed())) { + return true; + } + + RefPtr<dom::Promise> retPromise; + RefPtr<dom::StrongWorkerRef> workerRef; + + retPromise = dom::Promise::Create(global, rv); + if (retPromise) { + workerRef = dom::StrongWorkerRef::Create( + aWorkerPrivate, "ExtensionListenerCallWorkerRunnable", []() {}); + } + + if (NS_WARN_IF(rv.Failed() || !workerRef)) { + auto rejectMainThreadPromise = + [error = rv.Failed() ? rv.StealNSResult() : NS_ERROR_UNEXPECTED, + promiseResult = std::move(mPromiseResult)]() { + // TODO(rpl): this seems to be currently rejecting an error object + // without a stack trace, its a corner case but we may look into + // improve this error. + promiseResult->MaybeReject(error); + }; + + nsCOMPtr<nsIRunnable> runnable = + NS_NewRunnableFunction(__func__, std::move(rejectMainThreadPromise)); + NS_DispatchToMainThread(runnable); + JS_ClearPendingException(aCx); + return true; + } + + ExtensionListenerCallPromiseResultHandler::Create( + retPromise, this, new dom::ThreadSafeWorkerRef(workerRef)); + + // Translate the first parameter into the API object type (e.g. an + // ExtensionPort), the content of the original argument value is expected to + // be a dictionary that is valid as an internal descriptor for that API object + // type. + if (mAPIObjectType != APIObjectType::NONE) { + IgnoredErrorResult rv; + + // The api object descriptor is expected to have been prepended to the + // other arguments, assert here that the argsSequence does contain at least + // one element. + MOZ_ASSERT(!argsSequence.IsEmpty()); + + uint32_t apiObjectIdx = mAPIObjectPrepended ? 0 : argsSequence.Length() - 1; + JS::Rooted<JS::Value> apiObjectDescriptor( + aCx, argsSequence.ElementAt(apiObjectIdx)); + JS::Rooted<JS::Value> apiObjectValue(aCx); + + // We only expect the object type to be RUNTIME_PORT at the moment, + // until we will need to expect it to support other object types that + // some specific API may need. + MOZ_ASSERT(mAPIObjectType == APIObjectType::RUNTIME_PORT); + RefPtr<ExtensionPort> port = + extensionBrowser->GetPort(apiObjectDescriptor, rv); + if (NS_WARN_IF(rv.Failed())) { + retPromise->MaybeReject(rv.StealNSResult()); + return true; + } + + if (NS_WARN_IF(!dom::ToJSValue(aCx, port, &apiObjectValue))) { + retPromise->MaybeReject(NS_ERROR_UNEXPECTED); + return true; + } + + argsSequence.ReplaceElementAt(apiObjectIdx, apiObjectValue); + } + + // Create callback argument and append it to the call arguments. + JS::Rooted<JSObject*> sendResponseObj(aCx); + + switch (mCallbackArgType) { + case CallbackType::CALLBACK_NONE: + break; + case CallbackType::CALLBACK_SEND_RESPONSE: { + JS::Rooted<JSFunction*> sendResponseFn( + aCx, js::NewFunctionWithReserved(aCx, SendResponseCallback::Call, + /* nargs */ 1, 0, "sendResponse")); + sendResponseObj = JS_GetFunctionObject(sendResponseFn); + JS::Rooted<JS::Value> sendResponseValue( + aCx, JS::ObjectValue(*sendResponseObj)); + + // Create a SendResponseCallback instance that keeps a reference + // to the promise to resolve when the static SendReponseCallback::Call + // is being called. + // the SendReponseCallback instance from the resolved slot to resolve + // the promise and invalidated the sendResponse callback (any new call + // becomes a noop). + RefPtr<SendResponseCallback> sendResponsePtr = + SendResponseCallback::Create(global, retPromise, sendResponseValue, + rv); + if (NS_WARN_IF(rv.Failed())) { + retPromise->MaybeReject(NS_ERROR_UNEXPECTED); + return true; + } + + // Store the SendResponseCallback instance in a private value set on the + // function object reserved slot, where ehe SendResponseCallback::Call + // static function will get it back to resolve the related promise + // and then invalidate the sendResponse callback (any new call + // becomes a noop). + js::SetFunctionNativeReserved(sendResponseObj, + SLOT_SEND_RESPONSE_CALLBACK_INSTANCE, + JS::PrivateValue(sendResponsePtr)); + + if (NS_WARN_IF( + !argsSequence.AppendElement(sendResponseValue, fallible))) { + retPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY); + return true; + } + + break; + } + default: + MOZ_ASSERT_UNREACHABLE("Unexpected callbackType"); + break; + } + + // TODO: should `nsAutoMicroTask mt;` be used here? + dom::AutoEntryScript aes(global, "WebExtensionAPIEvent"); + JS::Rooted<JS::Value> retval(aCx); + ErrorResult erv; + erv.MightThrowJSException(); + MOZ_KnownLive(fn)->Call(argsSequence, &retval, erv, "WebExtensionAPIEvent", + dom::Function::eRethrowExceptions); + + // Calling the callback may have thrown an exception. + // TODO: add a ListenerCallOptions to optionally report the exception + // instead of forwarding it to the caller. + erv.WouldReportJSException(); + + if (erv.Failed()) { + if (erv.IsUncatchableException()) { + // TODO: include some more info? (e.g. api path). + retPromise->MaybeRejectWithTimeoutError( + "WebExtensions API Event listener threw uncatchable exception"); + return true; + } + + retPromise->MaybeReject(std::move(erv)); + return true; + } + + // Custom return value handling logic for events that do pass a + // sendResponse callback parameter (see expected behavior + // for the runtime.onMessage sendResponse parameter on MDN: + // https://mzl.la/3dokpMi): + // + // - listener returns Boolean true => the extension listener is + // expected to call sendResponse callback parameter asynchronosuly + // - listener return a Promise object => the promise is the listener + // response + // - listener return any other value => the listener didn't handle the + // event and the return value is ignored + // + if (mCallbackArgType == CallbackType::CALLBACK_SEND_RESPONSE) { + if (retval.isBoolean() && retval.isTrue()) { + // The listener returned `true` and so the promise relate to the + // listener call will be resolved once the extension will call + // the sendResponce function passed as a callback argument. + return true; + } + + // If the retval isn't true and it is not a Promise object, + // the listener isn't handling the event, and we resolve the + // promise with undefined (if the listener didn't reply already + // by calling sendResponse synchronsouly). + // undefined ( + if (!ExtensionEventListener::IsPromise(aCx, retval)) { + // Mark this listener call as cancelled, + // ExtensionListenerCallPromiseResult will check to know that it should + // release the main thread promise without resolving it. + // + // TODO: double-check if we should also cancel rejecting the promise + // returned by mozIExtensionEventListener.callListener when the listener + // call throws (by comparing it with the behavior on the current + // privileged-based API implementation). + mIsCallResultCancelled = true; + retPromise->MaybeResolveWithUndefined(); + + // Invalidate the sendResponse function by setting the private + // value where the SendResponseCallback instance was stored + // to a nullptr. + js::SetFunctionNativeReserved(sendResponseObj, + SLOT_SEND_RESPONSE_CALLBACK_INSTANCE, + JS::PrivateValue(nullptr)); + + return true; + } + } + + retPromise->MaybeResolve(retval); + + return true; +} + +// ExtensionListenerCallPromiseResultHandler + +NS_IMPL_ISUPPORTS0(ExtensionListenerCallPromiseResultHandler) + +// static +void ExtensionListenerCallPromiseResultHandler::Create( + const RefPtr<dom::Promise>& aPromise, + const RefPtr<ExtensionListenerCallWorkerRunnable>& aWorkerRunnable, + dom::ThreadSafeWorkerRef* aWorkerRef) { + MOZ_ASSERT(aPromise); + MOZ_ASSERT(aWorkerRef); + MOZ_ASSERT(aWorkerRef->Private()->IsOnCurrentThread()); + + RefPtr<ExtensionListenerCallPromiseResultHandler> handler = + new ExtensionListenerCallPromiseResultHandler(aWorkerRef, + aWorkerRunnable); + aPromise->AppendNativeHandler(handler); +} + +void ExtensionListenerCallPromiseResultHandler::WorkerRunCallback( + JSContext* aCx, JS::Handle<JS::Value> aValue, + PromiseCallbackType aCallbackType) { + MOZ_ASSERT(mWorkerRef); + mWorkerRef->Private()->AssertIsOnWorkerThread(); + + // The listener call was cancelled (e.g. when a runtime.onMessage listener + // returned false), release resources associated with this promise handler + // on the main thread without resolving the promise associated to the + // extension event listener call. + if (mWorkerRunnable->IsCallResultCancelled()) { + auto releaseMainThreadPromise = [runnable = std::move(mWorkerRunnable), + workerRef = std::move(mWorkerRef)]() {}; + nsCOMPtr<nsIRunnable> runnable = + NS_NewRunnableFunction(__func__, std::move(releaseMainThreadPromise)); + NS_DispatchToMainThread(runnable); + return; + } + + JS::Rooted<JS::Value> retval(aCx, aValue); + + if (retval.isObject()) { + // Try to serialize the result as an ClonedErrorHolder, + // in case the value is an Error object. + IgnoredErrorResult rv; + JS::Rooted<JSObject*> errObj(aCx, &retval.toObject()); + UniquePtr<dom::ClonedErrorHolder> ceh = + dom::ClonedErrorHolder::Create(aCx, errObj, rv); + if (!rv.Failed() && ceh) { + Unused << NS_WARN_IF(!ToJSValue(aCx, std::move(ceh), &retval)); + } + } + + UniquePtr<dom::StructuredCloneHolder> resHolder = + MakeUnique<dom::StructuredCloneHolder>( + dom::StructuredCloneHolder::CloningSupported, + dom::StructuredCloneHolder::TransferringNotSupported, + JS::StructuredCloneScope::SameProcess); + + IgnoredErrorResult erv; + resHolder->Write(aCx, retval, erv); + + // Failed to serialize the result, dispatch a runnable to reject + // the promise returned to the caller of the mozIExtensionCallback + // callWithPromiseResult method. + if (NS_WARN_IF(erv.Failed())) { + auto rejectMainThreadPromise = [error = erv.StealNSResult(), + runnable = std::move(mWorkerRunnable), + resHolder = std::move(resHolder)]() { + RefPtr<dom::Promise> promiseResult = std::move(runnable->mPromiseResult); + promiseResult->MaybeReject(error); + }; + + nsCOMPtr<nsIRunnable> runnable = + NS_NewRunnableFunction(__func__, std::move(rejectMainThreadPromise)); + NS_DispatchToMainThread(runnable); + JS_ClearPendingException(aCx); + return; + } + + auto resolveMainThreadPromise = [callbackType = aCallbackType, + resHolder = std::move(resHolder), + runnable = std::move(mWorkerRunnable), + workerRef = std::move(mWorkerRef)]() { + RefPtr<dom::Promise> promiseResult = std::move(runnable->mPromiseResult); + + auto* global = promiseResult->GetGlobalObject(); + dom::AutoEntryScript aes(global, + "ExtensionListenerCallWorkerRunnable::WorkerRun"); + JSContext* cx = aes.cx(); + JS::Rooted<JS::Value> jsvalue(cx); + IgnoredErrorResult rv; + + resHolder->Read(global, cx, &jsvalue, rv); + + if (NS_WARN_IF(rv.Failed())) { + promiseResult->MaybeReject(rv.StealNSResult()); + JS_ClearPendingException(cx); + } else { + switch (callbackType) { + case PromiseCallbackType::Resolve: + promiseResult->MaybeResolve(jsvalue); + break; + case PromiseCallbackType::Reject: + promiseResult->MaybeReject(jsvalue); + break; + } + } + }; + + nsCOMPtr<nsIRunnable> runnable = + NS_NewRunnableFunction(__func__, std::move(resolveMainThreadPromise)); + NS_DispatchToMainThread(runnable); +} + +void ExtensionListenerCallPromiseResultHandler::ResolvedCallback( + JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) { + WorkerRunCallback(aCx, aValue, PromiseCallbackType::Resolve); +} + +void ExtensionListenerCallPromiseResultHandler::RejectedCallback( + JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) { + WorkerRunCallback(aCx, aValue, PromiseCallbackType::Reject); +} + +} // namespace mozilla::extensions |