Commit 660c7da9 authored by Hiroshige Hayashizaki's avatar Hiroshige Hayashizaki Committed by Commit Bot

Merge module script evaluation methods

Before this CL, there are three slightly different methods
for module script evaluation:

- Modulator::ExecuteModule()
- ModuleRecord::Evaluate()
- V8ScriptRunner::EvaluateModule()

To unify the code paths and behavior, this CL

- Merges them into V8ScriptRunner::EvaluateModule()
  (Mostly just inlining)
- Merges Modulator::CaptureEvalErrorFlag into
  V8ScriptRunner::RethrowErrorsOption, and
- Introduces a thin wrapper ModuleScript::RunScriptAndReturnValue()
  to align with the ClassicScript conterpart.

The new behavior of V8ScriptRunner::EvaluateModule() is
the same as the previous ModulatorImplBase::ExecuteModule().

This CL shouldn't change the non-test behavior,
because all non-test code path went through
ModulatorImplBase::ExecuteModule(),

This CL also replaces some Modulator calls (that were in
Modulator::ExecuteModule() and are now in
V8ScriptRunner::EvaluateModule()) with equivalent direct calls
to ExecutionContext, to avoid many more overrides in DummyModulator
subclasses in unit tests.

Bug: 1111134
Change-Id: Icbda1e657b33077f0ae1e681045b04857229cbfc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2462885Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Reviewed-by: default avatarKouhei Ueno <kouhei@chromium.org>
Reviewed-by: default avatarDominic Farolino <dom@chromium.org>
Commit-Queue: Hiroshige Hayashizaki <hiroshige@chromium.org>
Cr-Commit-Position: refs/heads/master@{#822046}
parent 9a601cc6
......@@ -123,35 +123,6 @@ ScriptValue ModuleRecord::Instantiate(ScriptState* script_state,
return ScriptValue();
}
ScriptEvaluationResult ModuleRecord::Evaluate(ScriptState* script_state,
v8::Local<v8::Module> record,
const KURL& source_url) {
v8::Isolate* isolate = script_state->GetIsolate();
// Isolate exceptions that occur when executing the code. These exceptions
// should not interfere with javascript code we might evaluate from C++ when
// returning from here.
v8::TryCatch try_catch(isolate);
ExecutionContext* execution_context = ExecutionContext::From(script_state);
// Script IDs are not available on errored modules or on non-source text
// modules, so we give them a default value.
probe::ExecuteScript probe(execution_context, source_url,
record->GetStatus() != v8::Module::kErrored &&
record->IsSourceTextModule()
? record->ScriptId()
: v8::UnboundScript::kNoScriptId);
v8::Local<v8::Value> result;
if (!V8ScriptRunner::EvaluateModule(isolate, execution_context, record,
script_state->GetContext())
.ToLocal(&result)) {
return ScriptEvaluationResult::FromModuleException(try_catch.Exception());
}
return ScriptEvaluationResult::FromModuleSuccess(result);
}
void ModuleRecord::ReportException(ScriptState* script_state,
v8::Local<v8::Value> exception) {
V8ScriptRunner::ReportException(script_state->GetIsolate(), exception);
......
......@@ -80,10 +80,6 @@ class CORE_EXPORT ModuleRecord final {
v8::Local<v8::Module> record,
const KURL& source_url);
static ScriptEvaluationResult Evaluate(ScriptState*,
v8::Local<v8::Module> record,
const KURL& source_url);
static void ReportException(ScriptState*, v8::Local<v8::Value> exception);
static Vector<ModuleRequest> ModuleRequests(ScriptState*,
......
......@@ -15,6 +15,7 @@
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/script/classic_script.h"
#include "third_party/blink/renderer/core/script/js_module_script.h"
#include "third_party/blink/renderer/core/script/module_record_resolver.h"
#include "third_party/blink/renderer/core/testing/dummy_modulator.h"
#include "third_party/blink/renderer/core/testing/module_test_base.h"
......@@ -46,7 +47,7 @@ class TestModuleRecordResolver final : public ModuleRecordResolver {
private:
// Implements ModuleRecordResolver:
void RegisterModuleScript(const ModuleScript*) override { NOTREACHED(); }
void RegisterModuleScript(const ModuleScript*) override {}
void UnregisterModuleScript(const ModuleScript*) override { NOTREACHED(); }
const ModuleScript* GetModuleScriptFromModuleRecord(
......@@ -69,7 +70,7 @@ class TestModuleRecordResolver final : public ModuleRecordResolver {
class ModuleRecordTestModulator final : public DummyModulator {
public:
ModuleRecordTestModulator(v8::Isolate* isolate);
explicit ModuleRecordTestModulator(ScriptState*);
~ModuleRecordTestModulator() override = default;
void Trace(Visitor*) const override;
......@@ -81,17 +82,25 @@ class ModuleRecordTestModulator final : public DummyModulator {
private:
// Implements Modulator:
ScriptState* GetScriptState() override { return script_state_; }
ModuleRecordResolver* GetModuleRecordResolver() override {
return resolver_.Get();
}
Member<ScriptState> script_state_;
Member<TestModuleRecordResolver> resolver_;
};
ModuleRecordTestModulator::ModuleRecordTestModulator(v8::Isolate* isolate)
: resolver_(MakeGarbageCollected<TestModuleRecordResolver>(isolate)) {}
ModuleRecordTestModulator::ModuleRecordTestModulator(ScriptState* script_state)
: script_state_(script_state),
resolver_(MakeGarbageCollected<TestModuleRecordResolver>(
script_state->GetIsolate())) {
Modulator::SetModulator(script_state, this);
}
void ModuleRecordTestModulator::Trace(Visitor* visitor) const {
visitor->Trace(script_state_);
visitor->Trace(resolver_);
DummyModulator::Trace(visitor);
}
......@@ -141,11 +150,9 @@ TEST_P(ModuleRecordTest, instantiateNoDeps) {
V8TestingScope scope;
auto* modulator =
MakeGarbageCollected<ModuleRecordTestModulator>(scope.GetIsolate());
MakeGarbageCollected<ModuleRecordTestModulator>(scope.GetScriptState());
auto* resolver = modulator->GetTestModuleRecordResolver();
Modulator::SetModulator(scope.GetScriptState(), modulator);
const KURL js_url("https://example.com/foo.js");
v8::Local<v8::Module> module = ModuleRecord::Compile(
scope.GetIsolate(), "export const a = 42;", js_url, js_url,
......@@ -163,11 +170,9 @@ TEST_P(ModuleRecordTest, instantiateWithDeps) {
V8TestingScope scope;
auto* modulator =
MakeGarbageCollected<ModuleRecordTestModulator>(scope.GetIsolate());
MakeGarbageCollected<ModuleRecordTestModulator>(scope.GetScriptState());
auto* resolver = modulator->GetTestModuleRecordResolver();
Modulator::SetModulator(scope.GetScriptState(), modulator);
const KURL js_url_a("https://example.com/a.js");
v8::Local<v8::Module> module_a = ModuleRecord::Compile(
scope.GetIsolate(), "export const a = 'a';", js_url_a, js_url_a,
......@@ -224,11 +229,9 @@ TEST_P(ModuleRecordTest, EvaluationErrorIsRemembered) {
V8TestingScope scope;
auto* modulator =
MakeGarbageCollected<ModuleRecordTestModulator>(scope.GetIsolate());
MakeGarbageCollected<ModuleRecordTestModulator>(scope.GetScriptState());
auto* resolver = modulator->GetTestModuleRecordResolver();
Modulator::SetModulator(scope.GetScriptState(), modulator);
const KURL js_url_f("https://example.com/failure.js");
v8::Local<v8::Module> module_failure = ModuleRecord::Compile(
scope.GetIsolate(), "nonexistent_function()", js_url_f, js_url_f,
......@@ -239,7 +242,8 @@ TEST_P(ModuleRecordTest, EvaluationErrorIsRemembered) {
js_url_f)
.IsEmpty());
ScriptEvaluationResult evaluation_result1 =
ModuleRecord::Evaluate(scope.GetScriptState(), module_failure, js_url_f);
JSModuleScript::CreateForTest(modulator, module_failure, js_url_f)
->RunScriptAndReturnValue();
resolver->PrepareMockResolveResult(module_failure);
......@@ -253,7 +257,8 @@ TEST_P(ModuleRecordTest, EvaluationErrorIsRemembered) {
ModuleRecord::Instantiate(scope.GetScriptState(), module, js_url_c)
.IsEmpty());
ScriptEvaluationResult evaluation_result2 =
ModuleRecord::Evaluate(scope.GetScriptState(), module, js_url_f);
JSModuleScript::CreateForTest(modulator, module, js_url_c)
->RunScriptAndReturnValue();
if (base::FeatureList::IsEnabled(features::kTopLevelAwait)) {
EXPECT_EQ(evaluation_result1.GetResultType(),
......@@ -291,8 +296,7 @@ TEST_P(ModuleRecordTest, Evaluate) {
V8TestingScope scope;
auto* modulator =
MakeGarbageCollected<ModuleRecordTestModulator>(scope.GetIsolate());
Modulator::SetModulator(scope.GetScriptState(), modulator);
MakeGarbageCollected<ModuleRecordTestModulator>(scope.GetScriptState());
const KURL js_url("https://example.com/foo.js");
v8::Local<v8::Module> module = ModuleRecord::Compile(
......@@ -304,7 +308,8 @@ TEST_P(ModuleRecordTest, Evaluate) {
ModuleRecord::Instantiate(scope.GetScriptState(), module, js_url);
ASSERT_TRUE(exception.IsEmpty());
EXPECT_EQ(ModuleRecord::Evaluate(scope.GetScriptState(), module, js_url)
EXPECT_EQ(JSModuleScript::CreateForTest(modulator, module, js_url)
->RunScriptAndReturnValue()
.GetResultType(),
ScriptEvaluationResult::ResultType::kSuccess);
v8::Local<v8::Value> value =
......@@ -327,8 +332,7 @@ TEST_P(ModuleRecordTest, EvaluateCaptureError) {
V8TestingScope scope;
auto* modulator =
MakeGarbageCollected<ModuleRecordTestModulator>(scope.GetIsolate());
Modulator::SetModulator(scope.GetScriptState(), modulator);
MakeGarbageCollected<ModuleRecordTestModulator>(scope.GetScriptState());
const KURL js_url("https://example.com/foo.js");
v8::Local<v8::Module> module = ModuleRecord::Compile(
......@@ -340,7 +344,8 @@ TEST_P(ModuleRecordTest, EvaluateCaptureError) {
ASSERT_TRUE(exception.IsEmpty());
ScriptEvaluationResult result =
ModuleRecord::Evaluate(scope.GetScriptState(), module, js_url);
JSModuleScript::CreateForTest(modulator, module, js_url)
->RunScriptAndReturnValue();
v8::Local<v8::Value> value;
if (base::FeatureList::IsEnabled(features::kTopLevelAwait)) {
......
......@@ -32,6 +32,7 @@
#include "third_party/blink/renderer/bindings/core/v8/binding_security.h"
#include "third_party/blink/renderer/bindings/core/v8/referrer_script_info.h"
#include "third_party/blink/renderer/bindings/core/v8/script_evaluation_result.h"
#include "third_party/blink/renderer/bindings/core/v8/script_function.h"
#include "third_party/blink/renderer/bindings/core/v8/script_source_code.h"
#include "third_party/blink/renderer/bindings/core/v8/script_streamer.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
......@@ -44,6 +45,8 @@
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/inspector/inspector_trace_events.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/core/script/modulator.h"
#include "third_party/blink/renderer/core/script/module_script.h"
#include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h"
#include "third_party/blink/renderer/platform/bindings/v8_throw_exception.h"
#include "third_party/blink/renderer/platform/instrumentation/histogram.h"
......@@ -635,18 +638,146 @@ v8::MaybeLocal<v8::Value> V8ScriptRunner::CallFunction(
return result;
}
v8::MaybeLocal<v8::Value> V8ScriptRunner::EvaluateModule(
v8::Isolate* isolate,
ExecutionContext* execution_context,
v8::Local<v8::Module> module,
v8::Local<v8::Context> context) {
TRACE_EVENT0("v8,devtools.timeline", "v8.evaluateModule");
RUNTIME_CALL_TIMER_SCOPE(isolate, RuntimeCallStats::CounterId::kV8);
v8::Isolate::SafeForTerminationScope safe_for_termination(isolate);
// Do not perform a microtask checkpoint here. A checkpoint is performed
// only after module error handling to ensure proper timing with and without
// top-level await.
return module->Evaluate(context);
class ModuleEvaluationRejectionCallback final : public ScriptFunction {
public:
explicit ModuleEvaluationRejectionCallback(ScriptState* script_state)
: ScriptFunction(script_state) {}
static v8::Local<v8::Function> CreateFunction(ScriptState* script_state) {
ModuleEvaluationRejectionCallback* self =
MakeGarbageCollected<ModuleEvaluationRejectionCallback>(script_state);
return self->BindToV8Function();
}
private:
ScriptValue Call(ScriptValue value) override {
ModuleRecord::ReportException(GetScriptState(), value.V8Value());
return ScriptValue();
}
};
// <specdef href="https://html.spec.whatwg.org/C/#run-a-module-script">
// Spec with TLA: https://github.com/whatwg/html/pull/4352
ScriptEvaluationResult V8ScriptRunner::EvaluateModule(
ModuleScript* module_script,
RethrowErrorsOption rethrow_errors) {
// <spec step="1">If rethrow errors is not given, let it be false.</spec>
// <spec step="2">Let settings be the settings object of script.</spec>
//
// The settings object is |module_script->SettingsObject()|.
ScriptState* script_state = module_script->SettingsObject()->GetScriptState();
DCHECK_EQ(Modulator::From(script_state), module_script->SettingsObject());
ExecutionContext* execution_context = ExecutionContext::From(script_state);
v8::Isolate* isolate = script_state->GetIsolate();
// <spec step="3">Check if we can run script with settings. If this returns
// "do not run" then return NormalCompletion(empty).</spec>
if (!execution_context->CanExecuteScripts(kAboutToExecuteScript)) {
return ScriptEvaluationResult::FromModuleNotRun();
}
// <spec step="4">Prepare to run script given settings.</spec>
//
// These are placed here to also cover ModuleRecord::ReportException().
v8::MicrotasksScope microtasks_scope(isolate,
ToMicrotaskQueue(execution_context),
v8::MicrotasksScope::kRunMicrotasks);
ScriptState::EscapableScope scope(script_state);
// Without TLA: <spec step="5">Let evaluationStatus be null.</spec>
ScriptEvaluationResult result = ScriptEvaluationResult::FromModuleNotRun();
// <spec step="6">If script's error to rethrow is not null, ...</spec>
if (module_script->HasErrorToRethrow()) {
// Without TLA: <spec step="6">... then set evaluationStatus to Completion
// { [[Type]]: throw, [[Value]]: script's error to rethrow,
// [[Target]]: empty }.</spec>
// With TLA: <spec step="5">If script's error to rethrow is not null,
// then let valuationPromise be a promise rejected with script's error
// to rethrow.</spec>
result = ScriptEvaluationResult::FromModuleException(
module_script->CreateErrorToRethrow().V8Value());
} else {
// <spec step="7">Otherwise:</spec>
// <spec step="7.1">Let record be script's record.</spec>
v8::Local<v8::Module> record = module_script->V8Module();
CHECK(!record.IsEmpty());
// <spec step="7.2">Set evaluationStatus to record.Evaluate(). ...</spec>
// Isolate exceptions that occur when executing the code. These exceptions
// should not interfere with javascript code we might evaluate from C++
// when returning from here.
v8::TryCatch try_catch(isolate);
// Script IDs are not available on errored modules or on non-source text
// modules, so we give them a default value.
probe::ExecuteScript probe(execution_context, module_script->SourceURL(),
record->GetStatus() != v8::Module::kErrored &&
record->IsSourceTextModule()
? record->ScriptId()
: v8::UnboundScript::kNoScriptId);
TRACE_EVENT0("v8,devtools.timeline", "v8.evaluateModule");
RUNTIME_CALL_TIMER_SCOPE(isolate, RuntimeCallStats::CounterId::kV8);
v8::Isolate::SafeForTerminationScope safe_for_termination(isolate);
// Do not perform a microtask checkpoint here. A checkpoint is performed
// only after module error handling to ensure proper timing with and
// without top-level await.
v8::Local<v8::Value> v8_result;
if (!record->Evaluate(script_state->GetContext()).ToLocal(&v8_result)) {
result =
ScriptEvaluationResult::FromModuleException(try_catch.Exception());
} else {
result = ScriptEvaluationResult::FromModuleSuccess(v8_result);
}
// <spec step="7.2">... If Evaluate fails to complete as a result of the
// user agent aborting the running script, then set evaluationStatus to
// Completion { [[Type]]: throw, [[Value]]: a new "QuotaExceededError"
// DOMException, [[Target]]: empty }.</spec>
}
// [not specced] Store V8 code cache on successful evaluation.
if (result.GetResultType() == ScriptEvaluationResult::ResultType::kSuccess) {
execution_context->GetTaskRunner(TaskType::kNetworking)
->PostTask(FROM_HERE,
WTF::Bind(&Modulator::ProduceCacheModuleTreeTopLevel,
WrapWeakPersistent(Modulator::From(script_state)),
WrapWeakPersistent(module_script)));
}
if (!rethrow_errors.ShouldRethrow()) {
if (base::FeatureList::IsEnabled(features::kTopLevelAwait)) {
// <spec step="7"> If report errors is true, then upon rejection of
// evaluationPromise with reason, report the exception given by reason
// for script.</spec>
v8::Local<v8::Function> callback_failure =
ModuleEvaluationRejectionCallback::CreateFunction(script_state);
// Add a rejection handler to report back errors once the result
// promise is rejected.
result.GetPromise(script_state)
.Then(v8::Local<v8::Function>(), callback_failure);
} else {
// <spec step="8">If evaluationStatus is an abrupt completion,
// then:</spec>
if (result.GetResultType() ==
ScriptEvaluationResult::ResultType::kException) {
// <spec step="8.2">Otherwise, report the exception given by
// evaluationStatus.[[Value]] for script.</spec>
ModuleRecord::ReportException(script_state,
result.GetExceptionForModule());
}
}
}
// <spec step="8">Clean up after running script with settings.</spec>
// - Partially implement in MicrotaskScope destructor and the
// - ScriptState::EscapableScope destructor.
return result.Escape(&scope);
}
void V8ScriptRunner::ReportException(v8::Isolate* isolate,
......
......@@ -43,6 +43,7 @@ namespace blink {
class ScriptEvaluationResult;
class ExecutionContext;
class KURL;
class ModuleScript;
class ReferrerScriptInfo;
class ScriptFetchOptions;
class ScriptSourceCode;
......@@ -55,6 +56,9 @@ class CORE_EXPORT V8ScriptRunner final {
public:
// Rethrow errors flag in
// https://html.spec.whatwg.org/C/#run-a-classic-script
// implemented by CompileAndRunScript() and
// https://html.spec.whatwg.org/C/#run-a-module-script
// implemented by EvaluateModule().
class RethrowErrorsOption final {
STACK_ALLOCATED();
......@@ -70,11 +74,20 @@ class CORE_EXPORT V8ScriptRunner final {
return RethrowErrorsOption(base::nullopt);
}
// Rethrow errors flag is true. When rethrowing, a NetworkError with
// `message` is thrown. This is used only for importScripts(), and
// `message` is used to throw NetworkErrors with the same message text,
// no matter whether the NetworkError is thrown inside or outside
// EvaluateAndReturnValue().
// Rethrow errors flag is true.
// When an exception is to be rethrown,
// For classic scripts:
// The exception is rethrown to V8, and ScriptEvaluationResult doesn't
// retain the exception.
// When script's muted errors is true, a NetworkError with
// `message` is thrown. This is used only for importScripts(), and
// `message` is used to throw NetworkErrors with the same message text,
// no matter whether the NetworkError is thrown inside or outside
// V8ScriptRunner.
// For module scripts:
// The exception is caught and
// ScriptEvaluationResult::GetExceptionForModule() returns the exception
// to be rethrown.
static RethrowErrorsOption Rethrow(const String& message) {
return RethrowErrorsOption(message);
}
......@@ -134,10 +147,13 @@ class CORE_EXPORT V8ScriptRunner final {
int argc,
v8::Local<v8::Value> info[],
v8::Isolate*);
static v8::MaybeLocal<v8::Value> EvaluateModule(v8::Isolate*,
ExecutionContext*,
v8::Local<v8::Module>,
v8::Local<v8::Context>);
// https://html.spec.whatwg.org/C/#run-a-module-script
// Callers must enter a v8::HandleScope before calling.
// See the class comments of RethrowErrorsOption and ScriptEvaluationResult
// for exception handling and return value semantics.
static ScriptEvaluationResult EvaluateModule(ModuleScript*,
RethrowErrorsOption);
// Only to be used from ModuleRecord::ReportException().
static void ReportExceptionForModule(v8::Isolate*,
......
......@@ -14,6 +14,7 @@
#include "third_party/blink/renderer/core/layout/ng/custom/css_layout_definition.h"
#include "third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope.h"
#include "third_party/blink/renderer/core/layout/ng/custom/layout_worklet_global_scope_proxy.h"
#include "third_party/blink/renderer/core/script/js_module_script.h"
#include "third_party/blink/renderer/core/testing/page_test_base.h"
namespace blink {
......@@ -59,7 +60,9 @@ class LayoutWorkletTest : public PageTestBase {
ModuleRecord::Instantiate(script_state, module, js_url);
EXPECT_TRUE(exception.IsEmpty());
return ModuleRecord::Evaluate(script_state, module, js_url);
return JSModuleScript::CreateForTest(Modulator::From(script_state), module,
js_url)
->RunScriptAndReturnValue();
}
private:
......
......@@ -154,8 +154,8 @@ void DynamicImportTreeClient::NotifyModuleTreeLoadFinished(
// <spec step="7">Run the module script result, with the rethrow errors
// boolean set to true.</spec>
ScriptEvaluationResult result = modulator_->ExecuteModule(
module_script, Modulator::CaptureEvalErrorFlag::kCapture);
ScriptEvaluationResult result = module_script->RunScriptAndReturnValue(
V8ScriptRunner::RethrowErrorsOption::Rethrow(String()));
switch (result.GetResultType()) {
case ScriptEvaluationResult::ResultType::kException:
......@@ -217,8 +217,8 @@ void DynamicImportTreeClient::NotifyModuleTreeLoadFinished(
// step="2.3">Assert: Evaluate has already been invoked on moduleRecord
// and successfully completed.</spec>
//
// Because |error| is empty, we are sure that ExecuteModule() above was
// successfully completed.
// Because |error| is empty, we are sure that RunScriptAndReturnValue()
// above was successfully completed.
// <spec
// href="https://tc39.github.io/proposal-dynamic-import/#sec-finishdynamicimport"
......
......@@ -37,7 +37,9 @@ const KURL TestDependencyURL() {
class DynamicModuleResolverTestModulator final : public DummyModulator {
public:
explicit DynamicModuleResolverTestModulator(ScriptState* script_state)
: script_state_(script_state) {}
: script_state_(script_state) {
Modulator::SetModulator(script_state, this);
}
~DynamicModuleResolverTestModulator() override = default;
void ResolveTreeFetch(ModuleScript* module_script) {
......@@ -91,17 +93,6 @@ class DynamicModuleResolverTestModulator final : public DummyModulator {
fetch_tree_was_called_ = true;
}
ScriptEvaluationResult ExecuteModule(
ModuleScript* module_script,
CaptureEvalErrorFlag capture_error) final {
EXPECT_EQ(CaptureEvalErrorFlag::kCapture, capture_error);
ScriptState::EscapableScope scope(script_state_);
ScriptEvaluationResult result = ModuleRecord::Evaluate(
script_state_, module_script->V8Module(), module_script->SourceURL());
return result.Escape(&scope);
}
Member<ScriptState> script_state_;
Member<ModuleTreeClient> pending_client_;
KURL expected_fetch_tree_url_;
......
......@@ -195,26 +195,12 @@ class CORE_EXPORT Modulator : public GarbageCollected<Modulator>,
virtual Vector<ModuleRequest> ModuleRequestsFromModuleRecord(
v8::Local<v8::Module>) = 0;
enum class CaptureEvalErrorFlag : bool { kReport, kCapture };
// ExecuteModule implements #run-a-module-script HTML spec algorithm.
// https://html.spec.whatwg.org/C/#run-a-module-script
// CaptureEvalErrorFlag is used to implement "rethrow errors" parameter in
// run-a-module-script.
// - When "rethrow errors" is to be set, use kCapture for EvaluateModule().
// Then EvaluateModule() wraps exceptions in a ScriptEvaluationResult instead
// of throwing it and the caller should rethrow the exception.
// - When "rethrow errors" is not to be set, use kReport. If there is an error
// to throw, EvaluateModule() "report the error" inside it, and returns
// ScriptEvaluationResult wrapping the error. Otherwise, it returns either a
// ScriptEvaluationResult that is empty or contains the successful evaluation
// result.
virtual ScriptEvaluationResult ExecuteModule(ModuleScript*,
CaptureEvalErrorFlag) = 0;
virtual ModuleScriptFetcher* CreateModuleScriptFetcher(
ModuleScriptCustomFetchType,
util::PassKey<ModuleScriptLoader> pass_key) = 0;
// Produce V8 code cache for the given ModuleScript and its submodules.
virtual void ProduceCacheModuleTreeTopLevel(ModuleScript*) = 0;
};
} // namespace blink
......
......@@ -49,24 +49,6 @@ ModulatorImplBase::ModulatorImplBase(ScriptState* script_state)
ModulatorImplBase::~ModulatorImplBase() {}
class ModuleEvaluationRejectionCallback final : public ScriptFunction {
public:
explicit ModuleEvaluationRejectionCallback(ScriptState* script_state)
: ScriptFunction(script_state) {}
static v8::Local<v8::Function> CreateFunction(ScriptState* script_state) {
ModuleEvaluationRejectionCallback* self =
MakeGarbageCollected<ModuleEvaluationRejectionCallback>(script_state);
return self->BindToV8Function();
}
private:
ScriptValue Call(ScriptValue value) override {
ModuleRecord::ReportException(GetScriptState(), value.V8Value());
return ScriptValue();
}
};
bool ModulatorImplBase::IsScriptingDisabled() const {
return !GetExecutionContext()->CanExecuteScripts(kAboutToExecuteScript);
}
......@@ -335,101 +317,6 @@ void ModulatorImplBase::ProduceCacheModuleTree(
}
}
// <specdef href="https://html.spec.whatwg.org/C/#run-a-module-script">
// Spec with TLA: https://github.com/whatwg/html/pull/4352
ScriptEvaluationResult ModulatorImplBase::ExecuteModule(
ModuleScript* module_script,
CaptureEvalErrorFlag capture_error) {
// <spec step="1">If rethrow errors is not given, let it be false.</spec>
// <spec step="2">Let settings be the settings object of script.</spec>
//
// The settings object is |this|.
// <spec step="3">Check if we can run script with settings. If this returns
// "do not run" then return NormalCompletion(empty).</spec>
if (IsScriptingDisabled()) {
return ScriptEvaluationResult::FromModuleNotRun();
}
// <spec step="4">Prepare to run script given settings.</spec>
//
// These are placed here to also cover ModuleRecord::ReportException().
v8::Isolate* isolate = script_state_->GetIsolate();
v8::MicrotasksScope microtasks_scope(isolate,
ToMicrotaskQueue(GetExecutionContext()),
v8::MicrotasksScope::kRunMicrotasks);
ScriptState::EscapableScope scope(script_state_);
// Without TLA: <spec step="5">Let evaluationStatus be null.</spec>
ScriptEvaluationResult result = ScriptEvaluationResult::FromModuleNotRun();
// <spec step="6">If script's error to rethrow is not null, ...</spec>
if (module_script->HasErrorToRethrow()) {
// Without TLA: <spec step="6">... then set evaluationStatus to Completion
// { [[Type]]: throw, [[Value]]: script's error to rethrow,
// [[Target]]: empty }.</spec>
// With TLA: <spec step="5">If script's error to rethrow is not null,
// then let valuationPromise be a promise rejected with script's error
// to rethrow.</spec>
result = ScriptEvaluationResult::FromModuleException(
module_script->CreateErrorToRethrow().V8Value());
} else {
// <spec step="7">Otherwise:</spec>
// <spec step="7.1">Let record be script's record.</spec>
v8::Local<v8::Module> record = module_script->V8Module();
CHECK(!record.IsEmpty());
// <spec step="7.2">Set evaluationStatus to record.Evaluate(). ...</spec>
result = ModuleRecord::Evaluate(script_state_, record,
module_script->SourceURL());
// <spec step="7.2">... If Evaluate fails to complete as a result of the
// user agent aborting the running script, then set evaluationStatus to
// Completion { [[Type]]: throw, [[Value]]: a new "QuotaExceededError"
// DOMException, [[Target]]: empty }.</spec>
// [not specced] Store V8 code cache on successful evaluation.
if (result.GetResultType() ==
ScriptEvaluationResult::ResultType::kSuccess) {
TaskRunner()->PostTask(
FROM_HERE,
WTF::Bind(&ModulatorImplBase::ProduceCacheModuleTreeTopLevel,
WrapWeakPersistent(this), WrapPersistent(module_script)));
}
}
if (capture_error == CaptureEvalErrorFlag::kReport) {
if (base::FeatureList::IsEnabled(features::kTopLevelAwait)) {
// <spec step="7"> If report errors is true, then upon rejection of
// evaluationPromise with reason, report the exception given by reason
// for script.</spec>
v8::Local<v8::Function> callback_failure =
ModuleEvaluationRejectionCallback::CreateFunction(script_state_);
// Add a rejection handler to report back errors once the result
// promise is rejected.
result.GetPromise(script_state_)
.Then(v8::Local<v8::Function>(), callback_failure);
} else {
// <spec step="8">If evaluationStatus is an abrupt completion,
// then:</spec>
if (result.GetResultType() ==
ScriptEvaluationResult::ResultType::kException) {
// <spec step="8.2">Otherwise, report the exception given by
// evaluationStatus.[[Value]] for script.</spec>
ModuleRecord::ReportException(script_state_,
result.GetExceptionForModule());
}
}
}
// <spec step="8">Clean up after running script with settings.</spec>
// - Partially implement in MicrotaskScope destructor and the
// - ScriptState::EscapableScope destructor.
return result.Escape(&scope);
}
void ModulatorImplBase::Trace(Visitor* visitor) const {
visitor->Trace(script_state_);
visitor->Trace(map_);
......
......@@ -90,8 +90,6 @@ class ModulatorImplBase : public Modulator {
ScriptValue InstantiateModule(v8::Local<v8::Module>, const KURL&) override;
Vector<ModuleRequest> ModuleRequestsFromModuleRecord(
v8::Local<v8::Module>) override;
ScriptEvaluationResult ExecuteModule(ModuleScript*,
CaptureEvalErrorFlag) override;
// Populates |reason| and returns true if the dynamic import is disallowed on
// the associated execution context. In that case, a caller of this function
......@@ -100,7 +98,7 @@ class ModulatorImplBase : public Modulator {
// modification of |reason|.
virtual bool IsDynamicImportForbidden(String* reason) = 0;
void ProduceCacheModuleTreeTopLevel(ModuleScript*);
void ProduceCacheModuleTreeTopLevel(ModuleScript*) override;
void ProduceCacheModuleTree(ModuleScript*,
HeapHashSet<Member<const ModuleScript>>*);
......
......@@ -4,8 +4,11 @@
#include "third_party/blink/renderer/core/script/module_script.h"
#include "base/macros.h"
#include "third_party/blink/renderer/bindings/core/v8/module_record.h"
#include "third_party/blink/renderer/bindings/core/v8/script_value.h"
#include "third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/script/module_record_resolver.h"
#include "third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
......@@ -101,31 +104,31 @@ void ModuleScript::Trace(Visitor* visitor) const {
}
void ModuleScript::RunScript(LocalDOMWindow*) {
// We need a HandleScope for the ScriptEvaluationResult that is created
// in ::ExecuteModule(...).
// We need a HandleScope for the `ScriptEvaluationResult` returned from
// `RunScriptAndReturnValue`.
ScriptState::Scope scope(SettingsObject()->GetScriptState());
DVLOG(1) << *this << "::RunScript()";
SettingsObject()->ExecuteModule(this,
Modulator::CaptureEvalErrorFlag::kReport);
ignore_result(RunScriptAndReturnValue());
}
bool ModuleScript::RunScriptOnWorkerOrWorklet(
WorkerOrWorkletGlobalScope& global_scope) {
// We need a HandleScope for the ScriptEvaluationResult that is created
// in ::ExecuteModule(...).
// We need a HandleScope for the `ScriptEvaluationResult` returned from
// `RunScriptAndReturnValue`.
ScriptState::Scope scope(SettingsObject()->GetScriptState());
DCHECK(global_scope.IsContextThread());
// This |error| is always null because the second argument is |kReport|.
// TODO(nhiroki): Catch an error when an evaluation error happens.
// (https://crbug.com/680046)
ScriptEvaluationResult result = SettingsObject()->ExecuteModule(
this, Modulator::CaptureEvalErrorFlag::kReport);
ScriptEvaluationResult result = RunScriptAndReturnValue();
return result.GetResultType() == ScriptEvaluationResult::ResultType::kSuccess;
}
ScriptEvaluationResult ModuleScript::RunScriptAndReturnValue(
V8ScriptRunner::RethrowErrorsOption rethrow_errors) {
return V8ScriptRunner::EvaluateModule(this, std::move(rethrow_errors));
}
std::pair<size_t, size_t> ModuleScript::GetClassicScriptSizes() const {
return std::pair<size_t, size_t>(0, 0);
}
......
......@@ -7,6 +7,7 @@
#include "third_party/blink/renderer/bindings/core/v8/module_record.h"
#include "third_party/blink/renderer/bindings/core/v8/script_value.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_script_runner.h"
#include "third_party/blink/renderer/bindings/core/v8/world_safe_v8_reference.h"
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/script/modulator.h"
......@@ -54,6 +55,16 @@ class CORE_EXPORT ModuleScript : public Script {
virtual void ProduceCache() {}
const KURL& SourceURL() const { return source_url_; }
// https://html.spec.whatwg.org/C/#run-a-module-script
// Callers must enter a `v8::HandleScope` before calling.
// See the class comments of `RethrowErrorsOption` and
// `ScriptEvaluationResult` for exception handling and return value semantics.
WARN_UNUSED_RESULT ScriptEvaluationResult RunScriptAndReturnValue(
V8ScriptRunner::RethrowErrorsOption =
V8ScriptRunner::RethrowErrorsOption::DoNotRethrow());
Modulator* SettingsObject() const { return settings_object_; }
protected:
ModuleScript(Modulator*,
v8::Local<v8::Module>,
......@@ -61,8 +72,6 @@ class CORE_EXPORT ModuleScript : public Script {
const KURL& base_url,
const ScriptFetchOptions&);
Modulator* SettingsObject() const { return settings_object_; }
private:
mojom::blink::ScriptType GetScriptType() const override {
return mojom::blink::ScriptType::kModule;
......
......@@ -167,10 +167,7 @@ TEST_P(ModuleScriptTest, V8CodeCacheWithoutDiscarding) {
module_script->V8Module(),
module_script->SourceURL())
.IsEmpty());
ASSERT_EQ(ModuleRecord::Evaluate(scope.GetScriptState(),
module_script->V8Module(),
module_script->SourceURL())
.GetResultType(),
ASSERT_EQ(module_script->RunScriptAndReturnValue().GetResultType(),
ScriptEvaluationResult::ResultType::kSuccess);
TestFoo(scope);
......@@ -294,10 +291,7 @@ TEST_P(ModuleScriptTest, V8CodeCacheWithDiscarding) {
module_script->V8Module(),
module_script->SourceURL())
.IsEmpty());
ASSERT_EQ(ModuleRecord::Evaluate(scope.GetScriptState(),
module_script->V8Module(),
module_script->SourceURL())
.GetResultType(),
ASSERT_EQ(module_script->RunScriptAndReturnValue().GetResultType(),
ScriptEvaluationResult::ResultType::kSuccess);
TestFoo(scope);
......
......@@ -169,12 +169,6 @@ Vector<ModuleRequest> DummyModulator::ModuleRequestsFromModuleRecord(
return Vector<ModuleRequest>();
}
ScriptEvaluationResult DummyModulator::ExecuteModule(ModuleScript*,
CaptureEvalErrorFlag) {
NOTREACHED();
return ScriptEvaluationResult::FromModuleNotRun();
}
ModuleScriptFetcher* DummyModulator::CreateModuleScriptFetcher(
ModuleScriptCustomFetchType,
util::PassKey<ModuleScriptLoader> pass_key) {
......@@ -182,4 +176,6 @@ ModuleScriptFetcher* DummyModulator::CreateModuleScriptFetcher(
return nullptr;
}
void DummyModulator::ProduceCacheModuleTreeTopLevel(ModuleScript*) {}
} // namespace blink
......@@ -76,11 +76,10 @@ class DummyModulator : public Modulator {
ScriptValue InstantiateModule(v8::Local<v8::Module>, const KURL&) override;
Vector<ModuleRequest> ModuleRequestsFromModuleRecord(
v8::Local<v8::Module>) override;
ScriptEvaluationResult ExecuteModule(ModuleScript*,
CaptureEvalErrorFlag) override;
ModuleScriptFetcher* CreateModuleScriptFetcher(
ModuleScriptCustomFetchType,
util::PassKey<ModuleScriptLoader>) override;
void ProduceCacheModuleTreeTopLevel(ModuleScript*) override;
Member<ModuleRecordResolver> resolver_;
};
......
......@@ -70,10 +70,7 @@ void WorkletModuleTreeClient::NotifyModuleTreeLoadFinished(
}
// Step 5: "Run a module script given script."
ScriptEvaluationResult result =
Modulator::From(script_state_)
->ExecuteModule(module_script,
Modulator::CaptureEvalErrorFlag::kReport);
ScriptEvaluationResult result = module_script->RunScriptAndReturnValue();
auto* global_scope =
To<WorkletGlobalScope>(ExecutionContext::From(script_state_));
......
......@@ -26,6 +26,7 @@
#include "third_party/blink/renderer/core/messaging/message_channel.h"
#include "third_party/blink/renderer/core/messaging/message_port.h"
#include "third_party/blink/renderer/core/origin_trials/origin_trial_context.h"
#include "third_party/blink/renderer/core/script/js_module_script.h"
#include "third_party/blink/renderer/core/script/script.h"
#include "third_party/blink/renderer/core/testing/page_test_base.h"
#include "third_party/blink/renderer/core/workers/global_scope_creation_params.h"
......@@ -149,7 +150,9 @@ class AudioWorkletGlobalScopeTest : public PageTestBase {
EXPECT_TRUE(exception.IsEmpty());
ScriptEvaluationResult result =
ModuleRecord::Evaluate(script_state, module, js_url);
JSModuleScript::CreateForTest(Modulator::From(script_state), module,
js_url)
->RunScriptAndReturnValue();
return result.GetResultType() ==
ScriptEvaluationResult::ResultType::kSuccess;
}
......
......@@ -28,6 +28,7 @@
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/inspector/worker_devtools_params.h"
#include "third_party/blink/renderer/core/origin_trials/origin_trial_context.h"
#include "third_party/blink/renderer/core/script/js_module_script.h"
#include "third_party/blink/renderer/core/script/script.h"
#include "third_party/blink/renderer/core/testing/page_test_base.h"
#include "third_party/blink/renderer/core/workers/global_scope_creation_params.h"
......@@ -121,7 +122,9 @@ class AudioWorkletThreadTest : public PageTestBase {
ModuleRecord::Instantiate(script_state, module, js_url);
EXPECT_TRUE(exception.IsEmpty());
ScriptEvaluationResult result =
ModuleRecord::Evaluate(script_state, module, js_url);
JSModuleScript::CreateForTest(Modulator::From(script_state), module,
js_url)
->RunScriptAndReturnValue();
EXPECT_EQ(result.GetResultType(),
ScriptEvaluationResult::ResultType::kSuccess);
wait_event->Signal();
......
......@@ -14,6 +14,7 @@
#include "third_party/blink/renderer/bindings/core/v8/v8_gc_controller.h"
#include "third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/script/js_module_script.h"
#include "third_party/blink/renderer/core/script/script.h"
#include "third_party/blink/renderer/core/testing/page_test_base.h"
#include "third_party/blink/renderer/core/workers/parent_execution_context_task_runners.h"
......@@ -81,9 +82,11 @@ class AnimationAndPaintWorkletThreadTest : public PageTestBase {
ScriptValue exception =
ModuleRecord::Instantiate(script_state, module, js_url);
EXPECT_TRUE(exception.IsEmpty());
EXPECT_EQ(
ModuleRecord::Evaluate(script_state, module, js_url).GetResultType(),
ScriptEvaluationResult::ResultType::kSuccess);
EXPECT_EQ(JSModuleScript::CreateForTest(Modulator::From(script_state),
module, js_url)
->RunScriptAndReturnValue()
.GetResultType(),
ScriptEvaluationResult::ResultType::kSuccess);
wait_event->Signal();
}
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment