Skip to content

Commit

Permalink
Bug 1682632 - part2.3: ExtensionEventListener callListener promised r…
Browse files Browse the repository at this point in the history
…eturn value. r=baku

Depends on D80610

Differential Revision: https://phabricator.services.mozilla.com/D84684
  • Loading branch information
rpl committed Jun 11, 2021
1 parent a4e9a53 commit a7c04d8
Show file tree
Hide file tree
Showing 3 changed files with 278 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ interface mozIExtensionServiceWorkerInfo : nsISupports
[scriptable, builtinclass, uuid(e68e3c19-1b35-4112-8faa-5c5b84086a5b)]
interface mozIExtensionEventListener : nsISupports
{
[can_run_script]
void callListener(in Array<jsval> args);
[implicit_jscontext, can_run_script]
Promise callListener(in Array<jsval> args);
};

[scriptable, builtinclass, uuid(0fee1c8f-e363-46a6-bd0c-d3c3338e2534)]
Expand Down
220 changes: 203 additions & 17 deletions toolkit/components/extensions/webidl-api/ExtensionEventListener.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@
#include "ExtensionEventListener.h"

#include "mozilla/dom/FunctionBinding.h"
#include "nsJSPrincipals.h" // nsJSPrincipals::AutoSetActiveWorkerPrincipal
#include "nsThreadManager.h" // NS_IsMainThread

namespace mozilla {
namespace extensions {

// ExtensionEventListener

NS_IMPL_ISUPPORTS(ExtensionEventListener, mozIExtensionEventListener)

// static
Expand Down Expand Up @@ -39,13 +42,10 @@ already_AddRefed<ExtensionEventListener> ExtensionEventListener::Create(
// static
UniquePtr<dom::StructuredCloneHolder>
ExtensionEventListener::SerializeCallArguments(const nsTArray<JS::Value>& aArgs,
JSContext* aCx,
ErrorResult& aRv) {
dom::AutoEntryScript aes(xpc::PrivilegedJunkScope(),
"ExtensionEventListener :: CallListener");
JSContext* cx = aes.cx();
JS::Rooted<JS::Value> jsval(cx);

if (NS_WARN_IF(!dom::ToJSValue(cx, aArgs, &jsval))) {
JS::Rooted<JS::Value> jsval(aCx);
if (NS_WARN_IF(!dom::ToJSValue(aCx, aArgs, &jsval))) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
Expand All @@ -56,7 +56,7 @@ ExtensionEventListener::SerializeCallArguments(const nsTArray<JS::Value>& aArgs,
dom::StructuredCloneHolder::TransferringNotSupported,
JS::StructuredCloneScope::SameProcess);

argsHolder->Write(cx, jsval, aRv);
argsHolder->Write(aCx, jsval, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
Expand All @@ -65,25 +65,40 @@ ExtensionEventListener::SerializeCallArguments(const nsTArray<JS::Value>& aArgs,
}

NS_IMETHODIMP ExtensionEventListener::CallListener(
const nsTArray<JS::Value>& aArgs) {
const nsTArray<JS::Value>& aArgs, JSContext* aCx,
dom::Promise** aPromiseResult) {
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_ARG_POINTER(aPromiseResult);

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();
}

MutexAutoLock lock(mMutex);

if (NS_WARN_IF(!mWorkerRef)) {
return NS_ERROR_ABORT;
}

ErrorResult rv;
UniquePtr<dom::StructuredCloneHolder> argsHolder =
SerializeCallArguments(aArgs, rv);
SerializeCallArguments(aArgs, aCx, rv);
if (NS_WARN_IF(rv.Failed())) {
return rv.StealNSResult();
}

RefPtr<ExtensionListenerCallWorkerRunnable> runnable =
new ExtensionListenerCallWorkerRunnable(this, std::move(argsHolder));
new ExtensionListenerCallWorkerRunnable(this, std::move(argsHolder),
retPromise);
runnable->Dispatch();
retPromise.forget(aPromiseResult);

return NS_OK;
}
Expand All @@ -93,6 +108,8 @@ dom::WorkerPrivate* ExtensionEventListener::GetWorkerPrivate() const {
return mWorkerRef->Private();
}

// ExtensionListenerCallWorkerRunnable

void ExtensionListenerCallWorkerRunnable::DeserializeCallArguments(
JSContext* aCx, dom::Sequence<JS::Value>& aArgs, ErrorResult& aRv) {
JS::Rooted<JS::Value> jsvalue(aCx);
Expand Down Expand Up @@ -124,7 +141,7 @@ bool ExtensionListenerCallWorkerRunnable::WorkerRun(
return true;
}

ErrorResult rv;
IgnoredErrorResult rv;
dom::Sequence<JS::Value> argsSequence;
dom::SequenceRooter<JS::Value> arguments(aCx, &argsSequence);

Expand All @@ -133,11 +150,180 @@ bool ExtensionListenerCallWorkerRunnable::WorkerRun(
return true;
}

JS::Rooted<JS::Value> ignoredRetval(aCx);
MOZ_KnownLive(fn)->Call(MOZ_KnownLive(global), argsSequence, &ignoredRetval,
rv);
// Return false if calling the callback did throw an uncatchable exception.
return !rv.IsUncatchableException();
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;
}

// 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()) {
retPromise->MaybeReject(std::move(erv));
} else {
retPromise->MaybeResolve(retval);
}

ExtensionListenerCallPromiseResultHandler::Create(
retPromise, this, new dom::ThreadSafeWorkerRef(workerRef));
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();

JS::RootedValue 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());
RefPtr<dom::ClonedErrorHolder> ceh =
dom::ClonedErrorHolder::Create(aCx, errObj, rv);
if (!rv.Failed() && ceh) {
JS::RootedObject obj(aCx);
// Note: `ToJSValue` cannot be used because ClonedErrorHolder isn't
// wrapper cached.
Unused << NS_WARN_IF(!ceh->WrapObject(aCx, nullptr, &obj));
retval.setObject(*obj);
}
}

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;

{
// Set the active worker principal while reading the result,
// needed to be sure to be able to successfully deserialize the
// SavedFrame part of a ClonedErrorHolder (in case that was the
// result stored in the StructuredCloneHolder).
Maybe<nsJSPrincipals::AutoSetActiveWorkerPrincipal> set;
if (workerRef) {
set.emplace(workerRef->Private()->GetPrincipal());
}

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) {
WorkerRunCallback(aCx, aValue, PromiseCallbackType::Resolve);
}

void ExtensionListenerCallPromiseResultHandler::RejectedCallback(
JSContext* aCx, JS::Handle<JS::Value> aValue) {
WorkerRunCallback(aCx, aValue, PromiseCallbackType::Reject);
}

} // namespace extensions
Expand Down
Loading

0 comments on commit a7c04d8

Please sign in to comment.