diff --git a/Libraries/LibWeb/Bindings/MainThreadVM.cpp b/Libraries/LibWeb/Bindings/MainThreadVM.cpp index b7b894dfe6f..fc5d6655ef0 100644 --- a/Libraries/LibWeb/Bindings/MainThreadVM.cpp +++ b/Libraries/LibWeb/Bindings/MainThreadVM.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -92,6 +93,10 @@ ErrorOr initialize_main_thread_vm(HTML::EventLoop::Type type) VERIFY(!s_main_thread_vm); s_main_thread_vm = TRY(JS::VM::create(make())); + + auto& custom_data = verify_cast(*s_main_thread_vm->custom_data()); + custom_data.agent.event_loop = s_main_thread_vm->heap().allocate(type); + s_main_thread_vm->on_unimplemented_property_access = [](auto const& object, auto const& property_key) { dbgln("FIXME: Unimplemented IDL interface: '{}.{}'", object.class_name(), property_key.to_string()); }; @@ -100,9 +105,6 @@ ErrorOr initialize_main_thread_vm(HTML::EventLoop::Type type) // This avoids doing an exhaustive garbage collection on process exit. s_main_thread_vm->ref(); - auto& custom_data = verify_cast(*s_main_thread_vm->custom_data()); - custom_data.event_loop = s_main_thread_vm->heap().allocate(type); - // These strings could potentially live on the VM similar to CommonPropertyNames. DOM::MutationType::initialize_strings(); Editing::CommandNames::initialize_strings(); @@ -692,25 +694,24 @@ JS::VM& main_thread_vm() void queue_mutation_observer_microtask(DOM::Document const& document) { auto& vm = main_thread_vm(); - auto& custom_data = verify_cast(*vm.custom_data()); + auto& surrounding_agent = verify_cast(*vm.custom_data()).agent; // 1. If the surrounding agent’s mutation observer microtask queued is true, then return. - if (custom_data.mutation_observer_microtask_queued) + if (surrounding_agent.mutation_observer_microtask_queued) return; // 2. Set the surrounding agent’s mutation observer microtask queued to true. - custom_data.mutation_observer_microtask_queued = true; + surrounding_agent.mutation_observer_microtask_queued = true; // 3. Queue a microtask to notify mutation observers. // NOTE: This uses the implied document concept. In the case of mutation observers, it is always done in a node context, so document should be that node's document. - // FIXME: Is it safe to pass custom_data through? - HTML::queue_a_microtask(&document, GC::create_function(vm.heap(), [&custom_data, &heap = document.heap()]() { + HTML::queue_a_microtask(&document, GC::create_function(vm.heap(), [&surrounding_agent, &heap = document.heap()]() { // 1. Set the surrounding agent’s mutation observer microtask queued to false. - custom_data.mutation_observer_microtask_queued = false; + surrounding_agent.mutation_observer_microtask_queued = false; // 2. Let notifySet be a clone of the surrounding agent’s mutation observers. GC::RootVector notify_set(heap); - for (auto& observer : custom_data.mutation_observers) + for (auto& observer : surrounding_agent.mutation_observers) notify_set.append(observer); // FIXME: 3. Let signalSet be a clone of the surrounding agent’s signal slots. diff --git a/Libraries/LibWeb/Bindings/MainThreadVM.h b/Libraries/LibWeb/Bindings/MainThreadVM.h index 97ca4ca133b..bc46a89fd66 100644 --- a/Libraries/LibWeb/Bindings/MainThreadVM.h +++ b/Libraries/LibWeb/Bindings/MainThreadVM.h @@ -13,56 +13,16 @@ #include #include #include +#include namespace Web::Bindings { -// https://html.spec.whatwg.org/multipage/custom-elements.html#custom-element-reactions-stack -struct CustomElementReactionsStack { - CustomElementReactionsStack() = default; - ~CustomElementReactionsStack() = default; - - // https://html.spec.whatwg.org/multipage/custom-elements.html#element-queue - // Each item in the stack is an element queue, which is initially empty as well. Each item in an element queue is an element. - // (The elements are not necessarily custom yet, since this queue is used for upgrades as well.) - Vector>> element_queue_stack; - - // https://html.spec.whatwg.org/multipage/custom-elements.html#backup-element-queue - // Each custom element reactions stack has an associated backup element queue, which an initially-empty element queue. - Vector> backup_element_queue; - - // https://html.spec.whatwg.org/multipage/custom-elements.html#processing-the-backup-element-queue - // To prevent reentrancy when processing the backup element queue, each custom element reactions stack also has a processing the backup element queue flag, initially unset. - bool processing_the_backup_element_queue { false }; -}; - struct WebEngineCustomData final : public JS::VM::CustomData { virtual ~WebEngineCustomData() override = default; virtual void spin_event_loop_until(GC::Root> goal_condition) override; - GC::Root event_loop; - - // FIXME: These should only be on similar-origin window agents, but we don't currently differentiate agent types. - - // https://dom.spec.whatwg.org/#mutation-observer-compound-microtask-queued-flag - bool mutation_observer_microtask_queued { false }; - - // https://dom.spec.whatwg.org/#mutation-observer-list - // FIXME: This should be a set. - Vector> mutation_observers; - - GC::Root internal_realm; - - OwnPtr root_execution_context; - - // https://html.spec.whatwg.org/multipage/custom-elements.html#custom-element-reactions-stack - // Each similar-origin window agent has a custom element reactions stack, which is initially empty. - CustomElementReactionsStack custom_element_reactions_stack {}; - - // https://html.spec.whatwg.org/multipage/custom-elements.html#current-element-queue - // A similar-origin window agent's current element queue is the element queue at the top of its custom element reactions stack. - Vector>& current_element_queue() { return custom_element_reactions_stack.element_queue_stack.last(); } - Vector> const& current_element_queue() const { return custom_element_reactions_stack.element_queue_stack.last(); } + HTML::Agent agent; }; struct WebEngineCustomJobCallbackData final : public JS::JobCallback::CustomData { diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 109bdc40524..5090decc633 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -462,6 +462,7 @@ set(SOURCES HTML/PotentialCORSRequest.cpp HTML/PromiseRejectionEvent.cpp HTML/RadioNodeList.cpp + HTML/Scripting/Agent.cpp HTML/Scripting/ClassicScript.cpp HTML/Scripting/Environments.cpp HTML/Scripting/EnvironmentSettingsSnapshot.cpp diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index feaf8926109..feea9def9b9 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -118,6 +118,7 @@ #include #include #include +#include #include #include #include @@ -3949,7 +3950,7 @@ void Document::unload(GC::Ptr) auto intend_to_store_in_bfcache = false; // 6. Let eventLoop be oldDocument's relevant agent's event loop. - auto& event_loop = *verify_cast(*HTML::relevant_agent(*this).custom_data()).event_loop; + auto& event_loop = *HTML::relevant_agent(*this).event_loop; // 7. Increase eventLoop's termination nesting level by 1. event_loop.increment_termination_nesting_level(); @@ -6096,7 +6097,7 @@ Document::StepsToFireBeforeunloadResult Document::steps_to_fire_beforeunload(boo m_unload_counter++; // 3. Increase document's relevant agent's event loop's termination nesting level by 1. - auto& event_loop = *verify_cast(*HTML::relevant_agent(*this).custom_data()).event_loop; + auto& event_loop = *HTML::relevant_agent(*this).event_loop; event_loop.increment_termination_nesting_level(); // 4. Let eventFiringResult be the result of firing an event named beforeunload at document's relevant global object, diff --git a/Libraries/LibWeb/DOM/Element.cpp b/Libraries/LibWeb/DOM/Element.cpp index 1a77f1d8905..efe8ad47dff 100644 --- a/Libraries/LibWeb/DOM/Element.cpp +++ b/Libraries/LibWeb/DOM/Element.cpp @@ -54,6 +54,7 @@ #include #include #include +#include #include #include #include @@ -2088,8 +2089,7 @@ void Element::enqueue_an_element_on_the_appropriate_element_queue() { // 1. Let reactionsStack be element's relevant agent's custom element reactions stack. auto& relevant_agent = HTML::relevant_agent(*this); - auto* custom_data = verify_cast(relevant_agent.custom_data()); - auto& reactions_stack = custom_data->custom_element_reactions_stack; + auto& reactions_stack = relevant_agent.custom_element_reactions_stack; // 2. If reactionsStack is empty, then: if (reactions_stack.element_queue_stack.is_empty()) { @@ -2105,10 +2105,8 @@ void Element::enqueue_an_element_on_the_appropriate_element_queue() // 4. Queue a microtask to perform the following steps: // NOTE: `this` is protected by GC::Function - HTML::queue_a_microtask(&document(), GC::create_function(relevant_agent.heap(), [this]() { - auto& relevant_agent = HTML::relevant_agent(*this); - auto* custom_data = verify_cast(relevant_agent.custom_data()); - auto& reactions_stack = custom_data->custom_element_reactions_stack; + HTML::queue_a_microtask(&document(), GC::create_function(heap(), [this]() { + auto& reactions_stack = HTML::relevant_agent(*this).custom_element_reactions_stack; // 1. Invoke custom element reactions in reactionsStack's backup element queue. Bindings::invoke_custom_element_reactions(reactions_stack.backup_element_queue); @@ -2121,7 +2119,7 @@ void Element::enqueue_an_element_on_the_appropriate_element_queue() } // 3. Otherwise, add element to element's relevant agent's current element queue. - custom_data->current_element_queue().append(*this); + relevant_agent.current_element_queue().append(*this); } // https://html.spec.whatwg.org/multipage/custom-elements.html#enqueue-a-custom-element-upgrade-reaction diff --git a/Libraries/LibWeb/DOM/MutationObserver.cpp b/Libraries/LibWeb/DOM/MutationObserver.cpp index 03a95aa830e..5393670ea59 100644 --- a/Libraries/LibWeb/DOM/MutationObserver.cpp +++ b/Libraries/LibWeb/DOM/MutationObserver.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace Web::DOM { @@ -29,14 +30,12 @@ MutationObserver::MutationObserver(JS::Realm& realm, GC::Ptr(realm.vm().custom_data()); - agent_custom_data->mutation_observers.append(*this); + HTML::relevant_agent(*this).mutation_observers.append(*this); } MutationObserver::~MutationObserver() { - auto* agent_custom_data = verify_cast(vm().custom_data()); - agent_custom_data->mutation_observers.remove_all_matching([this](auto& observer) { + HTML::relevant_agent(*this).mutation_observers.remove_all_matching([this](auto& observer) { return observer.ptr() == this; }); } diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index 02dae80b457..07d6692fa9e 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -552,6 +552,7 @@ enum class AllowMultipleFiles; enum class MediaSeekMode; enum class SandboxingFlagSet; +struct Agent; struct EmbedderPolicy; struct Environment; struct EnvironmentSettingsObject; diff --git a/Libraries/LibWeb/HTML/CustomElements/CustomElementReactionsStack.h b/Libraries/LibWeb/HTML/CustomElements/CustomElementReactionsStack.h new file mode 100644 index 00000000000..92656cc6ecd --- /dev/null +++ b/Libraries/LibWeb/HTML/CustomElements/CustomElementReactionsStack.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021-2023, Luke Wilde + * Copyright (c) 2025, Shannon Booth + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace Web::HTML { + +// https://html.spec.whatwg.org/multipage/custom-elements.html#custom-element-reactions-stack +struct CustomElementReactionsStack { + CustomElementReactionsStack() = default; + ~CustomElementReactionsStack() = default; + + // https://html.spec.whatwg.org/multipage/custom-elements.html#element-queue + // Each item in the stack is an element queue, which is initially empty as well. Each item in an element queue is an element. + // (The elements are not necessarily custom yet, since this queue is used for upgrades as well.) + Vector>> element_queue_stack; + + // https://html.spec.whatwg.org/multipage/custom-elements.html#backup-element-queue + // Each custom element reactions stack has an associated backup element queue, which an initially-empty element queue. + Vector> backup_element_queue; + + // https://html.spec.whatwg.org/multipage/custom-elements.html#processing-the-backup-element-queue + // To prevent reentrancy when processing the backup element queue, each custom element reactions stack also has a processing the backup element queue flag, initially unset. + bool processing_the_backup_element_queue { false }; +}; + +} diff --git a/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp b/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp index 4a6d370f841..13cc1bca9b8 100644 --- a/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp +++ b/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -67,7 +68,7 @@ void EventLoop::schedule() EventLoop& main_thread_event_loop() { - return *static_cast(Bindings::main_thread_vm().custom_data())->event_loop; + return *static_cast(Bindings::main_thread_vm().custom_data())->agent.event_loop; } // https://html.spec.whatwg.org/multipage/webappapis.html#spin-the-event-loop @@ -460,8 +461,7 @@ TaskID queue_a_task(HTML::Task::Source source, GC::Ptr event_loop, GC TaskID queue_global_task(HTML::Task::Source source, JS::Object& global_object, GC::Ref> steps) { // 1. Let event loop be global's relevant agent's event loop. - auto& global_custom_data = verify_cast(*global_object.vm().custom_data()); - auto& event_loop = global_custom_data.event_loop; + auto& event_loop = relevant_agent(global_object).event_loop; // 2. Let document be global's associated Document, if global is a Window object; otherwise null. DOM::Document* document { nullptr }; diff --git a/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp b/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp index 3f36c6a6ac1..be21f5b48e0 100644 --- a/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp +++ b/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -752,8 +753,7 @@ GC::Ref HTMLParser::create_element_for(HTMLToken const& token, Opt perform_a_microtask_checkpoint(); // 3. Push a new element queue onto document's relevant agent's custom element reactions stack. - auto& custom_data = verify_cast(*vm.custom_data()); - custom_data.custom_element_reactions_stack.element_queue_stack.append({}); + relevant_agent(document).custom_element_reactions_stack.element_queue_stack.append({}); } // 9. Let element be the result of creating an element given document, localName, given namespace, null, is, and willExecuteScript. @@ -770,9 +770,7 @@ GC::Ref HTMLParser::create_element_for(HTMLToken const& token, Opt // 11. If willExecuteScript is true: if (will_execute_script) { // 1. Let queue be the result of popping from document's relevant agent's custom element reactions stack. (This will be the same element queue as was pushed above.) - auto& vm = main_thread_event_loop().vm(); - auto& custom_data = verify_cast(*vm.custom_data()); - auto queue = custom_data.custom_element_reactions_stack.element_queue_stack.take_last(); + auto queue = relevant_agent(document).custom_element_reactions_stack.element_queue_stack.take_last(); // 2. Invoke custom element reactions in queue. Bindings::invoke_custom_element_reactions(queue); @@ -5150,8 +5148,7 @@ void HTMLParser::insert_an_element_at_the_adjusted_insertion_location(GC::Ref(*relevant_agent(*element).custom_data()); - custom_data.custom_element_reactions_stack.element_queue_stack.append({}); + relevant_agent(*element).custom_element_reactions_stack.element_queue_stack.append({}); } // 4. Insert element at the adjusted insertion location. @@ -5160,8 +5157,7 @@ void HTMLParser::insert_an_element_at_the_adjusted_insertion_location(GC::Ref(*relevant_agent(*element).custom_data()); - auto queue = custom_data.custom_element_reactions_stack.element_queue_stack.take_last(); + auto queue = relevant_agent(*element).custom_element_reactions_stack.element_queue_stack.take_last(); Bindings::invoke_custom_element_reactions(queue); } } diff --git a/Libraries/LibWeb/HTML/Scripting/Agent.cpp b/Libraries/LibWeb/HTML/Scripting/Agent.cpp new file mode 100644 index 00000000000..d148f7ff718 --- /dev/null +++ b/Libraries/LibWeb/HTML/Scripting/Agent.cpp @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025, Shannon Booth + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace Web::HTML { + +// https://html.spec.whatwg.org/multipage/webappapis.html#relevant-agent +Agent& relevant_agent(JS::Object const& object) +{ + // The relevant agent for a platform object platformObject is platformObject's relevant Realm's agent. + // Spec Note: This pointer is not yet defined in the JavaScript specification; see tc39/ecma262#1357. + return verify_cast(relevant_realm(object).vm().custom_data())->agent; +} + +} diff --git a/Libraries/LibWeb/HTML/Scripting/Agent.h b/Libraries/LibWeb/HTML/Scripting/Agent.h new file mode 100644 index 00000000000..4a69bbda1bb --- /dev/null +++ b/Libraries/LibWeb/HTML/Scripting/Agent.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2025, Shannon Booth + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Web::HTML { + +// https://html.spec.whatwg.org/multipage/webappapis.html#similar-origin-window-agent +struct Agent { + GC::Root event_loop; + + // FIXME: These should only be on similar-origin window agents, but we don't currently differentiate agent types. + + // https://dom.spec.whatwg.org/#mutation-observer-compound-microtask-queued-flag + bool mutation_observer_microtask_queued { false }; + + // https://dom.spec.whatwg.org/#mutation-observer-list + // FIXME: This should be a set. + Vector> mutation_observers; + + // https://html.spec.whatwg.org/multipage/custom-elements.html#custom-element-reactions-stack + // Each similar-origin window agent has a custom element reactions stack, which is initially empty. + CustomElementReactionsStack custom_element_reactions_stack {}; + + // https://html.spec.whatwg.org/multipage/custom-elements.html#current-element-queue + // A similar-origin window agent's current element queue is the element queue at the top of its custom element reactions stack. + Vector>& current_element_queue() { return custom_element_reactions_stack.element_queue_stack.last(); } + Vector> const& current_element_queue() const { return custom_element_reactions_stack.element_queue_stack.last(); } +}; + +Agent& relevant_agent(JS::Object const&); + +} diff --git a/Libraries/LibWeb/HTML/Scripting/Environments.cpp b/Libraries/LibWeb/HTML/Scripting/Environments.cpp index 217711cc0b7..06634c6de90 100644 --- a/Libraries/LibWeb/HTML/Scripting/Environments.cpp +++ b/Libraries/LibWeb/HTML/Scripting/Environments.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -102,10 +103,8 @@ EventLoop& EnvironmentSettingsObject::responsible_event_loop() if (m_responsible_event_loop) return *m_responsible_event_loop; - auto& vm = global_object().vm(); - auto& event_loop = verify_cast(vm.custom_data())->event_loop; - m_responsible_event_loop = event_loop; - return *event_loop; + m_responsible_event_loop = relevant_agent(global_object()).event_loop; + return *m_responsible_event_loop; } // https://html.spec.whatwg.org/multipage/webappapis.html#check-if-we-can-run-script @@ -513,13 +512,6 @@ JS::Object& entry_global_object() return entry_realm().global_object(); } -JS::VM& relevant_agent(JS::Object const& object) -{ - // The relevant agent for a platform object platformObject is platformObject's relevant Realm's agent. - // Spec Note: This pointer is not yet defined in the JavaScript specification; see tc39/ecma262#1357. - return relevant_realm(object).vm(); -} - // https://html.spec.whatwg.org/multipage/webappapis.html#secure-context bool is_secure_context(Environment const& environment) { diff --git a/Libraries/LibWeb/HTML/Scripting/Environments.h b/Libraries/LibWeb/HTML/Scripting/Environments.h index d7adc8d6c74..532d1dc36d7 100644 --- a/Libraries/LibWeb/HTML/Scripting/Environments.h +++ b/Libraries/LibWeb/HTML/Scripting/Environments.h @@ -168,7 +168,6 @@ JS::Object& relevant_principal_global_object(JS::Object const&); JS::Realm& entry_realm(); EnvironmentSettingsObject& entry_settings_object(); JS::Object& entry_global_object(); -JS::VM& relevant_agent(JS::Object const&); [[nodiscard]] bool is_secure_context(Environment const&); [[nodiscard]] bool is_non_secure_context(Environment const&); diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp index 1b97d142a12..07332287523 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp @@ -2077,9 +2077,7 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::@function.name:snakecase@@overload_suffi if (function.extended_attributes.contains("CEReactions")) { // 1. Push a new element queue onto this object's relevant agent's custom element reactions stack. function_generator.append(R"~~~( - auto& relevant_agent = HTML::relevant_agent(*impl); - auto* custom_data = verify_cast(relevant_agent.custom_data()); - auto& reactions_stack = custom_data->custom_element_reactions_stack; + auto& reactions_stack = HTML::relevant_agent(*impl).custom_element_reactions_stack; reactions_stack.element_queue_stack.append({}); )~~~"); } @@ -3566,9 +3564,7 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::@attribute.getter_callback@) if (attribute.extended_attributes.contains("CEReactions")) { // 1. Push a new element queue onto this object's relevant agent's custom element reactions stack. attribute_generator.append(R"~~~( - auto& relevant_agent = HTML::relevant_agent(*impl); - auto* custom_data = verify_cast(relevant_agent.custom_data()); - auto& reactions_stack = custom_data->custom_element_reactions_stack; + auto& reactions_stack = HTML::relevant_agent(*impl).custom_element_reactions_stack; reactions_stack.element_queue_stack.append({}); )~~~"); } @@ -3914,9 +3910,7 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::@attribute.setter_callback@) if (attribute.extended_attributes.contains("CEReactions")) { // 1. Push a new element queue onto this object's relevant agent's custom element reactions stack. attribute_generator.append(R"~~~( - auto& relevant_agent = HTML::relevant_agent(*impl); - auto* custom_data = verify_cast(relevant_agent.custom_data()); - auto& reactions_stack = custom_data->custom_element_reactions_stack; + auto& reactions_stack = HTML::relevant_agent(*impl).custom_element_reactions_stack; reactions_stack.element_queue_stack.append({}); )~~~"); } @@ -4912,6 +4906,7 @@ void generate_prototype_implementation(IDL::Interface const& interface, StringBu #include #include #include +#include #include #include #include