diff options
-rw-r--r-- | Ladybird/WebContent/main.cpp | 3 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp | 564 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/Bindings/MainThreadVM.h | 3 | ||||
-rw-r--r-- | Userland/Services/WebContent/main.cpp | 2 |
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(); |