Skip to content

Commit

Permalink
Bug 1519100 - Use pref to completely pref-off top-level await in the …
Browse files Browse the repository at this point in the history
…browser; r=emilio,jonco

Differential Revision: https://phabricator.services.mozilla.com/D97184
  • Loading branch information
codehag committed Dec 3, 2020
1 parent 4883191 commit f710026
Show file tree
Hide file tree
Showing 13 changed files with 366 additions and 119 deletions.
13 changes: 9 additions & 4 deletions dom/base/nsJSUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,18 +173,23 @@ nsresult nsJSUtils::ModuleInstantiate(JSContext* aCx,
return NS_OK;
}

JSObject* nsJSUtils::ModuleEvaluate(JSContext* aCx,
JS::Handle<JSObject*> aModule) {
nsresult nsJSUtils::ModuleEvaluate(JSContext* aCx,
JS::Handle<JSObject*> aModule,
JS::MutableHandle<JS::Value> aResult) {
AUTO_PROFILER_LABEL("nsJSUtils::ModuleEvaluate", JS);

MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(CycleCollectedJSContext::Get() &&
CycleCollectedJSContext::Get()->MicroTaskLevel());

NS_ENSURE_TRUE(xpc::Scriptability::Get(aModule).Allowed(), nullptr);
NS_ENSURE_TRUE(xpc::Scriptability::Get(aModule).Allowed(), NS_OK);

return JS::ModuleEvaluate(aCx, aModule);
if (!JS::ModuleEvaluate(aCx, aModule, aResult)) {
return NS_ERROR_FAILURE;
}

return NS_OK;
}

static bool AddScopeChainItem(JSContext* aCx, nsINode* aNode,
Expand Down
11 changes: 7 additions & 4 deletions dom/base/nsJSUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,14 @@ class nsJSUtils {
* The JSContext where this is executed.
* @param JS::Handle<JSObject*> aModule
* The module to be evaluated.
* @returns JS::MutableHandle<JSObject*> aEvaluationPromise
* The evaluaation promise returned from evaluating the module.
* @param JS::Handle<Value*> aResult
* If Top level await is enabled:
* The evaluation promise returned from evaluating the module.
* Otherwise:
* Undefined
*/
static JSObject* ModuleEvaluate(JSContext* aCx,
JS::Handle<JSObject*> aModule);
static nsresult ModuleEvaluate(JSContext* aCx, JS::Handle<JSObject*> aModule,
JS::MutableHandle<JS::Value> aResult);

// Returns false if an exception got thrown on aCx. Passing a null
// aElement is allowed; that wil produce an empty aScopeChain.
Expand Down
116 changes: 98 additions & 18 deletions dom/script/ScriptLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1062,7 +1062,51 @@ void ScriptLoader::FinishDynamicImportAndReject(ModuleLoadRequest* aRequest,
AutoJSAPI jsapi;
MOZ_ASSERT(NS_FAILED(aResult));
MOZ_ALWAYS_TRUE(jsapi.Init(aRequest->mDynamicPromise));
FinishDynamicImport(jsapi.cx(), aRequest, aResult, nullptr);
if (!JS::ContextOptionsRef(jsapi.cx()).topLevelAwait()) {
// This is used so that Top Level Await functionality can be turned off
// entirely. It will be removed in bug#1676612.
FinishDynamicImport_NoTLA(jsapi.cx(), aRequest, aResult);
} else {
// Path for when Top Level Await is enabled.
FinishDynamicImport(jsapi.cx(), aRequest, aResult, nullptr);
}
}

// This is used so that Top Level Await functionality can be turned off
// entirely. It will be removed in bug#1676612.
void ScriptLoader::FinishDynamicImport_NoTLA(JSContext* aCx,
ModuleLoadRequest* aRequest,
nsresult aResult) {
LOG(("ScriptLoadRequest (%p): Finish dynamic import %x %d", aRequest,
unsigned(aResult), JS_IsExceptionPending(aCx)));

// Complete the dynamic import, report failures indicated by aResult or as a
// pending exception on the context.

JS::DynamicImportStatus status =
(NS_FAILED(aResult) || JS_IsExceptionPending(aCx))
? JS::DynamicImportStatus::Failed
: JS::DynamicImportStatus::Ok;

if (NS_FAILED(aResult) &&
aResult != NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE) {
MOZ_ASSERT(!JS_IsExceptionPending(aCx));
JS_ReportErrorNumberUC(aCx, js::GetErrorMessage, nullptr,
JSMSG_DYNAMIC_IMPORT_FAILED);
}

JS::Rooted<JS::Value> referencingScript(aCx,
aRequest->mDynamicReferencingPrivate);
JS::Rooted<JSString*> specifier(aCx, aRequest->mDynamicSpecifier);
JS::Rooted<JSObject*> promise(aCx, aRequest->mDynamicPromise);

JS::FinishDynamicModuleImport_NoTLA(aCx, status, referencingScript, specifier,
promise);

// FinishDynamicModuleImport clears any pending exception.
MOZ_ASSERT(!JS_IsExceptionPending(aCx));

aRequest->ClearDynamicImport();
}

void ScriptLoader::FinishDynamicImport(
Expand Down Expand Up @@ -2937,12 +2981,30 @@ nsresult ScriptLoader::EvaluateScript(ScriptLoadRequest* aRequest) {
LOG(("ScriptLoadRequest (%p): module has error to rethrow",
aRequest));
JS::Rooted<JS::Value> error(cx, moduleScript->ErrorToRethrow());
JS_SetPendingException(cx, error);

// For a dynamic import, the promise is rejected. Otherwise an error is
// either reported by AutoEntryScript.
if (request->IsDynamicImport()) {
FinishDynamicImport(cx, request, NS_OK, nullptr);
if (!JS::ContextOptionsRef(cx).topLevelAwait()) {
JS_SetPendingException(cx, error);
// For a dynamic import, the promise is rejected. Otherwise an error
// is either reported by AutoEntryScript.
if (request->IsDynamicImport()) {
FinishDynamicImport_NoTLA(cx, request, NS_OK);
}
} else {
ErrorResult err;
RefPtr<Promise> aPromise = Promise::Create(globalObject, err);
if (NS_WARN_IF(err.Failed())) {
return err.StealNSResult();
}
aPromise->MaybeReject(error);
JS::Rooted<JSObject*> aEvaluationPromise(cx, aPromise->PromiseObj());
if (request->IsDynamicImport()) {
FinishDynamicImport(cx, request, NS_OK, aEvaluationPromise);
} else {
if (!JS::ThrowOnModuleEvaluationFailure(cx, aEvaluationPromise)) {
LOG(("ScriptLoadRequest (%p): evaluation failed", aRequest));
// For a dynamic import, the promise is rejected. Otherwise an
// error is either reported by AutoEntryScript.
}
}
}
return NS_OK;
}
Expand All @@ -2955,19 +3017,37 @@ nsresult ScriptLoader::EvaluateScript(ScriptLoadRequest* aRequest) {

TRACE_FOR_TEST(aRequest->GetScriptElement(),
"scriptloader_evaluate_module");
JS::Rooted<JSObject*> aEvaluationPromise(
cx, nsJSUtils::ModuleEvaluate(cx, module));

if (request->IsDynamicImport()) {
FinishDynamicImport(cx, request, rv, aEvaluationPromise);
JS::Rooted<JS::Value> rval(cx);

rv = nsJSUtils::ModuleEvaluate(cx, module, &rval);
MOZ_ASSERT(NS_FAILED(rv) == aes.HasException());

if (NS_FAILED(rv)) {
LOG(("ScriptLoadRequest (%p): evaluation failed", aRequest));
// For a dynamic import, the promise is rejected. Otherwise an error is
// either reported by AutoEntryScript.
rv = NS_OK;
}

if (!JS::ContextOptionsRef(cx).topLevelAwait()) {
if (request->IsDynamicImport()) {
FinishDynamicImport_NoTLA(cx, request, rv);
}
} else {
// If this is not a dynamic import, and if the promise is rejected, the
// value is unwrapped from the promise value.
if (!JS::ThrowOnModuleEvaluationFailure(cx, aEvaluationPromise)) {
LOG(("ScriptLoadRequest (%p): evaluation failed", aRequest));
// For a dynamic import, the promise is rejected. Otherwise an error
// is either reported by AutoEntryScript.
rv = NS_OK;
// Path for when Top Level Await is enabled
JS::Rooted<JSObject*> aEvaluationPromise(cx, &rval.toObject());
if (request->IsDynamicImport()) {
FinishDynamicImport(cx, request, rv, aEvaluationPromise);
} else {
// If this is not a dynamic import, and if the promise is rejected,
// the value is unwrapped from the promise value.
if (!JS::ThrowOnModuleEvaluationFailure(cx, aEvaluationPromise)) {
LOG(("ScriptLoadRequest (%p): evaluation failed", aRequest));
// For a dynamic import, the promise is rejected. Otherwise an
// error is either reported by AutoEntryScript.
rv = NS_OK;
}
}
}

Expand Down
20 changes: 20 additions & 0 deletions dom/script/ScriptLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,26 @@ class ScriptLoader final : public nsISupports {
* Wrapper for JSAPI FinishDynamicImport function. Takes an optional argument
* `aEvaluationPromise` which, if null, exits early.
*
* This is the non-tla version, which works with modules which return
* completion records.
*
* @param aCX
* The JSContext for the module.
* @param aRequest
* The module load request for the dynamic module.
* @param aResult
* The result of running ModuleEvaluate
*/
void FinishDynamicImport_NoTLA(JSContext* aCx, ModuleLoadRequest* aRequest,
nsresult aResult);

/**
* Wrapper for JSAPI FinishDynamicImport function. Takes an optional argument
* `aEvaluationPromise` which, if null, exits early.
*
* This is the Top Level Await version, which works with modules which return
* promises.
*
* @param aCX
* The JSContext for the module.
* @param aRequest
Expand Down
8 changes: 2 additions & 6 deletions dom/worklet/Worklet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -405,12 +405,8 @@ void ExecutionRunnable::RunOnWorkletThread() {
// https://html.spec.whatwg.org/multipage/webappapis.html#run-a-module-script
// without /rethrow errors/ and so unhandled exceptions do not cause the
// promise to be rejected.
JS::Rooted<JSObject*> evaluationPromise(cx, JS::ModuleEvaluate(cx, module));

if (!JS::ThrowOnModuleEvaluationFailure(cx, evaluationPromise)) {
mResult = NS_ERROR_DOM_UNKNOWN_ERR;
return;
}
JS::Rooted<JS::Value> ignored(cx);
JS::ModuleEvaluate(cx, module, &ignored);

// All done.
mResult = NS_OK;
Expand Down
26 changes: 21 additions & 5 deletions js/public/Modules.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,27 @@ enum class DynamicImportStatus { Failed = 0, Ok };
/**
* This must be called after a dynamic import operation is complete.
*
* If |status| is Failed, any pending exception on the context will be used to
* If |evaluationPromise| is rejected, the rejection reason will be used to
* complete the user's promise.
*/
extern JS_PUBLIC_API bool FinishDynamicModuleImport(
JSContext* cx, Handle<JSObject*> evaluationPromise,
Handle<Value> referencingPrivate, Handle<JSString*> specifier,
Handle<JSObject*> promise);

/**
* This must be called after a dynamic import operation is complete.
*
* This is used so that Top Level Await functionality can be turned off
* entirely. It will be removed in bug#1676612.
*
* If |status| is Failed, any pending exception on the context will be used to
* complete the user's promise.
*/
extern JS_PUBLIC_API bool FinishDynamicModuleImport_NoTLA(
JSContext* cx, DynamicImportStatus status, Handle<Value> referencingPrivate,
Handle<JSString*> specifier, Handle<JSObject*> promise);

/**
* Parse the given source buffer as a module in the scope of the current global
* of cx and return a source text module record.
Expand Down Expand Up @@ -139,19 +152,22 @@ extern JS_PUBLIC_API bool ModuleInstantiate(JSContext* cx,

/*
* Perform the ModuleEvaluate operation on the given source text module record
* and returns a promise.
* and returns a bool. A result value is returned in result and is either
* undefined (and ignored) or a promise (if Top Level Await is enabled).
*
* If this module has already been evaluated, it returns the evaluation
* promise. Otherwise, it transitively evaluates all dependences of this module
* and then evaluates this module.
*
* ModuleInstantiate must have completed prior to calling this.
*/
extern JS_PUBLIC_API JSObject* ModuleEvaluate(JSContext* cx,
Handle<JSObject*> moduleRecord);
extern JS_PUBLIC_API bool ModuleEvaluate(JSContext* cx,
Handle<JSObject*> moduleRecord,
MutableHandleValue rval);

/*
* If a module evaluation fails, unwrap the result and rethrow.
* If a module evaluation fails, unwrap the resulting evaluation promise
* and rethrow.
*
* This does nothing if this module succeeds in evaluation. Otherwise, it
* takes the reason for the module throwing, unwraps it and throws it as a
Expand Down
Loading

0 comments on commit f710026

Please sign in to comment.