summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Ladybird/WebContent/main.cpp3
-rw-r--r--Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp564
-rw-r--r--Userland/Libraries/LibWeb/Bindings/MainThreadVM.h3
-rw-r--r--Userland/Services/WebContent/main.cpp2
4 files changed, 294 insertions, 278 deletions
diff --git a/Ladybird/WebContent/main.cpp b/Ladybird/WebContent/main.cpp
index f30f672378..abcc8c7e85 100644
--- a/Ladybird/WebContent/main.cpp
+++ b/Ladybird/WebContent/main.cpp
@@ -20,6 +20,7 @@
#include <LibCore/SystemServerTakeover.h>
#include <LibIPC/ConnectionFromClient.h>
#include <LibMain/Main.h>
+#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/Loader/ContentFilter.h>
#include <LibWeb/Loader/FrameLoader.h>
#include <LibWeb/Loader/ResourceLoader.h>
@@ -78,6 +79,8 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
Web::FrameLoader::set_error_page_url(DeprecatedString::formatted("file://{}/res/html/error.html", s_serenity_resource_root));
+ TRY(Web::Bindings::initialize_main_thread_vm());
+
auto maybe_content_filter_error = load_content_filters();
if (maybe_content_filter_error.is_error())
dbgln("Failed to load content filters: {}", maybe_content_filter_error.error());
diff --git a/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp b/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp
index 00e6a3fb6d..63ee19ef93 100644
--- a/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp
+++ b/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp
@@ -32,6 +32,8 @@
namespace Web::Bindings {
+static RefPtr<JS::VM> s_main_thread_vm;
+
// https://html.spec.whatwg.org/multipage/webappapis.html#active-script
HTML::Script* active_script()
{
@@ -52,324 +54,330 @@ HTML::Script* active_script()
});
}
-JS::VM& main_thread_vm()
+ErrorOr<void> initialize_main_thread_vm()
{
- static RefPtr<JS::VM> vm;
- if (!vm) {
- vm = JS::VM::create(make<WebEngineCustomData>()).release_value_but_fixme_should_propagate_errors();
-
- // NOTE: We intentionally leak the main thread JavaScript VM.
- // This avoids doing an exhaustive garbage collection on process exit.
- vm->ref();
-
- static_cast<WebEngineCustomData*>(vm->custom_data())->event_loop.set_vm(*vm);
-
- // 8.1.5.1 HostEnsureCanAddPrivateElement(O), https://html.spec.whatwg.org/multipage/webappapis.html#the-hostensurecanaddprivateelement-implementation
- vm->host_ensure_can_add_private_element = [](JS::Object const& object) -> JS::ThrowCompletionOr<void> {
- // 1. If O is a WindowProxy object, or implements Location, then return Completion { [[Type]]: throw, [[Value]]: a new TypeError }.
- if (is<HTML::WindowProxy>(object) || is<HTML::Location>(object))
- return vm->throw_completion<JS::TypeError>("Cannot add private elements to window or location object"sv);
-
- // 2. Return NormalCompletion(unused).
- return {};
- };
-
- // FIXME: Implement 8.1.5.2 HostEnsureCanCompileStrings(callerRealm, calleeRealm), https://html.spec.whatwg.org/multipage/webappapis.html#hostensurecancompilestrings(callerrealm,-calleerealm)
-
- // 8.1.5.3 HostPromiseRejectionTracker(promise, operation), https://html.spec.whatwg.org/multipage/webappapis.html#the-hostpromiserejectiontracker-implementation
- vm->host_promise_rejection_tracker = [](JS::Promise& promise, JS::Promise::RejectionOperation operation) {
- // 1. Let script be the running script.
- // The running script is the script in the [[HostDefined]] field in the ScriptOrModule component of the running JavaScript execution context.
- HTML::Script* script { nullptr };
- vm->running_execution_context().script_or_module.visit(
- [&script](JS::NonnullGCPtr<JS::Script>& js_script) {
- script = verify_cast<HTML::ClassicScript>(js_script->host_defined());
- },
- [&script](JS::NonnullGCPtr<JS::Module>& js_module) {
- script = verify_cast<HTML::ModuleScript>(js_module->host_defined());
- },
- [](Empty) {
- });
+ VERIFY(!s_main_thread_vm);
+
+ s_main_thread_vm = TRY(JS::VM::create(make<WebEngineCustomData>()));
+
+ // NOTE: We intentionally leak the main thread JavaScript VM.
+ // This avoids doing an exhaustive garbage collection on process exit.
+ s_main_thread_vm->ref();
+
+ static_cast<WebEngineCustomData*>(s_main_thread_vm->custom_data())->event_loop.set_vm(*s_main_thread_vm);
+
+ // 8.1.5.1 HostEnsureCanAddPrivateElement(O), https://html.spec.whatwg.org/multipage/webappapis.html#the-hostensurecanaddprivateelement-implementation
+ s_main_thread_vm->host_ensure_can_add_private_element = [](JS::Object const& object) -> JS::ThrowCompletionOr<void> {
+ // 1. If O is a WindowProxy object, or implements Location, then return Completion { [[Type]]: throw, [[Value]]: a new TypeError }.
+ if (is<HTML::WindowProxy>(object) || is<HTML::Location>(object))
+ return s_main_thread_vm->throw_completion<JS::TypeError>("Cannot add private elements to window or location object"sv);
+
+ // 2. Return NormalCompletion(unused).
+ return {};
+ };
+
+ // FIXME: Implement 8.1.5.2 HostEnsureCanCompileStrings(callerRealm, calleeRealm), https://html.spec.whatwg.org/multipage/webappapis.html#hostensurecancompilestrings(callerrealm,-calleerealm)
+
+ // 8.1.5.3 HostPromiseRejectionTracker(promise, operation), https://html.spec.whatwg.org/multipage/webappapis.html#the-hostpromiserejectiontracker-implementation
+ s_main_thread_vm->host_promise_rejection_tracker = [](JS::Promise& promise, JS::Promise::RejectionOperation operation) {
+ // 1. Let script be the running script.
+ // The running script is the script in the [[HostDefined]] field in the ScriptOrModule component of the running JavaScript execution context.
+ HTML::Script* script { nullptr };
+ s_main_thread_vm->running_execution_context().script_or_module.visit(
+ [&script](JS::NonnullGCPtr<JS::Script>& js_script) {
+ script = verify_cast<HTML::ClassicScript>(js_script->host_defined());
+ },
+ [&script](JS::NonnullGCPtr<JS::Module>& js_module) {
+ script = verify_cast<HTML::ModuleScript>(js_module->host_defined());
+ },
+ [](Empty) {
+ });
- // 2. If script is a classic script and script's muted errors is true, then return.
- // NOTE: is<T>() returns false if nullptr is passed.
- if (is<HTML::ClassicScript>(script)) {
- auto const& classic_script = static_cast<HTML::ClassicScript const&>(*script);
- if (classic_script.muted_errors() == HTML::ClassicScript::MutedErrors::Yes)
- return;
- }
+ // 2. If script is a classic script and script's muted errors is true, then return.
+ // NOTE: is<T>() returns false if nullptr is passed.
+ if (is<HTML::ClassicScript>(script)) {
+ auto const& classic_script = static_cast<HTML::ClassicScript const&>(*script);
+ if (classic_script.muted_errors() == HTML::ClassicScript::MutedErrors::Yes)
+ return;
+ }
- // 3. Let settings object be the current settings object.
- // 4. If script is not null, then set settings object to script's settings object.
- auto& settings_object = script ? script->settings_object() : HTML::current_settings_object();
-
- switch (operation) {
- case JS::Promise::RejectionOperation::Reject:
- // 4. If operation is "reject",
- // 1. Add promise to settings object's about-to-be-notified rejected promises list.
- settings_object.push_onto_about_to_be_notified_rejected_promises_list(promise);
- break;
- case JS::Promise::RejectionOperation::Handle: {
- // 5. If operation is "handle",
- // 1. If settings object's about-to-be-notified rejected promises list contains promise, then remove promise from that list and return.
- bool removed_about_to_be_notified_rejected_promise = settings_object.remove_from_about_to_be_notified_rejected_promises_list(promise);
- if (removed_about_to_be_notified_rejected_promise)
- return;
+ // 3. Let settings object be the current settings object.
+ // 4. If script is not null, then set settings object to script's settings object.
+ auto& settings_object = script ? script->settings_object() : HTML::current_settings_object();
+
+ switch (operation) {
+ case JS::Promise::RejectionOperation::Reject:
+ // 4. If operation is "reject",
+ // 1. Add promise to settings object's about-to-be-notified rejected promises list.
+ settings_object.push_onto_about_to_be_notified_rejected_promises_list(promise);
+ break;
+ case JS::Promise::RejectionOperation::Handle: {
+ // 5. If operation is "handle",
+ // 1. If settings object's about-to-be-notified rejected promises list contains promise, then remove promise from that list and return.
+ bool removed_about_to_be_notified_rejected_promise = settings_object.remove_from_about_to_be_notified_rejected_promises_list(promise);
+ if (removed_about_to_be_notified_rejected_promise)
+ return;
+
+ // 3. Remove promise from settings object's outstanding rejected promises weak set.
+ bool removed_outstanding_rejected_promise = settings_object.remove_from_outstanding_rejected_promises_weak_set(&promise);
+
+ // 2. If settings object's outstanding rejected promises weak set does not contain promise, then return.
+ // NOTE: This is done out of order because removed_outstanding_rejected_promise will be false if the promise wasn't in the set or true if it was and got removed.
+ if (!removed_outstanding_rejected_promise)
+ return;
+
+ // 4. Let global be settings object's global object.
+ auto& global = settings_object.global_object();
+
+ // 5. Queue a global task on the DOM manipulation task source given global to fire an event named rejectionhandled at global, using PromiseRejectionEvent,
+ // with the promise attribute initialized to promise, and the reason attribute initialized to the value of promise's [[PromiseResult]] internal slot.
+ HTML::queue_global_task(HTML::Task::Source::DOMManipulation, global, [&global, &promise] {
+ // FIXME: This currently assumes that global is a WindowObject.
+ auto& window = verify_cast<HTML::Window>(global);
+
+ HTML::PromiseRejectionEventInit event_init {
+ {}, // Initialize the inherited DOM::EventInit
+ /* .promise = */ promise,
+ /* .reason = */ promise.result(),
+ };
+ auto promise_rejection_event = HTML::PromiseRejectionEvent::create(HTML::relevant_realm(global), String::from_deprecated_string(HTML::EventNames::rejectionhandled).release_value_but_fixme_should_propagate_errors(), event_init).release_value_but_fixme_should_propagate_errors();
+ window.dispatch_event(promise_rejection_event);
+ });
+ break;
+ }
+ default:
+ VERIFY_NOT_REACHED();
+ }
+ };
- // 3. Remove promise from settings object's outstanding rejected promises weak set.
- bool removed_outstanding_rejected_promise = settings_object.remove_from_outstanding_rejected_promises_weak_set(&promise);
+ // 8.1.5.4.1 HostCallJobCallback(callback, V, argumentsList), https://html.spec.whatwg.org/multipage/webappapis.html#hostcalljobcallback
+ s_main_thread_vm->host_call_job_callback = [](JS::JobCallback& callback, JS::Value this_value, JS::MarkedVector<JS::Value> arguments_list) {
+ auto& callback_host_defined = verify_cast<WebEngineCustomJobCallbackData>(*callback.custom_data);
- // 2. If settings object's outstanding rejected promises weak set does not contain promise, then return.
- // NOTE: This is done out of order because removed_outstanding_rejected_promise will be false if the promise wasn't in the set or true if it was and got removed.
- if (!removed_outstanding_rejected_promise)
- return;
+ // 1. Let incumbent settings be callback.[[HostDefined]].[[IncumbentSettings]]. (NOTE: Not necessary)
+ // 2. Let script execution context be callback.[[HostDefined]].[[ActiveScriptContext]]. (NOTE: Not necessary)
- // 4. Let global be settings object's global object.
- auto& global = settings_object.global_object();
-
- // 5. Queue a global task on the DOM manipulation task source given global to fire an event named rejectionhandled at global, using PromiseRejectionEvent,
- // with the promise attribute initialized to promise, and the reason attribute initialized to the value of promise's [[PromiseResult]] internal slot.
- HTML::queue_global_task(HTML::Task::Source::DOMManipulation, global, [&global, &promise] {
- // FIXME: This currently assumes that global is a WindowObject.
- auto& window = verify_cast<HTML::Window>(global);
-
- HTML::PromiseRejectionEventInit event_init {
- {}, // Initialize the inherited DOM::EventInit
- /* .promise = */ promise,
- /* .reason = */ promise.result(),
- };
- auto promise_rejection_event = HTML::PromiseRejectionEvent::create(HTML::relevant_realm(global), String::from_deprecated_string(HTML::EventNames::rejectionhandled).release_value_but_fixme_should_propagate_errors(), event_init).release_value_but_fixme_should_propagate_errors();
- window.dispatch_event(promise_rejection_event);
- });
- break;
- }
- default:
- VERIFY_NOT_REACHED();
- }
- };
+ // 3. Prepare to run a callback with incumbent settings.
+ callback_host_defined.incumbent_settings->prepare_to_run_callback();
- // 8.1.5.4.1 HostCallJobCallback(callback, V, argumentsList), https://html.spec.whatwg.org/multipage/webappapis.html#hostcalljobcallback
- vm->host_call_job_callback = [](JS::JobCallback& callback, JS::Value this_value, JS::MarkedVector<JS::Value> arguments_list) {
- auto& callback_host_defined = verify_cast<WebEngineCustomJobCallbackData>(*callback.custom_data);
+ // 4. If script execution context is not null, then push script execution context onto the JavaScript execution context stack.
+ if (callback_host_defined.active_script_context)
+ s_main_thread_vm->push_execution_context(*callback_host_defined.active_script_context);
- // 1. Let incumbent settings be callback.[[HostDefined]].[[IncumbentSettings]]. (NOTE: Not necessary)
- // 2. Let script execution context be callback.[[HostDefined]].[[ActiveScriptContext]]. (NOTE: Not necessary)
+ // 5. Let result be Call(callback.[[Callback]], V, argumentsList).
+ auto result = JS::call(*s_main_thread_vm, *callback.callback.cell(), this_value, move(arguments_list));
- // 3. Prepare to run a callback with incumbent settings.
- callback_host_defined.incumbent_settings->prepare_to_run_callback();
+ // 6. If script execution context is not null, then pop script execution context from the JavaScript execution context stack.
+ if (callback_host_defined.active_script_context) {
+ VERIFY(&s_main_thread_vm->running_execution_context() == callback_host_defined.active_script_context.ptr());
+ s_main_thread_vm->pop_execution_context();
+ }
- // 4. If script execution context is not null, then push script execution context onto the JavaScript execution context stack.
- if (callback_host_defined.active_script_context)
- vm->push_execution_context(*callback_host_defined.active_script_context);
+ // 7. Clean up after running a callback with incumbent settings.
+ callback_host_defined.incumbent_settings->clean_up_after_running_callback();
- // 5. Let result be Call(callback.[[Callback]], V, argumentsList).
- auto result = JS::call(*vm, *callback.callback.cell(), this_value, move(arguments_list));
+ // 8. Return result.
+ return result;
+ };
- // 6. If script execution context is not null, then pop script execution context from the JavaScript execution context stack.
- if (callback_host_defined.active_script_context) {
- VERIFY(&vm->running_execution_context() == callback_host_defined.active_script_context.ptr());
- vm->pop_execution_context();
- }
+ // 8.1.5.4.2 HostEnqueueFinalizationRegistryCleanupJob(finalizationRegistry), https://html.spec.whatwg.org/multipage/webappapis.html#hostenqueuefinalizationregistrycleanupjob
+ s_main_thread_vm->host_enqueue_finalization_registry_cleanup_job = [](JS::FinalizationRegistry& finalization_registry) {
+ // 1. Let global be finalizationRegistry.[[Realm]]'s global object.
+ auto& global = finalization_registry.realm().global_object();
+
+ // 2. Queue a global task on the JavaScript engine task source given global to perform the following steps:
+ HTML::queue_global_task(HTML::Task::Source::JavaScriptEngine, global, [&finalization_registry] {
+ // 1. Let entry be finalizationRegistry.[[CleanupCallback]].[[Callback]].[[Realm]]'s environment settings object.
+ auto& entry = host_defined_environment_settings_object(*finalization_registry.cleanup_callback().callback.cell()->realm());
- // 7. Clean up after running a callback with incumbent settings.
- callback_host_defined.incumbent_settings->clean_up_after_running_callback();
+ // 2. Check if we can run script with entry. If this returns "do not run", then return.
+ if (entry.can_run_script() == HTML::RunScriptDecision::DoNotRun)
+ return;
- // 8. Return result.
- return result;
- };
+ // 3. Prepare to run script with entry.
+ entry.prepare_to_run_script();
- // 8.1.5.4.2 HostEnqueueFinalizationRegistryCleanupJob(finalizationRegistry), https://html.spec.whatwg.org/multipage/webappapis.html#hostenqueuefinalizationregistrycleanupjob
- vm->host_enqueue_finalization_registry_cleanup_job = [](JS::FinalizationRegistry& finalization_registry) {
- // 1. Let global be finalizationRegistry.[[Realm]]'s global object.
- auto& global = finalization_registry.realm().global_object();
+ // 4. Let result be the result of performing CleanupFinalizationRegistry(finalizationRegistry).
+ auto result = finalization_registry.cleanup();
- // 2. Queue a global task on the JavaScript engine task source given global to perform the following steps:
- HTML::queue_global_task(HTML::Task::Source::JavaScriptEngine, global, [&finalization_registry] {
- // 1. Let entry be finalizationRegistry.[[CleanupCallback]].[[Callback]].[[Realm]]'s environment settings object.
- auto& entry = host_defined_environment_settings_object(*finalization_registry.cleanup_callback().callback.cell()->realm());
+ // 5. Clean up after running script with entry.
+ entry.clean_up_after_running_script();
- // 2. Check if we can run script with entry. If this returns "do not run", then return.
- if (entry.can_run_script() == HTML::RunScriptDecision::DoNotRun)
+ // 6. If result is an abrupt completion, then report the exception given by result.[[Value]].
+ if (result.is_error())
+ HTML::report_exception(result, finalization_registry.realm());
+ });
+ };
+
+ // 8.1.5.4.3 HostEnqueuePromiseJob(job, realm), https://html.spec.whatwg.org/multipage/webappapis.html#hostenqueuepromisejob
+ s_main_thread_vm->host_enqueue_promise_job = [](Function<JS::ThrowCompletionOr<JS::Value>()> job, JS::Realm* realm) {
+ // 1. If realm is not null, then let job settings be the settings object for realm. Otherwise, let job settings be null.
+ HTML::EnvironmentSettingsObject* job_settings { nullptr };
+ if (realm)
+ job_settings = &host_defined_environment_settings_object(*realm);
+
+ // IMPLEMENTATION DEFINED: The JS spec says we must take implementation defined steps to make the currently active script or module at the time of HostEnqueuePromiseJob being invoked
+ // also be the active script or module of the job at the time of its invocation.
+ // This means taking it here now and passing it through to the lambda.
+ auto script_or_module = s_main_thread_vm->get_active_script_or_module();
+
+ // 2. Queue a microtask on the surrounding agent's event loop to perform the following steps:
+ // This instance of "queue a microtask" uses the "implied document". The best fit for "implied document" here is "If the task is being queued by or for a script, then return the script's settings object's responsible document."
+ // Do note that "implied document" from the spec is handwavy and the spec authors are trying to get rid of it: https://github.com/whatwg/html/issues/4980
+ auto* script = active_script();
+
+ // NOTE: This keeps job_settings alive by keeping realm alive, which is holding onto job_settings.
+ HTML::queue_a_microtask(script ? script->settings_object().responsible_document().ptr() : nullptr, [job_settings, job = move(job), script_or_module = move(script_or_module)] {
+ // The dummy execution context has to be kept up here to keep it alive for the duration of the function.
+ Optional<JS::ExecutionContext> dummy_execution_context;
+
+ if (job_settings) {
+ // 1. If job settings is not null, then check if we can run script with job settings. If this returns "do not run" then return.
+ if (job_settings->can_run_script() == HTML::RunScriptDecision::DoNotRun)
return;
- // 3. Prepare to run script with entry.
- entry.prepare_to_run_script();
+ // 2. If job settings is not null, then prepare to run script with job settings.
+ job_settings->prepare_to_run_script();
+
+ // IMPLEMENTATION DEFINED: Additionally to preparing to run a script, we also prepare to run a callback here. This matches WebIDL's
+ // invoke_callback() / call_user_object_operation() functions, and prevents a crash in host_make_job_callback()
+ // when getting the incumbent settings object.
+ job_settings->prepare_to_run_callback();
+
+ // IMPLEMENTATION DEFINED: Per the previous "implementation defined" comment, we must now make the script or module the active script or module.
+ // Since the only active execution context currently is the realm execution context of job settings, lets attach it here.
+ job_settings->realm_execution_context().script_or_module = script_or_module;
+ } else {
+ // FIXME: We need to setup a dummy execution context in case a JS::NativeFunction is called when processing the job.
+ // This is because JS::NativeFunction::call excepts something to be on the execution context stack to be able to get the caller context to initialize the environment.
+ // Do note that the JS spec gives _no_ guarantee that the execution context stack has something on it if HostEnqueuePromiseJob was called with a null realm: https://tc39.es/ecma262/#job-preparedtoevaluatecode
+ dummy_execution_context = JS::ExecutionContext { s_main_thread_vm->heap() };
+ dummy_execution_context->script_or_module = script_or_module;
+ s_main_thread_vm->push_execution_context(dummy_execution_context.value());
+ }
- // 4. Let result be the result of performing CleanupFinalizationRegistry(finalizationRegistry).
- auto result = finalization_registry.cleanup();
+ // 3. Let result be job().
+ [[maybe_unused]] auto result = job();
- // 5. Clean up after running script with entry.
- entry.clean_up_after_running_script();
+ // 4. If job settings is not null, then clean up after running script with job settings.
+ if (job_settings) {
+ // IMPLEMENTATION DEFINED: Disassociate the realm execution context from the script or module.
+ job_settings->realm_execution_context().script_or_module = Empty {};
- // 6. If result is an abrupt completion, then report the exception given by result.[[Value]].
- if (result.is_error())
- HTML::report_exception(result, finalization_registry.realm());
- });
- };
-
- // 8.1.5.4.3 HostEnqueuePromiseJob(job, realm), https://html.spec.whatwg.org/multipage/webappapis.html#hostenqueuepromisejob
- vm->host_enqueue_promise_job = [](Function<JS::ThrowCompletionOr<JS::Value>()> job, JS::Realm* realm) {
- // 1. If realm is not null, then let job settings be the settings object for realm. Otherwise, let job settings be null.
- HTML::EnvironmentSettingsObject* job_settings { nullptr };
- if (realm)
- job_settings = &host_defined_environment_settings_object(*realm);
-
- // IMPLEMENTATION DEFINED: The JS spec says we must take implementation defined steps to make the currently active script or module at the time of HostEnqueuePromiseJob being invoked
- // also be the active script or module of the job at the time of its invocation.
- // This means taking it here now and passing it through to the lambda.
- auto script_or_module = vm->get_active_script_or_module();
-
- // 2. Queue a microtask on the surrounding agent's event loop to perform the following steps:
- // This instance of "queue a microtask" uses the "implied document". The best fit for "implied document" here is "If the task is being queued by or for a script, then return the script's settings object's responsible document."
- // Do note that "implied document" from the spec is handwavy and the spec authors are trying to get rid of it: https://github.com/whatwg/html/issues/4980
- auto* script = active_script();
-
- // NOTE: This keeps job_settings alive by keeping realm alive, which is holding onto job_settings.
- HTML::queue_a_microtask(script ? script->settings_object().responsible_document().ptr() : nullptr, [job_settings, job = move(job), script_or_module = move(script_or_module)] {
- // The dummy execution context has to be kept up here to keep it alive for the duration of the function.
- Optional<JS::ExecutionContext> dummy_execution_context;
-
- if (job_settings) {
- // 1. If job settings is not null, then check if we can run script with job settings. If this returns "do not run" then return.
- if (job_settings->can_run_script() == HTML::RunScriptDecision::DoNotRun)
- return;
-
- // 2. If job settings is not null, then prepare to run script with job settings.
- job_settings->prepare_to_run_script();
-
- // IMPLEMENTATION DEFINED: Additionally to preparing to run a script, we also prepare to run a callback here. This matches WebIDL's
- // invoke_callback() / call_user_object_operation() functions, and prevents a crash in host_make_job_callback()
- // when getting the incumbent settings object.
- job_settings->prepare_to_run_callback();
-
- // IMPLEMENTATION DEFINED: Per the previous "implementation defined" comment, we must now make the script or module the active script or module.
- // Since the only active execution context currently is the realm execution context of job settings, lets attach it here.
- job_settings->realm_execution_context().script_or_module = script_or_module;
- } else {
- // FIXME: We need to setup a dummy execution context in case a JS::NativeFunction is called when processing the job.
- // This is because JS::NativeFunction::call excepts something to be on the execution context stack to be able to get the caller context to initialize the environment.
- // Do note that the JS spec gives _no_ guarantee that the execution context stack has something on it if HostEnqueuePromiseJob was called with a null realm: https://tc39.es/ecma262/#job-preparedtoevaluatecode
- dummy_execution_context = JS::ExecutionContext { vm->heap() };
- dummy_execution_context->script_or_module = script_or_module;
- vm->push_execution_context(dummy_execution_context.value());
- }
+ // IMPLEMENTATION DEFINED: See comment above, we need to clean up the non-standard prepare_to_run_callback() call.
+ job_settings->clean_up_after_running_callback();
- // 3. Let result be job().
- [[maybe_unused]] auto result = job();
+ job_settings->clean_up_after_running_script();
+ } else {
+ // Pop off the dummy execution context. See the above FIXME block about why this is done.
+ s_main_thread_vm->pop_execution_context();
+ }
- // 4. If job settings is not null, then clean up after running script with job settings.
- if (job_settings) {
- // IMPLEMENTATION DEFINED: Disassociate the realm execution context from the script or module.
- job_settings->realm_execution_context().script_or_module = Empty {};
+ // 5. If result is an abrupt completion, then report the exception given by result.[[Value]].
+ if (result.is_error())
+ HTML::report_exception(result, job_settings->realm());
+ });
+ };
- // IMPLEMENTATION DEFINED: See comment above, we need to clean up the non-standard prepare_to_run_callback() call.
- job_settings->clean_up_after_running_callback();
+ // 8.1.5.4.4 HostMakeJobCallback(callable), https://html.spec.whatwg.org/multipage/webappapis.html#hostmakejobcallback
+ s_main_thread_vm->host_make_job_callback = [](JS::FunctionObject& callable) -> JS::JobCallback {
+ // 1. Let incumbent settings be the incumbent settings object.
+ auto& incumbent_settings = HTML::incumbent_settings_object();
- job_settings->clean_up_after_running_script();
- } else {
- // Pop off the dummy execution context. See the above FIXME block about why this is done.
- vm->pop_execution_context();
- }
+ // 2. Let active script be the active script.
+ auto* script = active_script();
- // 5. If result is an abrupt completion, then report the exception given by result.[[Value]].
- if (result.is_error())
- HTML::report_exception(result, job_settings->realm());
- });
- };
-
- // 8.1.5.4.4 HostMakeJobCallback(callable), https://html.spec.whatwg.org/multipage/webappapis.html#hostmakejobcallback
- vm->host_make_job_callback = [](JS::FunctionObject& callable) -> JS::JobCallback {
- // 1. Let incumbent settings be the incumbent settings object.
- auto& incumbent_settings = HTML::incumbent_settings_object();
-
- // 2. Let active script be the active script.
- auto* script = active_script();
-
- // 3. Let script execution context be null.
- OwnPtr<JS::ExecutionContext> script_execution_context;
-
- // 4. If active script is not null, set script execution context to a new JavaScript execution context, with its Function field set to null,
- // its Realm field set to active script's settings object's Realm, and its ScriptOrModule set to active script's record.
- if (script) {
- script_execution_context = adopt_own(*new JS::ExecutionContext(vm->heap()));
- script_execution_context->function = nullptr;
- script_execution_context->realm = &script->settings_object().realm();
- if (is<HTML::ClassicScript>(script)) {
- script_execution_context->script_or_module = JS::NonnullGCPtr<JS::Script>(*verify_cast<HTML::ClassicScript>(script)->script_record());
- } else if (is<HTML::ModuleScript>(script)) {
- if (is<HTML::JavaScriptModuleScript>(script)) {
- script_execution_context->script_or_module = JS::NonnullGCPtr<JS::Module>(*verify_cast<HTML::JavaScriptModuleScript>(script)->record());
- } else {
- // NOTE: Handle CSS and JSON module scripts once we have those.
- VERIFY_NOT_REACHED();
- }
+ // 3. Let script execution context be null.
+ OwnPtr<JS::ExecutionContext> script_execution_context;
+
+ // 4. If active script is not null, set script execution context to a new JavaScript execution context, with its Function field set to null,
+ // its Realm field set to active script's settings object's Realm, and its ScriptOrModule set to active script's record.
+ if (script) {
+ script_execution_context = adopt_own(*new JS::ExecutionContext(s_main_thread_vm->heap()));
+ script_execution_context->function = nullptr;
+ script_execution_context->realm = &script->settings_object().realm();
+ if (is<HTML::ClassicScript>(script)) {
+ script_execution_context->script_or_module = JS::NonnullGCPtr<JS::Script>(*verify_cast<HTML::ClassicScript>(script)->script_record());
+ } else if (is<HTML::ModuleScript>(script)) {
+ if (is<HTML::JavaScriptModuleScript>(script)) {
+ script_execution_context->script_or_module = JS::NonnullGCPtr<JS::Module>(*verify_cast<HTML::JavaScriptModuleScript>(script)->record());
} else {
+ // NOTE: Handle CSS and JSON module scripts once we have those.
VERIFY_NOT_REACHED();
}
+ } else {
+ VERIFY_NOT_REACHED();
}
+ }
- // 5. Return the JobCallback Record { [[Callback]]: callable, [[HostDefined]]: { [[IncumbentSettings]]: incumbent settings, [[ActiveScriptContext]]: script execution context } }.
- auto host_defined = adopt_own(*new WebEngineCustomJobCallbackData(incumbent_settings, move(script_execution_context)));
- return { JS::make_handle(&callable), move(host_defined) };
- };
-
- // FIXME: Implement 8.1.5.5.1 HostGetImportMetaProperties(moduleRecord), https://html.spec.whatwg.org/multipage/webappapis.html#hostgetimportmetaproperties
- // FIXME: Implement 8.1.5.5.2 HostImportModuleDynamically(referencingScriptOrModule, moduleRequest, promiseCapability), https://html.spec.whatwg.org/multipage/webappapis.html#hostimportmoduledynamically(referencingscriptormodule,-modulerequest,-promisecapability)
- // FIXME: Implement 8.1.5.5.3 HostResolveImportedModule(referencingScriptOrModule, moduleRequest), https://html.spec.whatwg.org/multipage/webappapis.html#hostresolveimportedmodule(referencingscriptormodule,-modulerequest)
-
- // 8.1.5.5.4 HostGetSupportedImportAssertions(), https://html.spec.whatwg.org/multipage/webappapis.html#hostgetsupportedimportassertions
- vm->host_get_supported_import_assertions = []() -> Vector<DeprecatedString> {
- // 1. Return « "type" ».
- return { "type"sv };
- };
-
- // 8.1.6.5.3 HostResolveImportedModule(referencingScriptOrModule, moduleRequest), https://html.spec.whatwg.org/multipage/webappapis.html#hostresolveimportedmodule(referencingscriptormodule,-modulerequest)
- vm->host_resolve_imported_module = [](JS::ScriptOrModule const& referencing_string_or_module, JS::ModuleRequest const& module_request) -> JS::ThrowCompletionOr<JS::NonnullGCPtr<JS::Module>> {
- // 1. Let moduleMap and referencingScript be null.
- Optional<HTML::ModuleMap&> module_map;
- Optional<HTML::Script&> referencing_script;
-
- // 2. If referencingScriptOrModule is not null, then:
- if (!referencing_string_or_module.has<Empty>()) {
- // 1. Set referencingScript to referencingScriptOrModule.[[HostDefined]].
- referencing_script = verify_cast<HTML::Script>(referencing_string_or_module.has<JS::NonnullGCPtr<JS::Script>>() ? *referencing_string_or_module.get<JS::NonnullGCPtr<JS::Script>>()->host_defined() : *referencing_string_or_module.get<JS::NonnullGCPtr<JS::Module>>()->host_defined());
-
- // 2. Set moduleMap to referencingScript's settings object's module map.
- module_map = referencing_script->settings_object().module_map();
- }
- // 3. Otherwise:
- else {
- // 1. Assert: there is a current settings object.
- // NOTE: This is handled by the HTML::current_settings_object() accessor.
+ // 5. Return the JobCallback Record { [[Callback]]: callable, [[HostDefined]]: { [[IncumbentSettings]]: incumbent settings, [[ActiveScriptContext]]: script execution context } }.
+ auto host_defined = adopt_own(*new WebEngineCustomJobCallbackData(incumbent_settings, move(script_execution_context)));
+ return { JS::make_handle(&callable), move(host_defined) };
+ };
+
+ // FIXME: Implement 8.1.5.5.1 HostGetImportMetaProperties(moduleRecord), https://html.spec.whatwg.org/multipage/webappapis.html#hostgetimportmetaproperties
+ // FIXME: Implement 8.1.5.5.2 HostImportModuleDynamically(referencingScriptOrModule, moduleRequest, promiseCapability), https://html.spec.whatwg.org/multipage/webappapis.html#hostimportmoduledynamically(referencingscriptormodule,-modulerequest,-promisecapability)
+ // FIXME: Implement 8.1.5.5.3 HostResolveImportedModule(referencingScriptOrModule, moduleRequest), https://html.spec.whatwg.org/multipage/webappapis.html#hostresolveimportedmodule(referencingscriptormodule,-modulerequest)
+
+ // 8.1.5.5.4 HostGetSupportedImportAssertions(), https://html.spec.whatwg.org/multipage/webappapis.html#hostgetsupportedimportassertions
+ s_main_thread_vm->host_get_supported_import_assertions = []() -> Vector<DeprecatedString> {
+ // 1. Return « "type" ».
+ return { "type"sv };
+ };
+
+ // 8.1.6.5.3 HostResolveImportedModule(referencingScriptOrModule, moduleRequest), https://html.spec.whatwg.org/multipage/webappapis.html#hostresolveimportedmodule(referencingscriptormodule,-modulerequest)
+ s_main_thread_vm->host_resolve_imported_module = [](JS::ScriptOrModule const& referencing_string_or_module, JS::ModuleRequest const& module_request) -> JS::ThrowCompletionOr<JS::NonnullGCPtr<JS::Module>> {
+ // 1. Let moduleMap and referencingScript be null.
+ Optional<HTML::ModuleMap&> module_map;
+ Optional<HTML::Script&> referencing_script;
+
+ // 2. If referencingScriptOrModule is not null, then:
+ if (!referencing_string_or_module.has<Empty>()) {
+ // 1. Set referencingScript to referencingScriptOrModule.[[HostDefined]].
+ referencing_script = verify_cast<HTML::Script>(referencing_string_or_module.has<JS::NonnullGCPtr<JS::Script>>() ? *referencing_string_or_module.get<JS::NonnullGCPtr<JS::Script>>()->host_defined() : *referencing_string_or_module.get<JS::NonnullGCPtr<JS::Module>>()->host_defined());
+
+ // 2. Set moduleMap to referencingScript's settings object's module map.
+ module_map = referencing_script->settings_object().module_map();
+ }
+ // 3. Otherwise:
+ else {
+ // 1. Assert: there is a current settings object.
+ // NOTE: This is handled by the HTML::current_settings_object() accessor.
- // 2. Set moduleMap to the current settings object's module map.
- module_map = HTML::current_settings_object().module_map();
- }
+ // 2. Set moduleMap to the current settings object's module map.
+ module_map = HTML::current_settings_object().module_map();
+ }
- // 4. Let url be the result of resolving a module specifier given referencingScript and moduleRequest.[[Specifier]].
- auto url = MUST(HTML::resolve_module_specifier(referencing_script, module_request.module_specifier));
+ // 4. Let url be the result of resolving a module specifier given referencingScript and moduleRequest.[[Specifier]].
+ auto url = MUST(HTML::resolve_module_specifier(referencing_script, module_request.module_specifier));
- // 5. Assert: the previous step never throws an exception, because resolving a module specifier must have been previously successful
- // with these same two arguments (either while creating the corresponding module script, or in fetch an import() module script graph).
- // NOTE: Handled by MUST above.
+ // 5. Assert: the previous step never throws an exception, because resolving a module specifier must have been previously successful
+ // with these same two arguments (either while creating the corresponding module script, or in fetch an import() module script graph).
+ // NOTE: Handled by MUST above.
- // 6. Let moduleType be the result of running the module type from module request steps given moduleRequest.
- auto module_type = HTML::module_type_from_module_request(module_request);
+ // 6. Let moduleType be the result of running the module type from module request steps given moduleRequest.
+ auto module_type = HTML::module_type_from_module_request(module_request);
- // 7. Let resolvedModuleScript be moduleMap[(url, moduleType)]. (This entry must exist for us to have gotten to this point.)
- auto resolved_module_script = module_map->get(url, module_type).value();
+ // 7. Let resolvedModuleScript be moduleMap[(url, moduleType)]. (This entry must exist for us to have gotten to this point.)
+ auto resolved_module_script = module_map->get(url, module_type).value();
- // 8. Assert: resolvedModuleScript is a module script (i.e., is not null or "fetching").
- VERIFY(resolved_module_script.type == HTML::ModuleMap::EntryType::ModuleScript);
+ // 8. Assert: resolvedModuleScript is a module script (i.e., is not null or "fetching").
+ VERIFY(resolved_module_script.type == HTML::ModuleMap::EntryType::ModuleScript);
- // 9. Assert: resolvedModuleScript's record is not null.
- VERIFY(resolved_module_script.module_script->record());
+ // 9. Assert: resolvedModuleScript's record is not null.
+ VERIFY(resolved_module_script.module_script->record());
- // 10. Return resolvedModuleScript's record.
- return JS::NonnullGCPtr(*resolved_module_script.module_script->record());
- };
- }
- return *vm;
+ // 10. Return resolvedModuleScript's record.
+ return JS::NonnullGCPtr(*resolved_module_script.module_script->record());
+ };
+
+ return {};
+}
+
+JS::VM& main_thread_vm()
+{
+ VERIFY(s_main_thread_vm);
+ return *s_main_thread_vm;
}
// https://dom.spec.whatwg.org/#queue-a-mutation-observer-compound-microtask
diff --git a/Userland/Libraries/LibWeb/Bindings/MainThreadVM.h b/Userland/Libraries/LibWeb/Bindings/MainThreadVM.h
index 1fbac6e0b1..49b8a83d2f 100644
--- a/Userland/Libraries/LibWeb/Bindings/MainThreadVM.h
+++ b/Userland/Libraries/LibWeb/Bindings/MainThreadVM.h
@@ -50,7 +50,10 @@ struct WebEngineCustomJobCallbackData final : public JS::JobCallback::CustomData
};
HTML::Script* active_script();
+
+ErrorOr<void> initialize_main_thread_vm();
JS::VM& main_thread_vm();
+
void queue_mutation_observer_microtask(DOM::Document const&);
NonnullOwnPtr<JS::ExecutionContext> create_a_new_javascript_realm(JS::VM&, Function<JS::Object*(JS::Realm&)> create_global_object, Function<JS::Object*(JS::Realm&)> create_global_this_value);
diff --git a/Userland/Services/WebContent/main.cpp b/Userland/Services/WebContent/main.cpp
index 6403fb95a2..caa0a2e48d 100644
--- a/Userland/Services/WebContent/main.cpp
+++ b/Userland/Services/WebContent/main.cpp
@@ -12,6 +12,7 @@
#include <LibCore/System.h>
#include <LibIPC/SingleServer.h>
#include <LibMain/Main.h>
+#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/Loader/ResourceLoader.h>
#include <LibWeb/Platform/EventLoopPlugin.h>
#include <LibWeb/Platform/EventLoopPluginSerenity.h>
@@ -45,6 +46,7 @@ ErrorOr<int> serenity_main(Main::Arguments)
Web::WebSockets::WebSocketClientManager::initialize(TRY(WebView::WebSocketClientManagerAdapter::try_create()));
Web::ResourceLoader::initialize(TRY(WebView::RequestServerAdapter::try_create()));
+ TRY(Web::Bindings::initialize_main_thread_vm());
auto client = TRY(IPC::take_over_accepted_client_from_system_server<WebContent::ConnectionFromClient>());
return event_loop.exec();