LibWeb: Implement the "error to rethrow" mechanism in HTML::Script

This allows JS module loads to fail and throw without crashing the
WebContent process due to a TODO() assertion.
This commit is contained in:
Andreas Kling 2023-05-18 19:05:56 +02:00
parent 2959c2f2eb
commit 819fb39a87
6 changed files with 47 additions and 22 deletions

View file

@ -42,8 +42,9 @@ JS::NonnullGCPtr<ClassicScript> ClassicScript::create(DeprecatedString filename,
// 8. Set script's muted errors to muted errors.
script->m_muted_errors = muted_errors;
// FIXME: 9. Set script's parse error and error to rethrow to null.
// NOTE: Error to rethrow was set to null in the construction of ClassicScript. We do not have parse error as it would currently go unused.
// 9. Set script's parse error and error to rethrow to null.
script->set_parse_error(JS::js_null());
script->set_error_to_rethrow(JS::js_null());
// 10. Let result be ParseScript(source, settings's Realm, script).
auto parse_timer = Core::ElapsedTimer::start_new();
@ -55,9 +56,9 @@ JS::NonnullGCPtr<ClassicScript> ClassicScript::create(DeprecatedString filename,
auto& parse_error = result.error().first();
dbgln_if(HTML_SCRIPT_DEBUG, "ClassicScript: Failed to parse: {}", parse_error.to_deprecated_string());
// FIXME: 1. Set script's parse error and its error to rethrow to result[0].
// We do not have parse error as it would currently go unused.
script->m_error_to_rethrow = parse_error;
// 1. Set script's parse error and its error to rethrow to result[0].
script->set_parse_error(JS::SyntaxError::create(environment_settings_object.realm(), parse_error.to_string().release_value_but_fixme_should_propagate_errors()));
script->set_error_to_rethrow(script->parse_error());
// 2. Return script.
return script;
@ -73,8 +74,6 @@ JS::NonnullGCPtr<ClassicScript> ClassicScript::create(DeprecatedString filename,
// https://html.spec.whatwg.org/multipage/webappapis.html#run-a-classic-script
JS::Completion ClassicScript::run(RethrowErrors rethrow_errors, JS::GCPtr<JS::Environment> lexical_environment_override)
{
auto& vm = settings_object().realm().vm();
// 1. Let settings be the settings object of script.
auto& settings = settings_object();
@ -89,8 +88,8 @@ JS::Completion ClassicScript::run(RethrowErrors rethrow_errors, JS::GCPtr<JS::En
JS::Completion evaluation_status;
// 5. If script's error to rethrow is not null, then set evaluationStatus to Completion { [[Type]]: throw, [[Value]]: script's error to rethrow, [[Target]]: empty }.
if (m_error_to_rethrow.has_value()) {
evaluation_status = vm.throw_completion<JS::SyntaxError>(TRY_OR_THROW_OOM(vm, m_error_to_rethrow.value().to_string()));
if (!error_to_rethrow().is_null()) {
evaluation_status = JS::Completion { JS::Completion::Type::Throw, error_to_rethrow(), {} };
} else {
auto timer = Core::ElapsedTimer::start_new();

View file

@ -43,7 +43,6 @@ private:
JS::GCPtr<JS::Script> m_script_record;
MutedErrors m_muted_errors { MutedErrors::No };
Optional<JS::ParserError> m_error_to_rethrow;
};
}

View file

@ -619,9 +619,10 @@ void fetch_descendants_of_and_link_a_module_script(JavaScriptModuleScript& modul
// 2. Perform record.Link().
auto linking_result = const_cast<JS::SourceTextModule&>(record).link(result->vm());
// TODO: If this throws an exception, set result's error to rethrow to that exception.
if (linking_result.is_error())
TODO();
// If this throws an exception, set result's error to rethrow to that exception.
if (linking_result.is_throw_completion()) {
result->set_error_to_rethrow(linking_result.release_error().value().value());
}
} else {
// FIXME: 4. Otherwise, set result's error to rethrow to parse error.
TODO();

View file

@ -49,7 +49,8 @@ WebIDL::ExceptionOr<JS::GCPtr<JavaScriptModuleScript>> JavaScriptModuleScript::c
// FIXME: 5. Set script's fetch options to options.
// 6. Set script's parse error and error to rethrow to null.
// NOTE: Parse error and error to rethrow were set to null in the construction of Script.
script->set_parse_error(JS::js_null());
script->set_error_to_rethrow(JS::js_null());
// 7. Let result be ParseModule(source, settings's Realm, script).
auto result = JS::SourceTextModule::parse(source, settings_object.realm(), filename.view(), script);
@ -59,7 +60,8 @@ WebIDL::ExceptionOr<JS::GCPtr<JavaScriptModuleScript>> JavaScriptModuleScript::c
auto& parse_error = result.error().first();
dbgln("JavaScriptModuleScript: Failed to parse: {}", parse_error.to_deprecated_string());
// FIXME: 1. Set script's parse error to result[0].
// 1. Set script's parse error to result[0].
script->set_parse_error(JS::SyntaxError::create(settings_object.realm(), parse_error.to_string().release_value_but_fixme_should_propagate_errors()));
// 2. Return script.
return script;
@ -123,12 +125,16 @@ JS::Promise* JavaScriptModuleScript::run(PreventErrorReporting)
// 4. Let evaluationPromise be null.
JS::Promise* evaluation_promise = nullptr;
// FIXME: 5. If script's error to rethrow is not null, then set evaluationPromise to a promise rejected with script's error to rethrow.
// 5. If script's error to rethrow is not null, then set evaluationPromise to a promise rejected with script's error to rethrow.
if (!error_to_rethrow().is_null()) {
evaluation_promise = JS::Promise::create(settings.realm());
evaluation_promise->reject(error_to_rethrow());
}
// 6. Otherwise:
if (m_record) {
else {
// 1. Let record be script's record.
auto record = m_record;
VERIFY(record);
auto interpreter = JS::Interpreter::create_with_existing_realm(settings.realm());
JS::VM::InterpreterExecutionScope scope(*interpreter);
@ -147,8 +153,6 @@ JS::Promise* JavaScriptModuleScript::run(PreventErrorReporting)
} else {
evaluation_promise = elevation_promise_or_error.value();
}
} else {
TODO();
}
// FIXME: 7. If preventErrorReporting is false, then upon rejection of evaluationPromise with reason, report the exception given by reason for script.

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021-2023, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -23,4 +23,12 @@ void Script::visit_host_defined_self(JS::Cell::Visitor& visitor)
visitor.visit(this);
}
void Script::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_settings_object);
visitor.visit(m_parse_error);
visitor.visit(m_error_to_rethrow);
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021-2023, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -27,15 +27,29 @@ public:
EnvironmentSettingsObject& settings_object() { return m_settings_object; }
[[nodiscard]] JS::Value error_to_rethrow() const { return m_error_to_rethrow; }
void set_error_to_rethrow(JS::Value value) { m_error_to_rethrow = value; }
[[nodiscard]] JS::Value parse_error() const { return m_parse_error; }
void set_parse_error(JS::Value value) { m_parse_error = value; }
protected:
Script(AK::URL base_url, DeprecatedString filename, EnvironmentSettingsObject& environment_settings_object);
virtual void visit_edges(Visitor&) override;
private:
virtual void visit_host_defined_self(JS::Cell::Visitor&) override;
AK::URL m_base_url;
DeprecatedString m_filename;
JS::NonnullGCPtr<EnvironmentSettingsObject> m_settings_object;
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-script-parse-error
JS::Value m_parse_error;
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-script-error-to-rethrow
JS::Value m_error_to_rethrow;
};
}